mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-21 09:51:11 +00:00
WIP, checkpoint
This commit is contained in:
@@ -2,5 +2,6 @@ export enum AuthType {
|
|||||||
AAD = "aad",
|
AAD = "aad",
|
||||||
EncryptedToken = "encryptedtoken",
|
EncryptedToken = "encryptedtoken",
|
||||||
MasterKey = "masterkey",
|
MasterKey = "masterkey",
|
||||||
ResourceToken = "resourcetoken"
|
ResourceToken = "resourcetoken",
|
||||||
|
ConnectionString = "connectionstring"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -587,11 +587,3 @@ export interface MemoryUsageInfo {
|
|||||||
freeKB: number;
|
freeKB: number;
|
||||||
totalKB: number;
|
totalKB: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface resourceTokenConnectionStringProperties {
|
|
||||||
accountEndpoint: string;
|
|
||||||
collectionId: string;
|
|
||||||
databaseId: string;
|
|
||||||
partitionKey?: string;
|
|
||||||
resourceToken: string;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -17,15 +17,10 @@ import "./Shared/appInsights";
|
|||||||
import { SignInButton } from "./Platform/Hosted/Components/SignInButton";
|
import { SignInButton } from "./Platform/Hosted/Components/SignInButton";
|
||||||
import { useAADAuth } from "./hooks/useAADAuth";
|
import { useAADAuth } from "./hooks/useAADAuth";
|
||||||
import { FeedbackCommandButton } from "./Platform/Hosted/Components/FeedbackCommandButton";
|
import { FeedbackCommandButton } from "./Platform/Hosted/Components/FeedbackCommandButton";
|
||||||
|
import { HostedExplorerChildFrame } from "./HostedExplorerChildFrame";
|
||||||
|
|
||||||
initializeIcons();
|
initializeIcons();
|
||||||
|
|
||||||
interface HostedExplorerChildFrame extends Window {
|
|
||||||
authType: AuthType;
|
|
||||||
databaseAccount: DatabaseAccount;
|
|
||||||
authorizationToken: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const App: React.FunctionComponent = () => {
|
const App: React.FunctionComponent = () => {
|
||||||
// For handling encrypted portal tokens sent via query paramter
|
// For handling encrypted portal tokens sent via query paramter
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
@@ -37,6 +32,8 @@ const App: React.FunctionComponent = () => {
|
|||||||
|
|
||||||
const { isLoggedIn, armToken, graphToken, account, tenantId, logout, login } = useAADAuth();
|
const { isLoggedIn, armToken, graphToken, account, tenantId, logout, login } = useAADAuth();
|
||||||
const [databaseAccount, setDatabaseAccount] = React.useState<DatabaseAccount>();
|
const [databaseAccount, setDatabaseAccount] = React.useState<DatabaseAccount>();
|
||||||
|
const [authType, setAuthType] = React.useState<AuthType>(encryptedToken ? AuthType.EncryptedToken : undefined);
|
||||||
|
const [connectionString, setConnectionString] = React.useState<string>();
|
||||||
|
|
||||||
const ref = React.useRef<HTMLIFrameElement>();
|
const ref = React.useRef<HTMLIFrameElement>();
|
||||||
|
|
||||||
@@ -46,14 +43,31 @@ const App: React.FunctionComponent = () => {
|
|||||||
// In hosted mode, we can set global properties directly on the child iframe.
|
// In hosted mode, we can set global properties directly on the child iframe.
|
||||||
// This is not possible in the portal where the iframes have different origins
|
// This is not possible in the portal where the iframes have different origins
|
||||||
const frameWindow = ref.current.contentWindow as HostedExplorerChildFrame;
|
const frameWindow = ref.current.contentWindow as HostedExplorerChildFrame;
|
||||||
frameWindow.authType = AuthType.AAD;
|
// AAD authenticated uses ALWAYS using AAD authType
|
||||||
frameWindow.databaseAccount = databaseAccount;
|
if (isLoggedIn) {
|
||||||
frameWindow.authorizationToken = armToken;
|
frameWindow.hostedConfig = {
|
||||||
// const frameWindow = ref.current.contentWindow;
|
authType: AuthType.AAD,
|
||||||
// frameWindow.authType = AuthType.EncryptedToken;
|
databaseAccount,
|
||||||
// frameWindow.encryptedToken = encryptedToken;
|
authorizationToken: armToken
|
||||||
// frameWindow.encryptedTokenMetadata = encryptedTokenMetadata;
|
};
|
||||||
// frameWindow.parsedConnectionString = "foo";
|
} else if (authType === AuthType.EncryptedToken) {
|
||||||
|
frameWindow.hostedConfig = {
|
||||||
|
authType: AuthType.EncryptedToken,
|
||||||
|
encryptedToken,
|
||||||
|
encryptedTokenMetadata
|
||||||
|
};
|
||||||
|
} else if (authType === AuthType.ConnectionString) {
|
||||||
|
frameWindow.hostedConfig = {
|
||||||
|
authType: AuthType.ConnectionString,
|
||||||
|
encryptedToken,
|
||||||
|
encryptedTokenMetadata
|
||||||
|
};
|
||||||
|
} else if (authType === AuthType.ResourceToken) {
|
||||||
|
frameWindow.hostedConfig = {
|
||||||
|
authType: AuthType.ResourceToken,
|
||||||
|
resourceToken: connectionString
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [ref, encryptedToken, encryptedTokenMetadata, isLoggedIn, databaseAccount]);
|
}, [ref, encryptedToken, encryptedTokenMetadata, isLoggedIn, databaseAccount]);
|
||||||
|
|
||||||
@@ -95,14 +109,15 @@ const App: React.FunctionComponent = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
{databaseAccount && (
|
{(isLoggedIn && databaseAccount) ||
|
||||||
|
(encryptedTokenMetadata && encryptedTokenMetadata && (
|
||||||
// Ideally we would import and render data explorer like any other React component, however
|
// Ideally we would import and render data explorer like any other React component, however
|
||||||
// because it still has a significant amount of Knockout code, this would lead to memory leaks.
|
// because it still has a significant amount of Knockout code, this would lead to memory leaks.
|
||||||
// Knockout does not have a way to tear down all of its binding and listeners with a single method.
|
// Knockout does not have a way to tear down all of its binding and listeners with a single method.
|
||||||
// It's possible this can be changed once all knockout code has been removed.
|
// It's possible this can be changed once all knockout code has been removed.
|
||||||
<iframe
|
<iframe
|
||||||
// Setting key is needed so React will re-render this element on any account change
|
// Setting key is needed so React will re-render this element on any account change
|
||||||
key={databaseAccount.id}
|
key={databaseAccount?.id || encryptedTokenMetadata?.accountName}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
id="explorerMenu"
|
id="explorerMenu"
|
||||||
name="explorer"
|
name="explorer"
|
||||||
@@ -110,8 +125,10 @@ const App: React.FunctionComponent = () => {
|
|||||||
title="explorer"
|
title="explorer"
|
||||||
src="explorer.html?v=1.0.1&platform=Hosted"
|
src="explorer.html?v=1.0.1&platform=Hosted"
|
||||||
></iframe>
|
></iframe>
|
||||||
|
))}
|
||||||
|
{!isLoggedIn && !encryptedTokenMetadata && (
|
||||||
|
<ConnectExplorer {...{ login, setEncryptedToken, setAuthType, connectionString, setConnectionString }} />
|
||||||
)}
|
)}
|
||||||
{!isLoggedIn && !encryptedTokenMetadata && <ConnectExplorer {...{ login, setEncryptedToken }} />}
|
|
||||||
{isLoggedIn && <DirectoryPickerPanel {...{ isOpen, dismissPanel, armToken, tenantId }} />}
|
{isLoggedIn && <DirectoryPickerPanel {...{ isOpen, dismissPanel, armToken, tenantId }} />}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
32
src/HostedExplorerChildFrame.ts
Normal file
32
src/HostedExplorerChildFrame.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { AuthType } from "./AuthType";
|
||||||
|
import { AccessInputMetadata, DatabaseAccount } from "./Contracts/DataModels";
|
||||||
|
|
||||||
|
export interface HostedExplorerChildFrame extends Window {
|
||||||
|
hostedConfig: AAD | ConnectionString | EncryptedToken | ResourceToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AAD {
|
||||||
|
authType: AuthType.AAD;
|
||||||
|
databaseAccount: DatabaseAccount;
|
||||||
|
authorizationToken: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ConnectionString {
|
||||||
|
authType: AuthType.ConnectionString;
|
||||||
|
// Connection string uses still use encrypted token for Cassandra/Mongo APIs as they us the portal backend proxy
|
||||||
|
encryptedToken: string;
|
||||||
|
encryptedTokenMetadata: AccessInputMetadata;
|
||||||
|
// Master key is currently only used by Graph API. All other APIs use encrypted tokens and proxy with connection string
|
||||||
|
masterKey?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EncryptedToken {
|
||||||
|
authType: AuthType.EncryptedToken;
|
||||||
|
encryptedToken: string;
|
||||||
|
encryptedTokenMetadata: AccessInputMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ResourceToken {
|
||||||
|
authType: AuthType.ResourceToken;
|
||||||
|
resourceToken: string;
|
||||||
|
}
|
||||||
61
src/Main.tsx
61
src/Main.tsx
@@ -75,22 +75,73 @@ import AuthHeadersUtil from "./Platform/Hosted/Authorization";
|
|||||||
import { CollectionCreation } from "./Shared/Constants";
|
import { CollectionCreation } from "./Shared/Constants";
|
||||||
import { extractFeatures } from "./Platform/Hosted/extractFeatures";
|
import { extractFeatures } from "./Platform/Hosted/extractFeatures";
|
||||||
import { emulatorAccount } from "./Platform/Emulator/emulatorAccount";
|
import { emulatorAccount } from "./Platform/Emulator/emulatorAccount";
|
||||||
|
import { HostedExplorerChildFrame } from "./HostedExplorerChildFrame";
|
||||||
|
import {
|
||||||
|
getDatabaseAccountKindFromExperience,
|
||||||
|
getDatabaseAccountPropertiesFromMetadata
|
||||||
|
} from "./Platform/Hosted/HostedUtils";
|
||||||
|
|
||||||
// TODO: Encapsulate and reuse all global variables as environment variables
|
// TODO: Encapsulate and reuse all global variables as environment variables
|
||||||
window.authType = AuthType.AAD;
|
window.authType = AuthType.AAD;
|
||||||
|
|
||||||
|
// const accountResourceId =
|
||||||
|
// authType === AuthType.EncryptedToken
|
||||||
|
// ? Main._databaseAccountId
|
||||||
|
// : authType === AuthType.AAD && account
|
||||||
|
// ? account.id
|
||||||
|
// : "";
|
||||||
|
// const subscriptionId: string =
|
||||||
|
// accountResourceId && accountResourceId.split("subscriptions/")[1].split("/")[0];
|
||||||
|
// const resourceGroup: string =
|
||||||
|
// accountResourceId && accountResourceId.split("resourceGroups/")[1].split("/")[0];
|
||||||
|
|
||||||
const App: React.FunctionComponent = () => {
|
const App: React.FunctionComponent = () => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
initializeConfiguration().then(config => {
|
initializeConfiguration().then(config => {
|
||||||
let explorer: Explorer;
|
let explorer: Explorer;
|
||||||
if (config.platform === Platform.Hosted) {
|
if (config.platform === Platform.Hosted) {
|
||||||
const authType: AuthType = window.authType;
|
const win = (window as unknown) as HostedExplorerChildFrame;
|
||||||
explorer = new Explorer();
|
explorer = new Explorer();
|
||||||
if (window.authType === AuthType.EncryptedToken) {
|
if (win.hostedConfig.authType === AuthType.EncryptedToken) {
|
||||||
|
// TODO: Remove window.authType
|
||||||
|
window.authType = AuthType.EncryptedToken;
|
||||||
|
// Impossible to tell if this is a try cosmos sub using an encrypted token
|
||||||
|
explorer.isTryCosmosDBSubscription(false);
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
accessToken: window.encryptedToken
|
accessToken: encodeURIComponent(win.hostedConfig.encryptedToken)
|
||||||
});
|
});
|
||||||
Hosted.initDataExplorerFrameInputs(explorer);
|
|
||||||
|
// const apiExperience: string = DefaultExperienceUtility.getDefaultExperienceFromApiKind(
|
||||||
|
// Main._accessInputMetadata.apiKind
|
||||||
|
// );
|
||||||
|
explorer.initDataExplorerWithFrameInputs({
|
||||||
|
databaseAccount: {
|
||||||
|
id: "",
|
||||||
|
// id: Main._databaseAccountId,
|
||||||
|
name: win.hostedConfig.encryptedTokenMetadata.accountName,
|
||||||
|
kind: "",
|
||||||
|
kind: getDatabaseAccountKindFromExperience(win.hostedConfig.encryptedTokenMetadata.apiKind),
|
||||||
|
properties: getDatabaseAccountPropertiesFromMetadata(win.hostedConfig.encryptedTokenMetadata),
|
||||||
|
tags: []
|
||||||
|
},
|
||||||
|
subscriptionId: undefined,
|
||||||
|
resourceGroup: undefined,
|
||||||
|
masterKey: undefined,
|
||||||
|
hasWriteAccess: true, // TODO: we should embed this information in the token ideally
|
||||||
|
authorizationToken: undefined,
|
||||||
|
features: extractFeatures(),
|
||||||
|
csmEndpoint: undefined,
|
||||||
|
dnsSuffix: undefined,
|
||||||
|
serverId: AuthHeadersUtil.serverId,
|
||||||
|
extensionEndpoint: configContext.BACKEND_ENDPOINT,
|
||||||
|
subscriptionType: CollectionCreation.DefaultSubscriptionType,
|
||||||
|
quotaId: undefined,
|
||||||
|
addCollectionDefaultFlight: explorer.flight(),
|
||||||
|
isTryCosmosDBSubscription: explorer.isTryCosmosDBSubscription()
|
||||||
|
});
|
||||||
|
explorer.isAccountReady(true);
|
||||||
|
} else if (window.authType === AuthType.ResourceToken) {
|
||||||
|
} else if (window.authType === AuthType.ConnectionString) {
|
||||||
} else if (window.authType === AuthType.AAD) {
|
} else if (window.authType === AuthType.AAD) {
|
||||||
const account = window.databaseAccount;
|
const account = window.databaseAccount;
|
||||||
const serverId = AuthHeadersUtil.serverId;
|
const serverId = AuthHeadersUtil.serverId;
|
||||||
@@ -118,7 +169,7 @@ const App: React.FunctionComponent = () => {
|
|||||||
}
|
}
|
||||||
} else if (config.platform === Platform.Emulator) {
|
} else if (config.platform === Platform.Emulator) {
|
||||||
window.authType = AuthType.MasterKey;
|
window.authType = AuthType.MasterKey;
|
||||||
const explorer = new Explorer();
|
explorer = new Explorer();
|
||||||
explorer.databaseAccount(emulatorAccount);
|
explorer.databaseAccount(emulatorAccount);
|
||||||
explorer.isAccountReady(true);
|
explorer.isAccountReady(true);
|
||||||
} else if (config.platform === Platform.Portal) {
|
} else if (config.platform === Platform.Portal) {
|
||||||
|
|||||||
@@ -3,15 +3,25 @@ import { useBoolean } from "@uifabric/react-hooks";
|
|||||||
import { HttpHeaders } from "../../../Common/Constants";
|
import { HttpHeaders } from "../../../Common/Constants";
|
||||||
import { GenerateTokenResponse } from "../../../Contracts/DataModels";
|
import { GenerateTokenResponse } from "../../../Contracts/DataModels";
|
||||||
import { configContext } from "../../../ConfigContext";
|
import { configContext } from "../../../ConfigContext";
|
||||||
|
import { AuthType } from "../../../AuthType";
|
||||||
|
import { isResourceTokenConnectionString } from "../Helpers/ResourceTokenUtils";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
connectionString: string;
|
||||||
login: () => void;
|
login: () => void;
|
||||||
setEncryptedToken: (token: string) => void;
|
setEncryptedToken: (token: string) => void;
|
||||||
|
setConnectionString: (connectionString: string) => void;
|
||||||
|
setAuthType: (authType: AuthType) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ConnectExplorer: React.FunctionComponent<Props> = ({ setEncryptedToken, login }: Props) => {
|
export const ConnectExplorer: React.FunctionComponent<Props> = ({
|
||||||
const [connectionString, setConnectionString] = React.useState<string>("");
|
setEncryptedToken,
|
||||||
const [isConnectionStringVisible, { setTrue: showConnectionString }] = useBoolean(false);
|
login,
|
||||||
|
setAuthType,
|
||||||
|
connectionString,
|
||||||
|
setConnectionString
|
||||||
|
}: Props) => {
|
||||||
|
const [isFormVisible, { setTrue: showForm }] = useBoolean(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="connectExplorer" className="connectExplorerContainer" style={{ display: "flex" }}>
|
<div id="connectExplorer" className="connectExplorerContainer" style={{ display: "flex" }}>
|
||||||
@@ -21,11 +31,17 @@ export const ConnectExplorer: React.FunctionComponent<Props> = ({ setEncryptedTo
|
|||||||
<img src="images/HdeConnectCosmosDB.svg" alt="Azure Cosmos DB" />
|
<img src="images/HdeConnectCosmosDB.svg" alt="Azure Cosmos DB" />
|
||||||
</p>
|
</p>
|
||||||
<p className="welcomeText">Welcome to Azure Cosmos DB</p>
|
<p className="welcomeText">Welcome to Azure Cosmos DB</p>
|
||||||
{isConnectionStringVisible ? (
|
{isFormVisible ? (
|
||||||
<form
|
<form
|
||||||
id="connectWithConnectionString"
|
id="connectWithConnectionString"
|
||||||
onSubmit={async event => {
|
onSubmit={async event => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (isResourceTokenConnectionString(connectionString)) {
|
||||||
|
setAuthType(AuthType.ResourceToken);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const headers = new Headers();
|
const headers = new Headers();
|
||||||
headers.append(HttpHeaders.connectionString, connectionString);
|
headers.append(HttpHeaders.connectionString, connectionString);
|
||||||
const url = configContext.BACKEND_ENDPOINT + "/api/guest/tokens/generateToken";
|
const url = configContext.BACKEND_ENDPOINT + "/api/guest/tokens/generateToken";
|
||||||
@@ -37,6 +53,7 @@ export const ConnectExplorer: React.FunctionComponent<Props> = ({ setEncryptedTo
|
|||||||
const result: GenerateTokenResponse = JSON.parse(await response.json());
|
const result: GenerateTokenResponse = JSON.parse(await response.json());
|
||||||
console.log(result.readWrite || result.read);
|
console.log(result.readWrite || result.read);
|
||||||
setEncryptedToken(decodeURIComponent(result.readWrite || result.read));
|
setEncryptedToken(decodeURIComponent(result.readWrite || result.read));
|
||||||
|
setAuthType(AuthType.ConnectionString);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<p className="connectExplorerContent connectStringText">Connect to your account with connection string</p>
|
<p className="connectExplorerContent connectStringText">Connect to your account with connection string</p>
|
||||||
@@ -66,7 +83,7 @@ export const ConnectExplorer: React.FunctionComponent<Props> = ({ setEncryptedTo
|
|||||||
) : (
|
) : (
|
||||||
<div id="connectWithAad">
|
<div id="connectWithAad">
|
||||||
<input className="filterbtnstyle" type="button" value="Sign In" onClick={login} />
|
<input className="filterbtnstyle" type="button" value="Sign In" onClick={login} />
|
||||||
<p className="switchConnectTypeText" onClick={showConnectionString}>
|
<p className="switchConnectTypeText" onClick={showForm}>
|
||||||
Connect to your account with connection string
|
Connect to your account with connection string
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import Main from "./Main";
|
import { isResourceTokenConnectionString, parseResourceTokenConnectionString } from "./ResourceTokenUtils";
|
||||||
|
|
||||||
describe("Main", () => {
|
describe("parseResourceTokenConnectionString", () => {
|
||||||
it("correctly parses resource token connection string", () => {
|
it("correctly parses resource token connection string", () => {
|
||||||
const connectionString =
|
const connectionString =
|
||||||
"AccountEndpoint=fakeEndpoint;DatabaseId=fakeDatabaseId;CollectionId=fakeCollectionId;type=resource&ver=1&sig=2dIP+CdIfT1ScwHWdv5GGw==;fakeToken;";
|
"AccountEndpoint=fakeEndpoint;DatabaseId=fakeDatabaseId;CollectionId=fakeCollectionId;type=resource&ver=1&sig=2dIP+CdIfT1ScwHWdv5GGw==;fakeToken;";
|
||||||
const properties = Main.parseResourceTokenConnectionString(connectionString);
|
const properties = parseResourceTokenConnectionString(connectionString);
|
||||||
|
|
||||||
expect(properties).toEqual({
|
expect(properties).toEqual({
|
||||||
accountEndpoint: "fakeEndpoint",
|
accountEndpoint: "fakeEndpoint",
|
||||||
@@ -18,7 +18,7 @@ describe("Main", () => {
|
|||||||
it("correctly parses resource token connection string with partition key", () => {
|
it("correctly parses resource token connection string with partition key", () => {
|
||||||
const connectionString =
|
const connectionString =
|
||||||
"type=resource&ver=1&sig=2dIP+CdIfT1ScwHWdv5GGw==;fakeToken;AccountEndpoint=fakeEndpoint;DatabaseId=fakeDatabaseId;CollectionId=fakeCollectionId;PartitionKey=fakePartitionKey;";
|
"type=resource&ver=1&sig=2dIP+CdIfT1ScwHWdv5GGw==;fakeToken;AccountEndpoint=fakeEndpoint;DatabaseId=fakeDatabaseId;CollectionId=fakeCollectionId;PartitionKey=fakePartitionKey;";
|
||||||
const properties = Main.parseResourceTokenConnectionString(connectionString);
|
const properties = parseResourceTokenConnectionString(connectionString);
|
||||||
|
|
||||||
expect(properties).toEqual({
|
expect(properties).toEqual({
|
||||||
accountEndpoint: "fakeEndpoint",
|
accountEndpoint: "fakeEndpoint",
|
||||||
@@ -29,3 +29,16 @@ describe("Main", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("isResourceToken", () => {
|
||||||
|
it("valid resource connection string", () => {
|
||||||
|
const connectionString =
|
||||||
|
"AccountEndpoint=fakeEndpoint;DatabaseId=fakeDatabaseId;CollectionId=fakeCollectionId;type=resource&ver=1&sig=2dIP+CdIfT1ScwHWdv5GGw==;fakeToken;";
|
||||||
|
expect(isResourceTokenConnectionString(connectionString)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("non-resource connection string", () => {
|
||||||
|
const connectionString = "AccountEndpoint=https://stfaul-sql.documents.azure.com:443/;AccountKey=foo;";
|
||||||
|
expect(isResourceTokenConnectionString(connectionString)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
43
src/Platform/Hosted/Helpers/ResourceTokenUtils.ts
Normal file
43
src/Platform/Hosted/Helpers/ResourceTokenUtils.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
export interface ParsedResourceTokenConnectionString {
|
||||||
|
accountEndpoint: string;
|
||||||
|
collectionId: string;
|
||||||
|
databaseId: string;
|
||||||
|
partitionKey?: string;
|
||||||
|
resourceToken: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseResourceTokenConnectionString(connectionString: string): ParsedResourceTokenConnectionString {
|
||||||
|
let accountEndpoint: string;
|
||||||
|
let collectionId: string;
|
||||||
|
let databaseId: string;
|
||||||
|
let partitionKey: string;
|
||||||
|
let resourceToken: string;
|
||||||
|
const connectionStringParts = connectionString.split(";");
|
||||||
|
connectionStringParts.forEach((part: string) => {
|
||||||
|
if (part.startsWith("type=resource")) {
|
||||||
|
resourceToken = part + ";";
|
||||||
|
} else if (part.startsWith("AccountEndpoint=")) {
|
||||||
|
accountEndpoint = part.substring(16);
|
||||||
|
} else if (part.startsWith("DatabaseId=")) {
|
||||||
|
databaseId = part.substring(11);
|
||||||
|
} else if (part.startsWith("CollectionId=")) {
|
||||||
|
collectionId = part.substring(13);
|
||||||
|
} else if (part.startsWith("PartitionKey=")) {
|
||||||
|
partitionKey = part.substring(13);
|
||||||
|
} else if (part !== "") {
|
||||||
|
resourceToken += part + ";";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
accountEndpoint,
|
||||||
|
collectionId,
|
||||||
|
databaseId,
|
||||||
|
partitionKey,
|
||||||
|
resourceToken
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isResourceTokenConnectionString(connectionString: string): boolean {
|
||||||
|
return connectionString && connectionString.includes("type=resource");
|
||||||
|
}
|
||||||
@@ -31,3 +31,15 @@ export function getDatabaseAccountPropertiesFromMetadata(metadata: AccessInputMe
|
|||||||
}
|
}
|
||||||
return properties;
|
return properties;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getDatabaseAccountKindFromExperience(apiExperience: string): string {
|
||||||
|
if (apiExperience === Constants.DefaultAccountExperience.MongoDB) {
|
||||||
|
return Constants.AccountKind.MongoDB;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (apiExperience === Constants.DefaultAccountExperience.ApiForMongoDB) {
|
||||||
|
return Constants.AccountKind.MongoDB;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Constants.AccountKind.GlobalDocumentDB;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import * as Constants from "../../Common/Constants";
|
import * as Constants from "../../Common/Constants";
|
||||||
import { configContext } from "../../ConfigContext";
|
import { configContext } from "../../ConfigContext";
|
||||||
import { ApiKind, DatabaseAccount, resourceTokenConnectionStringProperties } from "../../Contracts/DataModels";
|
import { ApiKind, DatabaseAccount } from "../../Contracts/DataModels";
|
||||||
import { DataExplorerInputsFrame } from "../../Contracts/ViewModels";
|
import { DataExplorerInputsFrame } from "../../Contracts/ViewModels";
|
||||||
import Explorer from "../../Explorer/Explorer";
|
import Explorer from "../../Explorer/Explorer";
|
||||||
import "../../Explorer/Tables/DataTable/DataTableBindingManager";
|
import "../../Explorer/Tables/DataTable/DataTableBindingManager";
|
||||||
@@ -12,38 +12,6 @@ import { extractFeatures } from "./extractFeatures";
|
|||||||
import { getDatabaseAccountPropertiesFromMetadata } from "./HostedUtils";
|
import { getDatabaseAccountPropertiesFromMetadata } from "./HostedUtils";
|
||||||
|
|
||||||
export default class Main {
|
export default class Main {
|
||||||
public static parseResourceTokenConnectionString(connectionString: string): resourceTokenConnectionStringProperties {
|
|
||||||
let accountEndpoint: string;
|
|
||||||
let collectionId: string;
|
|
||||||
let databaseId: string;
|
|
||||||
let partitionKey: string;
|
|
||||||
let resourceToken: string;
|
|
||||||
const connectionStringParts = connectionString.split(";");
|
|
||||||
connectionStringParts.forEach((part: string) => {
|
|
||||||
if (part.startsWith("type=resource")) {
|
|
||||||
resourceToken = part + ";";
|
|
||||||
} else if (part.startsWith("AccountEndpoint=")) {
|
|
||||||
accountEndpoint = part.substring(16);
|
|
||||||
} else if (part.startsWith("DatabaseId=")) {
|
|
||||||
databaseId = part.substring(11);
|
|
||||||
} else if (part.startsWith("CollectionId=")) {
|
|
||||||
collectionId = part.substring(13);
|
|
||||||
} else if (part.startsWith("PartitionKey=")) {
|
|
||||||
partitionKey = part.substring(13);
|
|
||||||
} else if (part !== "") {
|
|
||||||
resourceToken += part + ";";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
accountEndpoint,
|
|
||||||
collectionId,
|
|
||||||
databaseId,
|
|
||||||
partitionKey,
|
|
||||||
resourceToken
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static initDataExplorerFrameInputs(
|
public static initDataExplorerFrameInputs(
|
||||||
explorer: Explorer,
|
explorer: Explorer,
|
||||||
masterKey?: string /* master key extracted from connection string if available */,
|
masterKey?: string /* master key extracted from connection string if available */,
|
||||||
@@ -68,6 +36,8 @@ export default class Main {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (authType === AuthType.EncryptedToken) {
|
if (authType === AuthType.EncryptedToken) {
|
||||||
|
// TODO: Remove window.authType
|
||||||
|
window.authType = AuthType.EncryptedToken;
|
||||||
const apiExperience: string = DefaultExperienceUtility.getDefaultExperienceFromApiKind(
|
const apiExperience: string = DefaultExperienceUtility.getDefaultExperienceFromApiKind(
|
||||||
Main._accessInputMetadata.apiKind
|
Main._accessInputMetadata.apiKind
|
||||||
);
|
);
|
||||||
@@ -150,29 +120,8 @@ export default class Main {
|
|||||||
throw new Error(`Unsupported AuthType ${authType}`);
|
throw new Error(`Unsupported AuthType ${authType}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static _getDatabaseAccountKindFromExperience(apiExperience: string): string {
|
|
||||||
if (apiExperience === Constants.DefaultAccountExperience.MongoDB) {
|
|
||||||
return Constants.AccountKind.MongoDB;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (apiExperience === Constants.DefaultAccountExperience.ApiForMongoDB) {
|
|
||||||
return Constants.AccountKind.MongoDB;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Constants.AccountKind.GlobalDocumentDB;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static _getMasterKeyFromConnectionString(connectionString: string): string {
|
private static _getMasterKeyFromConnectionString(connectionString: string): string {
|
||||||
if (!connectionString || Main._accessInputMetadata == null || Main._accessInputMetadata.apiKind !== ApiKind.Graph) {
|
|
||||||
// client only needs master key for Graph API
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const matchedParts: string[] = connectionString.match("AccountKey=(.*);ApiKind=Gremlin;$");
|
const matchedParts: string[] = connectionString.match("AccountKey=(.*);ApiKind=Gremlin;$");
|
||||||
return (matchedParts.length > 1 && matchedParts[1]) || undefined;
|
return (matchedParts.length > 1 && matchedParts[1]) || undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static _isResourceToken(connectionString: string): boolean {
|
|
||||||
return connectionString && connectionString.includes("type=resource");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user