Skip to content

Commit

Permalink
Add missing multi-tenant support.
Browse files Browse the repository at this point in the history
Select the right Tenant when authenticating SDK calls and pass it along to VSCode when retrieving the token from the session.
  • Loading branch information
sevoku committed Nov 20, 2024
1 parent b1d47fb commit 8ae59be
Show file tree
Hide file tree
Showing 4 changed files with 22 additions and 8 deletions.
3 changes: 2 additions & 1 deletion src/docdb/getCosmosClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export type CosmosDBKeyCredential = {

export type CosmosDBAuthCredential = {
type: 'auth';
tenantId: string;
};

export type CosmosDBCredential = CosmosDBKeyCredential | CosmosDBAuthCredential;
Expand Down Expand Up @@ -106,7 +107,7 @@ export function getCosmosClient(
...commonProperties,
aadCredentials: {
getToken: async (scopes, _options) => {
const session = await getSessionFromVSCode(scopes, undefined, { createIfNone: true });
const session = await getSessionFromVSCode(scopes, authCred.tenantId, { createIfNone: true });
return {
token: session?.accessToken ?? '',
expiresOnTimestamp: 0,
Expand Down
10 changes: 8 additions & 2 deletions src/docdb/tree/DocDBAccountTreeItemBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,12 @@ import { deleteCosmosDBAccount } from '../../commands/deleteDatabaseAccount/dele
import { getThemeAgnosticIconPath, SERVERLESS_CAPABILITY_NAME } from '../../constants';
import { nonNullProp } from '../../utils/nonNull';
import { rejectOnTimeout } from '../../utils/timeout';
import { getCosmosClient, getCosmosKeyCredential, type CosmosDBCredential } from '../getCosmosClient';
import {
getCosmosAuthCredential,
getCosmosClient,
getCosmosKeyCredential,
type CosmosDBCredential,
} from '../getCosmosClient';
import { getSignedInPrincipalIdForAccountEndpoint } from '../utils/azureSessionHelper';
import { ensureRbacPermission, isRbacException, showRbacPermissionError } from '../utils/rbacUtils';
import { DocDBTreeItemBase } from './DocDBTreeItemBase';
Expand Down Expand Up @@ -131,8 +136,9 @@ export abstract class DocDBAccountTreeItemBase extends DocDBTreeItemBase<Databas
} catch (e) {
if (e instanceof Error && isRbacException(e) && !this.hasShownRbacNotification) {
this.hasShownRbacNotification = true;
const tenantId = getCosmosAuthCredential(this.root.credentials)?.tenantId;
const principalId =
(await getSignedInPrincipalIdForAccountEndpoint(this.root.endpoint)) ?? '';
(await getSignedInPrincipalIdForAccountEndpoint(this.root.endpoint, tenantId)) ?? '';
// chedck if the principal ID matches the one that is signed in, otherwise this might be a security problem, hence show the error message
if (
e.message.includes(`[${principalId}]`) &&
Expand Down
14 changes: 10 additions & 4 deletions src/docdb/utils/azureSessionHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,20 @@
import { getSessionFromVSCode } from '@microsoft/vscode-azext-azureauth/out/src/getSessionFromVSCode';
import type * as vscode from 'vscode';

export async function getSignedInPrincipalIdForAccountEndpoint(accountEndpoint: string): Promise<string | undefined> {
const session = await getSessionForDatabaseAccount(accountEndpoint);
export async function getSignedInPrincipalIdForAccountEndpoint(
accountEndpoint: string,
tenantId: string | undefined,
): Promise<string | undefined> {
const session = await getSessionForDatabaseAccount(accountEndpoint, tenantId);
const principalId = session?.account.id.split('/')[1] ?? session?.account.id;
return principalId;
}

async function getSessionForDatabaseAccount(endpoint: string): Promise<vscode.AuthenticationSession | undefined> {
async function getSessionForDatabaseAccount(
endpoint: string,
tenantId: string | undefined,
): Promise<vscode.AuthenticationSession | undefined> {
const endpointUrl = new URL(endpoint);
const scrope = `${endpointUrl.origin}${endpointUrl.pathname}.default`;
return await getSessionFromVSCode(scrope, undefined, { createIfNone: false });
return await getSessionFromVSCode(scrope, tenantId, { createIfNone: false });
}
3 changes: 2 additions & 1 deletion src/tree/SubscriptionTreeItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,8 +252,9 @@ export class SubscriptionTreeItem extends SubscriptionTreeItemBase {
}
}

const tenantId = parent.subscription.tenantId ?? databaseAccount.identity?.tenantId;
// OAuth is always enabled for Cosmos DB and will be used as a fall back if key auth is unavailable
const authCred = { type: 'auth' };
const authCred = { type: 'auth', tenantId: tenantId };
const credentials = [keyCred, authCred].filter(
(cred): cred is CosmosDBCredential => cred !== undefined,
);
Expand Down

0 comments on commit 8ae59be

Please sign in to comment.