-
Notifications
You must be signed in to change notification settings - Fork 52
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Remove dependency on Azure Account extension (#551)
* Use vscode authentication library instead of Azure Account extension * remove msal-node dependency * remove azure auth * remove duplication of getDefaultScope function and add error handling * auth fixes for switching between signed-in tenants * If no subscriptions are selected, display them all in the treeview * remove unused properties from jwt interface * fix URLs in select-subscriptions command * get tenant id from auth session and new filter option for listing subs * avoid passing treenodes to cluster library functions * avoid exposing ISubscriptionContext from tree nodes * handle session changes when tenants change and not when we triggered the change, and get session silently whenever possible * Select a tenant by default rather than forcing user to do that * list clusters using resource management client (as we were before) * dedicated command for tenant selection, and abstract session provider with interface
- Loading branch information
Showing
43 changed files
with
1,648 additions
and
6,483 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
import { | ||
AuthenticationSession, | ||
Disposable as VsCodeDisposable, | ||
ProgressLocation, | ||
ProgressOptions, | ||
QuickPickItem, | ||
window, | ||
} from "vscode"; | ||
import { AzureSessionProvider, ReadyAzureSessionProvider, Tenant, TokenInfo, isReady } from "./types"; | ||
import { Environment } from "@azure/ms-rest-azure-env"; | ||
import { getConfiguredAzureEnv } from "../commands/utils/config"; | ||
import { Errorable, failed } from "../commands/utils/errorable"; | ||
import { TokenCredential } from "@azure/core-auth"; | ||
import { parseJson } from "../commands/utils/json"; | ||
import { getSessionProvider } from "./azureSessionProvider"; | ||
|
||
export function getEnvironment(): Environment { | ||
return getConfiguredAzureEnv(); | ||
} | ||
|
||
export async function getReadySessionProvider(): Promise<Errorable<ReadyAzureSessionProvider>> { | ||
const sessionProvider = getSessionProvider(); | ||
if (isReady(sessionProvider)) { | ||
return { succeeded: true, result: sessionProvider }; | ||
} | ||
|
||
switch (sessionProvider.signInStatus) { | ||
case "Initializing": | ||
case "SigningIn": | ||
await waitForSignIn(sessionProvider); | ||
break; | ||
case "SignedOut": | ||
await sessionProvider.signIn(); | ||
break; | ||
case "SignedIn": | ||
break; | ||
} | ||
|
||
// Get a session, which will prompt the user to select a tenant if necessary. | ||
const session = await sessionProvider.getAuthSession(); | ||
if (failed(session)) { | ||
return { succeeded: false, error: `Failed to get authentication session: ${session.error}` }; | ||
} | ||
|
||
if (!isReady(sessionProvider)) { | ||
return { succeeded: false, error: "Not signed in." }; | ||
} | ||
|
||
return { succeeded: true, result: sessionProvider }; | ||
} | ||
|
||
async function waitForSignIn(sessionProvider: AzureSessionProvider): Promise<void> { | ||
const options: ProgressOptions = { | ||
location: ProgressLocation.Notification, | ||
title: "Waiting for sign-in", | ||
cancellable: true, | ||
}; | ||
|
||
await window.withProgress(options, (_, token) => { | ||
let listener: VsCodeDisposable | undefined; | ||
token.onCancellationRequested(listener?.dispose()); | ||
return new Promise((resolve) => { | ||
listener = sessionProvider.signInStatusChangeEvent((status) => { | ||
if (status === "SignedIn") { | ||
listener?.dispose(); | ||
resolve(undefined); | ||
} | ||
}); | ||
}); | ||
}); | ||
} | ||
|
||
export function getCredential(sessionProvider: ReadyAzureSessionProvider): TokenCredential { | ||
return { | ||
getToken: async () => { | ||
const session = await sessionProvider.getAuthSession(); | ||
if (failed(session)) { | ||
throw new Error(`No Microsoft authentication session found: ${session.error}`); | ||
} | ||
|
||
return { token: session.result.accessToken, expiresOnTimestamp: 0 }; | ||
}, | ||
}; | ||
} | ||
|
||
export function getTokenInfo(session: AuthenticationSession): Errorable<TokenInfo> { | ||
const tokenParts = session.accessToken.split("."); | ||
if (tokenParts.length !== 3) { | ||
return { succeeded: false, error: `Access token not a valid JWT: ${session.accessToken}` }; | ||
} | ||
|
||
const body = tokenParts[1]; | ||
let jsonBody: string; | ||
try { | ||
jsonBody = Buffer.from(body, "base64").toString(); | ||
} catch (e) { | ||
return { succeeded: false, error: `Failed to decode JWT token body: ${body}` }; | ||
} | ||
|
||
const jwt = parseJson<Jwt>(jsonBody); | ||
if (failed(jwt)) { | ||
return jwt; | ||
} | ||
|
||
const tokenInfo: TokenInfo = { | ||
token: session.accessToken, | ||
expiry: new Date(jwt.result.exp * 1000), | ||
}; | ||
|
||
return { succeeded: true, result: tokenInfo }; | ||
} | ||
|
||
export function getDefaultScope(endpointUrl: string): string { | ||
// Endpoint URL is that of the audience, e.g. for ARM in the public cloud | ||
// it would be "https://management.azure.com". | ||
return endpointUrl.endsWith("/") ? `${endpointUrl}.default` : `${endpointUrl}/.default`; | ||
} | ||
|
||
/** | ||
* The type of a JSON-parsed JWT body. Right now we only make use of the 'exp' field, | ||
* but other standard claims could be added here if needed. | ||
*/ | ||
interface Jwt { | ||
exp: number; | ||
} | ||
|
||
export async function quickPickTenant(tenants: Tenant[]): Promise<Tenant | undefined> { | ||
const items: (QuickPickItem & { tenant: Tenant })[] = tenants.map((t) => ({ | ||
label: `${t.name} (${t.id})`, | ||
tenant: t, | ||
})); | ||
const result = await window.showQuickPick(items, { | ||
placeHolder: "Select a tenant", | ||
}); | ||
return result ? result.tenant : undefined; | ||
} |
Oops, something went wrong.