[Hosted] AAD implementation for item operations (#643)

This commit is contained in:
Zachary Foster 2021-05-18 18:59:09 -04:00 committed by GitHub
parent 48eeb8419d
commit 2bc298fef1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 42 additions and 22 deletions

View File

@ -10,6 +10,13 @@ const _global = typeof self === "undefined" ? window : self;
export const tokenProvider = async (requestInfo: RequestInfo) => { export const tokenProvider = async (requestInfo: RequestInfo) => {
const { verb, resourceId, resourceType, headers } = requestInfo; const { verb, resourceId, resourceType, headers } = requestInfo;
if (userContext.features.enableAadDataPlane && userContext.aadToken) {
const AUTH_PREFIX = `type=aad&ver=1.0&sig=`;
const authorizationToken = `${AUTH_PREFIX}${userContext.aadToken}`;
return authorizationToken;
}
if (configContext.platform === Platform.Emulator) { if (configContext.platform === Platform.Emulator) {
// TODO This SDK method mutates the headers object. Find a better one or fix the SDK. // TODO This SDK method mutates the headers object. Find a better one or fix the SDK.
await setAuthorizationTokenHeaderUsingMasterKey(verb, resourceId, resourceType, headers, EmulatorMasterKey); await setAuthorizationTokenHeaderUsingMasterKey(verb, resourceId, resourceType, headers, EmulatorMasterKey);
@ -76,7 +83,7 @@ export function client(): Cosmos.CosmosClient {
if (_client) return _client; if (_client) return _client;
const options: Cosmos.CosmosClientOptions = { const options: Cosmos.CosmosClientOptions = {
endpoint: endpoint() || "https://cosmos.azure.com", // CosmosClient gets upset if we pass a bad URL. This should never actually get called endpoint: endpoint() || "https://cosmos.azure.com", // CosmosClient gets upset if we pass a bad URL. This should never actually get called
key: userContext.masterKey, ...(!userContext.features.enableAadDataPlane && { key: userContext.masterKey }),
tokenProvider, tokenProvider,
connectionPolicy: { connectionPolicy: {
enableEndpointDiscovery: false, enableEndpointDiscovery: false,

View File

@ -1,8 +1,8 @@
import { CollectionBase } from "../../Contracts/ViewModels"; import { CollectionBase } from "../../Contracts/ViewModels";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { getEntityName } from "../DocumentUtility"; import { getEntityName } from "../DocumentUtility";
import { handleError } from "../ErrorHandlingUtils"; import { handleError } from "../ErrorHandlingUtils";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
export const createDocument = async (collection: CollectionBase, newDocument: unknown): Promise<unknown> => { export const createDocument = async (collection: CollectionBase, newDocument: unknown): Promise<unknown> => {
const entityName = getEntityName(); const entityName = getEntityName();

View File

@ -1,6 +1,6 @@
import { Queries } from "../Constants";
import { FeedOptions, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos"; import { FeedOptions, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
import { LocalStorageUtility, StorageKey } from "../../Shared/StorageUtility"; import { LocalStorageUtility, StorageKey } from "../../Shared/StorageUtility";
import { Queries } from "../Constants";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
export const queryDocuments = ( export const queryDocuments = (

View File

@ -1,8 +1,8 @@
import { QueryResults } from "../../Contracts/ViewModels"; import { QueryResults } from "../../Contracts/ViewModels";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { MinimalQueryIterator, nextPage } from "../IteratorUtilities";
import { handleError } from "../ErrorHandlingUtils";
import { getEntityName } from "../DocumentUtility"; import { getEntityName } from "../DocumentUtility";
import { handleError } from "../ErrorHandlingUtils";
import { MinimalQueryIterator, nextPage } from "../IteratorUtilities";
export const queryDocumentsPage = async ( export const queryDocumentsPage = async (
resourceName: string, resourceName: string,

View File

@ -1,6 +1,6 @@
import * as Versions from "./Versions";
import * as ActionContracts from "./ActionContracts"; import * as ActionContracts from "./ActionContracts";
import * as Diagnostics from "./Diagnostics"; import * as Diagnostics from "./Diagnostics";
import * as Versions from "./Versions";
/** /**
* Messaging types used with Data Explorer <-> Portal communication * Messaging types used with Data Explorer <-> Portal communication

View File

@ -5,20 +5,20 @@ import { render } from "react-dom";
import ChevronRight from "../images/chevron-right.svg"; import ChevronRight from "../images/chevron-right.svg";
import "../less/hostedexplorer.less"; import "../less/hostedexplorer.less";
import { AuthType } from "./AuthType"; import { AuthType } from "./AuthType";
import { ConnectExplorer } from "./Platform/Hosted/Components/ConnectExplorer";
import { DatabaseAccount } from "./Contracts/DataModels"; import { DatabaseAccount } from "./Contracts/DataModels";
import { DirectoryPickerPanel } from "./Platform/Hosted/Components/DirectoryPickerPanel";
import { AccountSwitcher } from "./Platform/Hosted/Components/AccountSwitcher";
import "./Explorer/Menus/NavBar/MeControlComponent.less"; import "./Explorer/Menus/NavBar/MeControlComponent.less";
import { useTokenMetadata } from "./hooks/usePortalAccessToken";
import { MeControl } from "./Platform/Hosted/Components/MeControl";
import "./Platform/Hosted/ConnectScreen.less";
import "./Shared/appInsights";
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 { useTokenMetadata } from "./hooks/usePortalAccessToken";
import { HostedExplorerChildFrame } from "./HostedExplorerChildFrame"; import { HostedExplorerChildFrame } from "./HostedExplorerChildFrame";
import { AccountSwitcher } from "./Platform/Hosted/Components/AccountSwitcher";
import { ConnectExplorer } from "./Platform/Hosted/Components/ConnectExplorer";
import { DirectoryPickerPanel } from "./Platform/Hosted/Components/DirectoryPickerPanel";
import { FeedbackCommandButton } from "./Platform/Hosted/Components/FeedbackCommandButton";
import { MeControl } from "./Platform/Hosted/Components/MeControl";
import { SignInButton } from "./Platform/Hosted/Components/SignInButton";
import "./Platform/Hosted/ConnectScreen.less";
import { extractMasterKeyfromConnectionString } from "./Platform/Hosted/HostedUtils"; import { extractMasterKeyfromConnectionString } from "./Platform/Hosted/HostedUtils";
import "./Shared/appInsights";
initializeIcons(); initializeIcons();
@ -31,7 +31,7 @@ const App: React.FunctionComponent = () => {
// For showing/hiding panel // For showing/hiding panel
const [isOpen, { setTrue: openPanel, setFalse: dismissPanel }] = useBoolean(false); const [isOpen, { setTrue: openPanel, setFalse: dismissPanel }] = useBoolean(false);
const { isLoggedIn, armToken, graphToken, account, tenantId, logout, login, switchTenant } = useAADAuth(); const { isLoggedIn, armToken, graphToken, aadToken, account, tenantId, logout, login, switchTenant } = useAADAuth();
const [databaseAccount, setDatabaseAccount] = React.useState<DatabaseAccount>(); const [databaseAccount, setDatabaseAccount] = React.useState<DatabaseAccount>();
const [authType, setAuthType] = React.useState<AuthType>(encryptedToken ? AuthType.EncryptedToken : undefined); const [authType, setAuthType] = React.useState<AuthType>(encryptedToken ? AuthType.EncryptedToken : undefined);
const [connectionString, setConnectionString] = React.useState<string>(); const [connectionString, setConnectionString] = React.useState<string>();
@ -50,6 +50,7 @@ const App: React.FunctionComponent = () => {
authType: AuthType.AAD, authType: AuthType.AAD,
databaseAccount, databaseAccount,
authorizationToken: armToken, authorizationToken: armToken,
aadToken,
}; };
} else if (authType === AuthType.EncryptedToken) { } else if (authType === AuthType.EncryptedToken) {
frameWindow.hostedConfig = { frameWindow.hostedConfig = {

View File

@ -7,6 +7,7 @@ export interface HostedExplorerChildFrame extends Window {
} }
export interface AAD { export interface AAD {
aadToken: string;
authType: AuthType.AAD; authType: AuthType.AAD;
databaseAccount: DatabaseAccount; databaseAccount: DatabaseAccount;
authorizationToken: string; authorizationToken: string;

View File

@ -13,6 +13,7 @@ export type Features = {
readonly enableSpark: boolean; readonly enableSpark: boolean;
readonly enableTtl: boolean; readonly enableTtl: boolean;
readonly executeSproc: boolean; readonly executeSproc: boolean;
readonly enableAadDataPlane: boolean;
readonly hostedDataExplorer: boolean; readonly hostedDataExplorer: boolean;
readonly junoEndpoint?: string; readonly junoEndpoint?: string;
readonly livyEndpoint?: string; readonly livyEndpoint?: string;
@ -43,6 +44,7 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear
return { return {
canExceedMaximumValue: "true" === get("canexceedmaximumvalue"), canExceedMaximumValue: "true" === get("canexceedmaximumvalue"),
cosmosdb: "true" === get("cosmosdb"), cosmosdb: "true" === get("cosmosdb"),
enableAadDataPlane: "true" === get("enableaaddataplane"),
enableChangeFeedPolicy: "true" === get("enablechangefeedpolicy"), enableChangeFeedPolicy: "true" === get("enablechangefeedpolicy"),
enableFixedCollectionWithSharedThroughput: "true" === get("enablefixedcollectionwithsharedthroughput"), enableFixedCollectionWithSharedThroughput: "true" === get("enablefixedcollectionwithsharedthroughput"),
enableKOPanel: "true" === get("enablekopanel"), enableKOPanel: "true" === get("enablekopanel"),

View File

@ -1,11 +1,11 @@
import "@jupyterlab/terminal/style/index.css";
import "./index.css";
import { ServerConnection } from "@jupyterlab/services"; import { ServerConnection } from "@jupyterlab/services";
import { JupyterLabAppFactory } from "./JupyterLabAppFactory"; import "@jupyterlab/terminal/style/index.css";
import { HttpHeaders, TerminalQueryParams } from "../Common/Constants";
import { Action } from "../Shared/Telemetry/TelemetryConstants"; import { Action } from "../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
import { updateUserContext } from "../UserContext"; import { updateUserContext } from "../UserContext";
import { HttpHeaders, TerminalQueryParams } from "../Common/Constants"; import "./index.css";
import { JupyterLabAppFactory } from "./JupyterLabAppFactory";
const getUrlVars = (): { [key: string]: string } => { const getUrlVars = (): { [key: string]: string } => {
const vars: { [key: string]: string } = {}; const vars: { [key: string]: string } = {};
@ -50,7 +50,7 @@ const createServerSettings = (urlVars: { [key: string]: string }): ServerConnect
const main = async (): Promise<void> => { const main = async (): Promise<void> => {
const urlVars = getUrlVars(); const urlVars = getUrlVars();
// Initialize userContext. Currently only subscrptionId is required by TelemetryProcessor // Initialize userContext. Currently only subscriptionId is required by TelemetryProcessor
updateUserContext({ updateUserContext({
subscriptionId: urlVars[TerminalQueryParams.SubscriptionId], subscriptionId: urlVars[TerminalQueryParams.SubscriptionId],
}); });

View File

@ -11,6 +11,7 @@ interface UserContext {
readonly resourceGroup?: string; readonly resourceGroup?: string;
readonly databaseAccount?: DatabaseAccount; readonly databaseAccount?: DatabaseAccount;
readonly endpoint?: string; readonly endpoint?: string;
readonly aadToken?: string;
readonly accessToken?: string; readonly accessToken?: string;
readonly authorizationToken?: string; readonly authorizationToken?: string;
readonly resourceToken?: string; readonly resourceToken?: string;

View File

@ -25,6 +25,7 @@ interface ReturnType {
isLoggedIn: boolean; isLoggedIn: boolean;
graphToken: string; graphToken: string;
armToken: string; armToken: string;
aadToken: string;
login: () => void; login: () => void;
logout: () => void; logout: () => void;
tenantId: string; tenantId: string;
@ -40,6 +41,7 @@ export function useAADAuth(): ReturnType {
const [tenantId, setTenantId] = React.useState<string>(cachedTenantId); const [tenantId, setTenantId] = React.useState<string>(cachedTenantId);
const [graphToken, setGraphToken] = React.useState<string>(); const [graphToken, setGraphToken] = React.useState<string>();
const [armToken, setArmToken] = React.useState<string>(); const [armToken, setArmToken] = React.useState<string>();
const [aadToken, setAadToken] = React.useState<string>();
msalInstance.setActiveAccount(account); msalInstance.setActiveAccount(account);
const login = React.useCallback(async () => { const login = React.useCallback(async () => {
@ -79,9 +81,13 @@ export function useAADAuth(): ReturnType {
authority: `https://login.microsoftonline.com/${tenantId}`, authority: `https://login.microsoftonline.com/${tenantId}`,
scopes: ["https://management.azure.com//.default"], scopes: ["https://management.azure.com//.default"],
}), }),
]).then(([graphTokenResponse, armTokenResponse]) => { msalInstance.acquireTokenSilent({
scopes: ["https://cosmos.azure.com/.default"],
}),
]).then(([graphTokenResponse, armTokenResponse, aadTokenResponse]) => {
setGraphToken(graphTokenResponse.accessToken); setGraphToken(graphTokenResponse.accessToken);
setArmToken(armTokenResponse.accessToken); setArmToken(armTokenResponse.accessToken);
setAadToken(aadTokenResponse.accessToken);
}); });
} }
}, [account, tenantId]); }, [account, tenantId]);
@ -92,6 +98,7 @@ export function useAADAuth(): ReturnType {
isLoggedIn, isLoggedIn,
graphToken, graphToken,
armToken, armToken,
aadToken,
login, login,
logout, logout,
switchTenant, switchTenant,

View File

@ -83,6 +83,7 @@ async function configureHostedWithAAD(config: AAD, explorerParams: ExplorerParam
updateUserContext({ updateUserContext({
authType: AuthType.AAD, authType: AuthType.AAD,
authorizationToken: `Bearer ${config.authorizationToken}`, authorizationToken: `Bearer ${config.authorizationToken}`,
aadToken: config.aadToken,
}); });
const account = config.databaseAccount; const account = config.databaseAccount;
const accountResourceId = account.id; const accountResourceId = account.id;