From dcd8d1637bb58daf7ac6f2b5c90e7ee907770aae Mon Sep 17 00:00:00 2001 From: Laurent Nguyen Date: Tue, 10 Oct 2023 19:25:58 +0000 Subject: [PATCH] Implement retrieval of authorization token for Fabric via iframe rpc (#1647) * For Fabric, send message to get Authorization token from iframe parent * tokenProvider: set date header and return token * Expect account endpoint on initialize message from Fabric * Fix format --------- Co-authored-by: artrejo --- src/Common/CosmosClient.ts | 26 ++++++++++++---- src/Common/MessageHandler.ts | 2 +- src/Contracts/ExplorerContracts.ts | 44 ++------------------------- src/Contracts/FabricContract.ts | 37 +++++++++++++++++++++-- src/Contracts/MessageTypes.ts | 48 ++++++++++++++++++++++++++++++ src/hooks/useKnockoutExplorer.ts | 45 +++++++++++++++------------- 6 files changed, 131 insertions(+), 71 deletions(-) create mode 100644 src/Contracts/MessageTypes.ts diff --git a/src/Common/CosmosClient.ts b/src/Common/CosmosClient.ts index 84d80b0e2..bd4b76d0d 100644 --- a/src/Common/CosmosClient.ts +++ b/src/Common/CosmosClient.ts @@ -1,13 +1,14 @@ import * as Cosmos from "@azure/cosmos"; -import { configContext, Platform } from "../ConfigContext"; +import { sendCachedDataMessage } from "Common/MessageHandler"; +import { AuthorizationToken, MessageTypes } from "Contracts/MessageTypes"; +import { AuthType } from "../AuthType"; +import { PriorityLevel } from "../Common/Constants"; +import { Platform, configContext } from "../ConfigContext"; import { userContext } from "../UserContext"; import { logConsoleError } from "../Utils/NotificationConsoleUtils"; +import * as PriorityBasedExecutionUtils from "../Utils/PriorityBasedExecutionUtils"; import { EmulatorMasterKey, HttpHeaders } from "./Constants"; import { getErrorMessage } from "./ErrorHandlingUtils"; -import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility"; -import { PriorityLevel } from "../Common/Constants"; -import * as PriorityBasedExecutionUtils from "../Utils/PriorityBasedExecutionUtils"; -import { AuthType } from "../AuthType"; const _global = typeof self === "undefined" ? window : self; @@ -26,6 +27,15 @@ export const tokenProvider = async (requestInfo: Cosmos.RequestInfo) => { return decodeURIComponent(headers.authorization); } + if (configContext.platform === Platform.Fabric) { + const authorizationToken = await sendCachedDataMessage(MessageTypes.GetAuthorizationToken, [ + requestInfo, + ]); + console.log("Response from Fabric: ", authorizationToken); + headers[HttpHeaders.msDate] = authorizationToken.XDate; + return authorizationToken.PrimaryReadWriteToken; + } + if (userContext.masterKey) { // TODO This SDK method mutates the headers object. Find a better one or fix the SDK. await Cosmos.setAuthorizationTokenHeaderUsingMasterKey(verb, resourceId, resourceType, headers, EmulatorMasterKey); @@ -56,7 +66,11 @@ export const endpoint = () => { return userContext.endpoint || userContext?.databaseAccount?.properties?.documentEndpoint; }; -export async function getTokenFromAuthService(verb: string, resourceType: string, resourceId?: string): Promise { +export async function getTokenFromAuthService( + verb: string, + resourceType: string, + resourceId?: string, +): Promise { try { const host = configContext.BACKEND_ENDPOINT; const response = await _global.fetch(host + "/api/guest/runtimeproxy/authorizationTokens", { diff --git a/src/Common/MessageHandler.ts b/src/Common/MessageHandler.ts index 834e2d7b5..e32b31d6c 100644 --- a/src/Common/MessageHandler.ts +++ b/src/Common/MessageHandler.ts @@ -22,7 +22,7 @@ export function handleCachedDataMessage(message: any): void { if (messageContent.error != null) { cachedDataPromise.deferred.reject(messageContent.error); } else { - cachedDataPromise.deferred.resolve(JSON.parse(messageContent.data)); + cachedDataPromise.deferred.resolve(messageContent.data); } runGarbageCollector(); } diff --git a/src/Contracts/ExplorerContracts.ts b/src/Contracts/ExplorerContracts.ts index fa286f1fc..42e5d3dc0 100644 --- a/src/Contracts/ExplorerContracts.ts +++ b/src/Contracts/ExplorerContracts.ts @@ -1,46 +1,6 @@ +import { MessageTypes } from "Contracts/MessageTypes"; import * as ActionContracts from "./ActionContracts"; import * as Diagnostics from "./Diagnostics"; import * as Versions from "./Versions"; -/** - * Messaging types used with Data Explorer <-> Portal communication - * and Hosted <-> Explorer communication - */ -export enum MessageTypes { - TelemetryInfo, - LogInfo, - RefreshResources, - AllDatabases, - CollectionsForDatabase, - RefreshOffers, - AllOffers, - UpdateLocationHash, - SingleOffer, - RefreshOffer, - UpdateAccountName, - ForbiddenError, - AadSignIn, - GetAccessAadRequest, - GetAccessAadResponse, - UpdateAccountSwitch, - UpdateDirectoryControl, - SwitchAccount, - SendNotification, - ClearNotification, - ExplorerClickEvent, - LoadingStatus, - GetArcadiaToken, - CreateWorkspace, - CreateSparkPool, - RefreshDatabaseAccount, - CloseTab, - OpenQuickstartBlade, - OpenPostgreSQLPasswordReset, - OpenPostgresNetworkingBlade, - OpenCosmosDBNetworkingBlade, - DisplayNPSSurvey, - OpenVCoreMongoNetworkingBlade, - OpenVCoreMongoConnectionStringsBlade, -} - -export { ActionContracts, Diagnostics, Versions }; +export { ActionContracts, Diagnostics, MessageTypes, Versions }; diff --git a/src/Contracts/FabricContract.ts b/src/Contracts/FabricContract.ts index 61f5c0bdb..14dca076a 100644 --- a/src/Contracts/FabricContract.ts +++ b/src/Contracts/FabricContract.ts @@ -1,3 +1,5 @@ +import { AuthorizationToken, MessageTypes } from "./MessageTypes"; + export type FabricMessage = | { type: "newContainer"; @@ -5,21 +7,52 @@ export type FabricMessage = } | { type: "initialize"; - connectionString: string | undefined; + message: { + endpoint: string | undefined; + error: string | undefined; + }; } | { type: "openTab"; databaseName: string; collectionName: string | undefined; + } + | { + type: "authorizationToken"; + message: { + id: string; + error: string | undefined; + data: AuthorizationToken | undefined; + }; }; export type DataExploreMessage = | "ready" | { - type: number; + type: MessageTypes.TelemetryInfo; data: { action: "LoadDatabases"; actionModifier: "success" | "start"; defaultExperience: "SQL"; }; + } + | { + type: MessageTypes.GetAuthorizationToken; + id: string; + params: GetCosmosTokenMessageOptions[]; }; + +export type GetCosmosTokenMessageOptions = { + verb: "connect" | "delete" | "get" | "head" | "options" | "patch" | "post" | "put" | "trace"; + resourceType: "" | "dbs" | "colls" | "docs" | "sprocs" | "pkranges"; + resourceId: string; +}; + +export type CosmosDBTokenResponse = { + token: string; + date: string; +}; + +export type CosmosDBConnectionInfoResponse = { + endpoint: string; +}; diff --git a/src/Contracts/MessageTypes.ts b/src/Contracts/MessageTypes.ts new file mode 100644 index 000000000..7bd129819 --- /dev/null +++ b/src/Contracts/MessageTypes.ts @@ -0,0 +1,48 @@ +/** + * Messaging types used with Data Explorer <-> Portal communication, + * Hosted <-> Explorer communication and Data Explorer -> Fabric communication. + */ +export enum MessageTypes { + TelemetryInfo, + LogInfo, + RefreshResources, + AllDatabases, + CollectionsForDatabase, + RefreshOffers, + AllOffers, + UpdateLocationHash, + SingleOffer, + RefreshOffer, + UpdateAccountName, + ForbiddenError, + AadSignIn, + GetAccessAadRequest, + GetAccessAadResponse, + UpdateAccountSwitch, + UpdateDirectoryControl, + SwitchAccount, + SendNotification, + ClearNotification, + ExplorerClickEvent, + LoadingStatus, + GetArcadiaToken, + CreateWorkspace, + CreateSparkPool, + RefreshDatabaseAccount, + CloseTab, + OpenQuickstartBlade, + OpenPostgreSQLPasswordReset, + OpenPostgresNetworkingBlade, + OpenCosmosDBNetworkingBlade, + DisplayNPSSurvey, + OpenVCoreMongoNetworkingBlade, + OpenVCoreMongoConnectionStringsBlade, + + // Data Explorer -> Fabric communication + GetAuthorizationToken, +} + +export interface AuthorizationToken { + XDate: string; + PrimaryReadWriteToken: string; +} diff --git a/src/hooks/useKnockoutExplorer.ts b/src/hooks/useKnockoutExplorer.ts index 16d044105..f2c8c2abe 100644 --- a/src/hooks/useKnockoutExplorer.ts +++ b/src/hooks/useKnockoutExplorer.ts @@ -2,15 +2,13 @@ import { createUri } from "Common/UrlUtility"; import { FabricMessage } from "Contracts/FabricContract"; import Explorer from "Explorer/Explorer"; import { useSelectedNode } from "Explorer/useSelectedNode"; -import { fetchEncryptedToken } from "Platform/Hosted/Components/ConnectExplorer"; import { getNetworkSettingsWarningMessage } from "Utils/NetworkUtility"; -import { fetchAccessData } from "hooks/usePortalAccessToken"; import { ReactTabKind, useTabs } from "hooks/useTabs"; import { useEffect, useState } from "react"; import { AuthType } from "../AuthType"; import { AccountKind, Flights } from "../Common/Constants"; import { normalizeArmEndpoint } from "../Common/EnvironmentUtility"; -import { sendMessage, sendReadyMessage } from "../Common/MessageHandler"; +import { handleCachedDataMessage, sendMessage, sendReadyMessage } from "../Common/MessageHandler"; import { Platform, configContext, updateConfigContext } from "../ConfigContext"; import { ActionType, DataExplorerAction, TabKind } from "../Contracts/ActionContracts"; import { MessageTypes } from "../Contracts/ExplorerContracts"; @@ -107,23 +105,7 @@ async function configureFabric(): Promise { switch (data.type) { case "initialize": { - // TODO For now, retrieve info from session storage. Replace with info injected into Data Explorer - const connectionString = data.connectionString ?? sessionStorage.getItem("connectionString"); - if (!connectionString) { - console.error("No connection string found in session storage"); - return undefined; - } - const encryptedToken = await fetchEncryptedToken(connectionString); - // TODO Duplicated from useTokenMetadata - const encryptedTokenMetadata = await fetchAccessData(encryptedToken); - - const hostedConfig: EncryptedToken = { - authType: AuthType.EncryptedToken, - encryptedToken, - encryptedTokenMetadata, - }; - - explorer = await configureWithEncryptedToken(hostedConfig); + explorer = await configureWithFabric(data.message.endpoint); resolve(explorer); break; } @@ -166,6 +148,10 @@ async function configureFabric(): Promise { break; } + case "authorizationToken": { + handleCachedDataMessage(data); + break; + } default: console.error(`Unknown Fabric message type: ${JSON.stringify(data)}`); break; @@ -315,6 +301,25 @@ function configureHostedWithResourceToken(config: ResourceToken): Explorer { return explorer; } +function configureWithFabric(documentEndpoint: string): Explorer { + updateUserContext({ + authType: AuthType.ConnectionString, + databaseAccount: { + id: "", + location: "", + type: "", + name: "Mounted", + kind: AccountKind.Default, + properties: { + documentEndpoint, + }, + }, + }); + const explorer = new Explorer(); + setTimeout(() => explorer.refreshAllDatabases(), 0); + return explorer; +} + function configureWithEncryptedToken(config: EncryptedToken): Explorer { const apiExperience = DefaultExperienceUtility.getDefaultExperienceFromApiKind(config.encryptedTokenMetadata.apiKind); updateUserContext({