mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-27 21:01:57 +00:00
Compare commits
5 Commits
hotfix/fix
...
users/srna
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
832f8d560d | ||
|
|
d85c96d408 | ||
|
|
bad6a60d07 | ||
|
|
b690fe18e6 | ||
|
|
1bbe08378c |
@@ -6,7 +6,7 @@ export enum Platform {
|
|||||||
|
|
||||||
interface ConfigContext {
|
interface ConfigContext {
|
||||||
platform: Platform;
|
platform: Platform;
|
||||||
allowedParentFrameOrigins: RegExp;
|
allowedParentFrameOrigins: string[];
|
||||||
gitSha?: string;
|
gitSha?: string;
|
||||||
proxyPath?: string;
|
proxyPath?: string;
|
||||||
AAD_ENDPOINT: string;
|
AAD_ENDPOINT: string;
|
||||||
@@ -30,7 +30,12 @@ interface ConfigContext {
|
|||||||
// Default configuration
|
// Default configuration
|
||||||
let configContext: Readonly<ConfigContext> = {
|
let configContext: Readonly<ConfigContext> = {
|
||||||
platform: Platform.Portal,
|
platform: Platform.Portal,
|
||||||
allowedParentFrameOrigins: /^https:\/\/portal\.azure\.com$|^https:\/\/portal\.azure\.us$|^https:\/\/portal\.azure\.cn$|^https:\/\/portal\.microsoftazure\.de$|^https:\/\/.+\.portal\.azure\.com$|^https:\/\/.+\.portal\.azure\.us$|^https:\/\/.+\.portal\.azure\.cn$|^https:\/\/.+\.portal\.microsoftazure\.de$|^https:\/\/main\.documentdb\.ext\.azure\.com$|^https:\/\/main\.documentdb\.ext\.microsoftazure\.de$|^https:\/\/main\.documentdb\.ext\.azure\.cn$|^https:\/\/main\.documentdb\.ext\.azure\.us$/,
|
allowedParentFrameOrigins: [
|
||||||
|
`^https:\\/\\/cosmos.azure.(com|cn|us)$`,
|
||||||
|
`^https:\\/\\/[\\.\\w]+.portal.azure.(com|cn|us)$`,
|
||||||
|
`^https:\\/\\/[\\.\\w]+.ext.azure.(com|cn|us)$`,
|
||||||
|
`^https:\\/\\/[\\.\\w]+microsoftazure.de$`
|
||||||
|
],
|
||||||
// Webpack injects this at build time
|
// Webpack injects this at build time
|
||||||
gitSha: process.env.GIT_SHA,
|
gitSha: process.env.GIT_SHA,
|
||||||
hostedExplorerURL: "https://cosmos.azure.com/",
|
hostedExplorerURL: "https://cosmos.azure.com/",
|
||||||
@@ -73,8 +78,13 @@ export async function initializeConfiguration(): Promise<ConfigContext> {
|
|||||||
const response = await fetch("./config.json");
|
const response = await fetch("./config.json");
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
try {
|
try {
|
||||||
const externalConfig = await response.json();
|
const { allowedParentFrameOrigins, ...externalConfig } = await response.json();
|
||||||
Object.assign(configContext, externalConfig);
|
Object.assign(configContext, externalConfig);
|
||||||
|
if (allowedParentFrameOrigins && allowedParentFrameOrigins.length > 0) {
|
||||||
|
updateConfigContext({
|
||||||
|
allowedParentFrameOrigins: [...configContext.allowedParentFrameOrigins, ...allowedParentFrameOrigins]
|
||||||
|
});
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Unable to parse json in config file");
|
console.error("Unable to parse json in config file");
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ export class DynamicListViewModel extends WaitsForTemplateViewModel {
|
|||||||
public onRemoveItemKeyPress = (data: any, event: KeyboardEvent, source: any): boolean => {
|
public onRemoveItemKeyPress = (data: any, event: KeyboardEvent, source: any): boolean => {
|
||||||
if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) {
|
if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) {
|
||||||
this.removeItem(data, event);
|
this.removeItem(data, event);
|
||||||
|
(document.querySelector(".dynamicListItem:last-of-type input") as HTMLElement).focus();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -94,7 +95,7 @@ export class DynamicListViewModel extends WaitsForTemplateViewModel {
|
|||||||
|
|
||||||
public addItem(): void {
|
public addItem(): void {
|
||||||
this.listItems.push({ value: ko.observable("") });
|
this.listItems.push({ value: ko.observable("") });
|
||||||
document.getElementById("uniqueKeyItems").focus();
|
(document.querySelector(".dynamicListItem:last-of-type input") as HTMLElement).focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
public onAddItemKeyPress = (source: any, event: KeyboardEvent): boolean => {
|
public onAddItemKeyPress = (source: any, event: KeyboardEvent): boolean => {
|
||||||
|
|||||||
@@ -115,10 +115,10 @@
|
|||||||
|
|
||||||
<!-- Database provisioned throughput - Start -->
|
<!-- Database provisioned throughput - Start -->
|
||||||
<!-- ko if: canConfigureThroughput -->
|
<!-- ko if: canConfigureThroughput -->
|
||||||
<div class="databaseProvision" aria-label="New database provision support"
|
<div class="databaseProvision" aria-label="Provision database throughput"
|
||||||
data-bind="visible: databaseCreateNew">
|
data-bind="visible: databaseCreateNew">
|
||||||
<input tabindex="0" type="checkbox" data-test="addCollectionPane-databaseSharedThroughput"
|
<input tabindex="0" type="checkbox" data-test="addCollectionPane-databaseSharedThroughput"
|
||||||
id="addCollection-databaseSharedThroughput" title="Provision shared throughput"
|
id="addCollection-databaseSharedThroughput" title="Provision database throughput"
|
||||||
data-bind="checked: databaseCreateNewShared" />
|
data-bind="checked: databaseCreateNewShared" />
|
||||||
<span class="databaseProvisionText" for="databaseSharedThroughput">Provision database throughput</span>
|
<span class="databaseProvisionText" for="databaseSharedThroughput">Provision database throughput</span>
|
||||||
<span class="infoTooltip" role="tooltip" tabindex="0">
|
<span class="infoTooltip" role="tooltip" tabindex="0">
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import Explorer from "../Explorer";
|
|||||||
|
|
||||||
// TODO: Use specific actions for logging telemetry data
|
// TODO: Use specific actions for logging telemetry data
|
||||||
export abstract class ContextualPaneBase extends WaitsForTemplateViewModel {
|
export abstract class ContextualPaneBase extends WaitsForTemplateViewModel {
|
||||||
|
private initalFocusedElement: HTMLElement | undefined;
|
||||||
public id: string;
|
public id: string;
|
||||||
public container: Explorer;
|
public container: Explorer;
|
||||||
public firstFieldHasFocus: ko.Observable<boolean>;
|
public firstFieldHasFocus: ko.Observable<boolean>;
|
||||||
@@ -49,9 +50,11 @@ export abstract class ContextualPaneBase extends WaitsForTemplateViewModel {
|
|||||||
this.visible(false);
|
this.visible(false);
|
||||||
this.isExecuting(false);
|
this.isExecuting(false);
|
||||||
this.resetData();
|
this.resetData();
|
||||||
|
this.resetFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
public open() {
|
public open() {
|
||||||
|
this.initalFocusedElement = document.activeElement as HTMLElement;
|
||||||
this.visible(true);
|
this.visible(true);
|
||||||
this.firstFieldHasFocus(true);
|
this.firstFieldHasFocus(true);
|
||||||
this.resizePane();
|
this.resizePane();
|
||||||
@@ -123,4 +126,11 @@ export abstract class ContextualPaneBase extends WaitsForTemplateViewModel {
|
|||||||
|
|
||||||
$(paneElement).height(newPaneElementHeight);
|
$(paneElement).height(newPaneElementHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private resetFocus(): void {
|
||||||
|
if (this.initalFocusedElement) {
|
||||||
|
this.initalFocusedElement.focus();
|
||||||
|
this.initalFocusedElement = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,8 +67,7 @@
|
|||||||
name="collectionIdConfirmation"
|
name="collectionIdConfirmation"
|
||||||
required
|
required
|
||||||
class="collid"
|
class="collid"
|
||||||
data-bind="value: collectionIdConfirmation, hasFocus: firstFieldHasFocus"
|
data-bind="value: collectionIdConfirmation, hasFocus: firstFieldHasFocus, attr: { 'aria-label': collectionIdConfirmationText }"
|
||||||
aria-label="Confirm by typing the collection id"
|
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ export default class AddTableEntityPane extends TableEntityPane {
|
|||||||
this.updateIsActionEnabled();
|
this.updateIsActionEnabled();
|
||||||
super.open();
|
super.open();
|
||||||
}
|
}
|
||||||
const focusElement = document.getElementById("addTableEntityValue");
|
const focusElement = document.getElementById("closeAddEntityPane");
|
||||||
focusElement && focusElement.focus();
|
focusElement && focusElement.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
<div class="firstdivbg headerline">
|
<div class="firstdivbg headerline">
|
||||||
<span role="heading" aria-level="2" data-bind="text: title"></span>
|
<span role="heading" aria-level="2" data-bind="text: title"></span>
|
||||||
<div
|
<div
|
||||||
|
id="closeAddEntityPane"
|
||||||
class="closeImg"
|
class="closeImg"
|
||||||
role="button"
|
role="button"
|
||||||
aria-label="Close pane"
|
aria-label="Close pane"
|
||||||
|
|||||||
21
src/Utils/MessageValidation.test.ts
Normal file
21
src/Utils/MessageValidation.test.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { isInvalidParentFrameOrigin } from "./MessageValidation";
|
||||||
|
|
||||||
|
test.each`
|
||||||
|
domain | expected
|
||||||
|
${"https://cosmos.azure.com"} | ${false}
|
||||||
|
${"https://cosmos.azure.us"} | ${false}
|
||||||
|
${"https://cosmos.azure.cn"} | ${false}
|
||||||
|
${"https://cosmos.microsoftazure.de"} | ${false}
|
||||||
|
${"https://subdomain.portal.azure.com"} | ${false}
|
||||||
|
${"https://subdomain.portal.azure.us"} | ${false}
|
||||||
|
${"https://subdomain.portal.azure.cn"} | ${false}
|
||||||
|
${"https://subdomain.microsoftazure.de"} | ${false}
|
||||||
|
${"https://main.documentdb.ext.azure.com"} | ${false}
|
||||||
|
${"https://main.documentdb.ext.azure.us"} | ${false}
|
||||||
|
${"https://main.documentdb.ext.azure.cn"} | ${false}
|
||||||
|
${"https://main.documentdb.ext.microsoftazure.de"} | ${false}
|
||||||
|
${"https://random.domain"} | ${true}
|
||||||
|
${"https://malicious.cloudapp.azure.com"} | ${true}
|
||||||
|
`("returns $expected when called with $domain", ({ domain, expected }) => {
|
||||||
|
expect(isInvalidParentFrameOrigin({ origin: domain } as MessageEvent)).toBe(expected);
|
||||||
|
});
|
||||||
@@ -4,13 +4,18 @@ export function isInvalidParentFrameOrigin(event: MessageEvent): boolean {
|
|||||||
return !isValidOrigin(configContext.allowedParentFrameOrigins, event);
|
return !isValidOrigin(configContext.allowedParentFrameOrigins, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isValidOrigin(allowedOrigins: RegExp, event: MessageEvent): boolean {
|
function isValidOrigin(allowedOrigins: string[], event: MessageEvent): boolean {
|
||||||
const eventOrigin = (event && event.origin) || "";
|
const eventOrigin = (event && event.origin) || "";
|
||||||
const windowOrigin = (window && window.origin) || "";
|
const windowOrigin = (window && window.origin) || "";
|
||||||
if (eventOrigin === windowOrigin) {
|
if (eventOrigin === windowOrigin) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = allowedOrigins && allowedOrigins.test(eventOrigin);
|
for (const origin of allowedOrigins) {
|
||||||
return result;
|
const result = new RegExp(origin).test(eventOrigin);
|
||||||
|
if (result) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user