Skip to content

Commit

Permalink
add options for getting session, and don't require active token for s…
Browse files Browse the repository at this point in the history
…elected tenant
  • Loading branch information
peterbom committed May 28, 2024
1 parent c583894 commit 4853c8d
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 11 deletions.
38 changes: 28 additions & 10 deletions src/auth/azureSessionProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
AuthenticationGetSessionOptions,
AuthenticationSession,
} from "vscode";
import { AzureAuthenticationSession, AzureSessionProvider, SignInStatus, Tenant } from "./types";
import { AzureAuthenticationSession, AzureSessionProvider, GetAuthSessionOptions, SignInStatus, Tenant } from "./types";
import { Errorable, bindAsync, getErrorMessage, map as errmap, succeeded } from "../commands/utils/errorable";
import { getDefaultScope, quickPickTenant } from "./azureAuth";
import { getConfiguredAzureEnv } from "../commands/utils/config";
Expand Down Expand Up @@ -119,7 +119,9 @@ class AzureSessionProviderImpl extends VsCodeDisposable implements AzureSessionP
// This allows the user to sign in to the Microsoft provider and list tenants,
// but the resulting session will not allow tenant-level operations. For that,
// we need to get a session for a specific tenant.
const getSessionResult = await this.getArmSession("organizations", authScenario);
const orgTenantId = "organizations";
const scopes = getScopes(orgTenantId, {});
const getSessionResult = await this.getArmSession(orgTenantId, scopes, authScenario);

// Get the tenants
const getTenantsResult = await bindAsync(getSessionResult, (session) => getTenants(session));
Expand Down Expand Up @@ -149,7 +151,7 @@ class AzureSessionProviderImpl extends VsCodeDisposable implements AzureSessionP
* @returns The current Azure session, if available. If the user is not signed in, or there are no tenants,
* an error message is returned.
*/
public async getAuthSession(): Promise<Errorable<AzureAuthenticationSession>> {
public async getAuthSession(options?: GetAuthSessionOptions): Promise<Errorable<AzureAuthenticationSession>> {
await this.initializePromise;
if (this.signInStatusValue !== "SignedIn") {
return { succeeded: false, error: `Not signed in (${this.signInStatusValue}).` };
Expand All @@ -173,7 +175,9 @@ class AzureSessionProviderImpl extends VsCodeDisposable implements AzureSessionP
}

// Get a session for a specific tenant.
return await this.getArmSession(this.selectedTenantValue.id, AuthScenario.GetSession);
const tenantId = this.selectedTenantValue.id;
const scopes = getScopes(tenantId, options || {});
return await this.getArmSession(tenantId, scopes, AuthScenario.GetSession);
}

private async getNewSelectedTenant(
Expand Down Expand Up @@ -202,22 +206,29 @@ class AzureSessionProviderImpl extends VsCodeDisposable implements AzureSessionP
}

private async getDefaultTenantId(tenants: Tenant[]): Promise<Tenant | null> {
if (tenants.length === 1) {
return tenants[0];
}

// It may be the case that the user has access to multiple tenants, but only has a valid token for one of them.
// This might happen if the user has signed in to one recently, but not the others. In this case, we would want
// to default to the tenant that the user has a valid token for.
// Use the 'Initialization' scenario to ensure this is silent (no user interaction).
const getSessionPromises = tenants.map((t) => this.getArmSession(t.id, AuthScenario.Initialization));
const getSessionPromises = tenants.map((t) =>
this.getArmSession(t.id, getScopes(t.id, {}), AuthScenario.Initialization),
);
const results = await Promise.all(getSessionPromises);
const accessibleTenants = results.filter(succeeded).map((r) => r.result);
return accessibleTenants.length === 1 ? findTenant(tenants, accessibleTenants[0].tenantId) : null;
}

private async getArmSession(
tenantId: string,
scopes: string[],
authScenario: AuthScenario,
): Promise<Errorable<AzureAuthenticationSession>> {
this.handleSessionChanges = false;
try {
const tenantScopes = tenantId ? [`VSCODE_TENANT:${tenantId}`] : [];
const scopes = [getDefaultScope(getConfiguredAzureEnv().resourceManagerEndpointUrl), ...tenantScopes];

let options: AuthenticationGetSessionOptions;
let silentFirst = false;
switch (authScenario) {
Expand Down Expand Up @@ -263,6 +274,13 @@ function getConfiguredAuthProviderId(): AuthProviderId {
return getConfiguredAzureEnv().name === Environment.AzureCloud.name ? "microsoft" : "microsoft-sovereign-cloud";
}

function getScopes(tenantId: string | null, options: GetAuthSessionOptions): string[] {
const defaultScopes = options.scopes || [getDefaultScope(getConfiguredAzureEnv().resourceManagerEndpointUrl)];
const tenantScopes = tenantId ? [`VSCODE_TENANT:${tenantId}`] : [];
const clientIdScopes = options.applicationClientId ? [`VSCODE_CLIENT_ID:${options.applicationClientId}`] : [];
return [...defaultScopes, ...tenantScopes, ...clientIdScopes];
}

async function getTenants(session: AuthenticationSession): Promise<Errorable<Tenant[]>> {
const armEndpoint = getConfiguredAzureEnv().resourceManagerEndpointUrl;
const credential: TokenCredential = {
Expand All @@ -273,14 +291,14 @@ async function getTenants(session: AuthenticationSession): Promise<Errorable<Ten
const subscriptionClient = new SubscriptionClient(credential, { endpoint: armEndpoint });

const tenantsResult = await listAll(subscriptionClient.tenants.list());
return errmap(tenantsResult, (t) => t.filter(asTenant).map((t) => ({ name: t.displayName, id: t.tenantId })));
return errmap(tenantsResult, (t) => t.filter(isTenant).map((t) => ({ name: t.displayName, id: t.tenantId })));
}

function findTenant(tenants: Tenant[], tenantId: string): Tenant | null {
return tenants.find((t) => t.id === tenantId) || null;
}

function asTenant(tenant: TenantIdDescription): tenant is { tenantId: string; displayName: string } {
function isTenant(tenant: TenantIdDescription): tenant is { tenantId: string; displayName: string } {
return tenant.tenantId !== undefined && tenant.displayName !== undefined;
}

Expand Down
7 changes: 6 additions & 1 deletion src/auth/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,18 @@ export type Tenant = {
id: string;
};

export type GetAuthSessionOptions = {
applicationClientId?: string;
scopes?: string[];
};

export type AzureSessionProvider = {
signIn(): Promise<void>;
signInStatus: SignInStatus;
availableTenants: Tenant[];
selectedTenant: Tenant | null;
signInStatusChangeEvent: Event<SignInStatus>;
getAuthSession(): Promise<Errorable<AzureAuthenticationSession>>;
getAuthSession(options?: GetAuthSessionOptions): Promise<Errorable<AzureAuthenticationSession>>;
dispose(): void;
};

Expand Down

0 comments on commit 4853c8d

Please sign in to comment.