Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate to new Azure SDK packages #197

Merged
merged 9 commits into from
Jun 26, 2020
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
563 changes: 279 additions & 284 deletions ThirdPartyNotice.txt

Large diffs are not rendered by default.

304 changes: 282 additions & 22 deletions package-lock.json

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -222,12 +222,17 @@
"webpack-cli": "3.1.2"
},
"dependencies": {
"@azure/arm-appservice": "^6.0.0",
"@azure/arm-resources": "^3.0.0",
"@azure/arm-subscriptions": "^2.0.0",
"@azure/ms-rest-azure-env": "^2.0.0",
"@azure/ms-rest-nodeauth": "3.0.5",
"adal-node": "0.1.28",
"azure-arm-resource": "7.0.1",
"form-data": "2.3.3",
"http-proxy-agent": "2.1.0",
"https-proxy-agent": "2.2.3",
"ms-rest-azure": "2.5.9",
"ms-rest-js": "^1.0.1",
"request": "2.88.0",
"request-promise": "4.2.2",
"semver": "5.6.0",
Expand Down
11 changes: 6 additions & 5 deletions sample/src/extension.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { window, ExtensionContext, commands, QuickPickItem, extensions } from 'vscode';
import { AzureAccount, AzureSession } from '../../src/azure-account.api'; // Other extensions need to copy this .d.ts to their repository.
import { SubscriptionClient, ResourceManagementClient, SubscriptionModels } from 'azure-arm-resource';
import WebSiteManagementClient = require('azure-arm-website');
import { SubscriptionClient, SubscriptionModels } from '@azure/arm-subscriptions';
import { ResourceManagementClient } from '@azure/arm-resources';
import { WebSiteManagementClient } from '@azure/arm-appservice';

export function activate(context: ExtensionContext) {
const azureAccount = extensions.getExtension<AzureAccount>('ms-vscode.azure-account')!.exports;
Expand Down Expand Up @@ -35,7 +36,7 @@ async function loadSubscriptionItems(api: AzureAccount) {
await api.waitForFilters();
const subscriptionItems: SubscriptionItem[] = [];
for (const session of api.sessions) {
const credentials = session.credentials;
const credentials = session.credentialsV2;
const subscriptionClient = new SubscriptionClient(credentials);
const subscriptions = await listAll(subscriptionClient.subscriptions, subscriptionClient.subscriptions.list());
subscriptionItems.push(...subscriptions.map(subscription => ({
Expand All @@ -51,7 +52,7 @@ async function loadSubscriptionItems(api: AzureAccount) {

async function loadResourceGroupItems(subscriptionItem: SubscriptionItem) {
const { session, subscription } = subscriptionItem;
const resources = new ResourceManagementClient(session.credentials, subscription.subscriptionId!);
const resources = new ResourceManagementClient(session.credentialsV2, subscription.subscriptionId!);
const resourceGroups = await listAll(resources.resourceGroups, resources.resourceGroups.list());
resourceGroups.sort((a, b) => (a.name || '').localeCompare(b.name || ''));
return resourceGroups.map(resourceGroup => ({
Expand All @@ -75,7 +76,7 @@ async function loadWebAppItems(api: AzureAccount) {
await api.waitForFilters();
const webAppsPromises: Promise<QuickPickItem[]>[] = [];
for (const filter of api.filters) {
const client = new WebSiteManagementClient(filter.session.credentials, filter.subscription.subscriptionId!);
const client = new WebSiteManagementClient(filter.session.credentialsV2, filter.subscription.subscriptionId!);
webAppsPromises.push(listAll(client.webApps, client.webApps.list())
.then(webApps => webApps.map(webApp => {
return {
Expand Down
8 changes: 5 additions & 3 deletions src/azure-account.api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@

import { Event, Terminal, Progress, CancellationToken } from 'vscode';
import { ServiceClientCredentials } from 'ms-rest';
import { AzureEnvironment } from 'ms-rest-azure';
import { SubscriptionModels } from 'azure-arm-resource';
import { ReadStream } from 'fs';
import { DeviceTokenCredentials } from '@azure/ms-rest-nodeauth';
import { Environment } from '@azure/ms-rest-azure-env';
import { SubscriptionModels } from '@azure/arm-subscriptions';

export type AzureLoginStatus = 'Initializing' | 'LoggingIn' | 'LoggedIn' | 'LoggedOut';

Expand All @@ -27,10 +28,11 @@ export interface AzureAccount {
}

export interface AzureSession {
readonly environment: AzureEnvironment;
readonly environment: Environment;
readonly userId: string;
readonly tenantId: string;
readonly credentials: ServiceClientCredentials;
readonly credentialsV2: DeviceTokenCredentials;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a more general interface like ServiceClientCredentials in the old SDK? 'Device' refers to the device login flow.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to TokenCredentialsBase, which seems to be the most general interface for the new SDK

}

export interface AzureSubscription {
Expand Down
62 changes: 33 additions & 29 deletions src/azure-account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ const CacheDriver = require('adal-node/lib/cache-driver');
const createLogContext = require('adal-node/lib/log').createLogContext;

import { MemoryCache, AuthenticationContext, Logging, UserCodeInfo } from 'adal-node';
import { DeviceTokenCredentials, AzureEnvironment } from 'ms-rest-azure';
import { SubscriptionClient, SubscriptionModels } from 'azure-arm-resource';
import { DeviceTokenCredentials } from 'ms-rest-azure';
import { SubscriptionClient, SubscriptionModels } from '@azure/arm-subscriptions';
import * as nls from 'vscode-nls';
import * as keytarType from 'keytar';
import * as http from 'http';
Expand All @@ -20,6 +20,8 @@ import { createCloudConsole } from './cloudConsole';
import * as codeFlowLogin from './codeFlowLogin';
import { TelemetryReporter } from './telemetry';
import { TokenResponse } from 'adal-node';
import { DeviceTokenCredentials as DeviceTokenCredentials2 } from '@azure/ms-rest-nodeauth';
import { Environment } from '@azure/ms-rest-azure-env';

const localize = nls.loadMessageBundle();

Expand All @@ -44,7 +46,7 @@ function getNodeModule<T>(moduleName: string): T | undefined {

const credentialsSection = 'VS Code Azure';

async function getStoredCredentials(environment: AzureEnvironment, migrateToken?: boolean) {
async function getStoredCredentials(environment: Environment, migrateToken?: boolean) {
if (!keytar) {
return;
}
Expand All @@ -68,7 +70,7 @@ async function getStoredCredentials(environment: AzureEnvironment, migrateToken?
}
}

async function storeRefreshToken(environment: AzureEnvironment, token: string) {
async function storeRefreshToken(environment: Environment, token: string) {
if (keytar) {
try {
await keytar.setPassword(credentialsSection, environment.name, token);
Expand All @@ -88,11 +90,11 @@ async function deleteRefreshToken(environmentName: string) {
}
}

const staticEnvironments: AzureEnvironment[] = [
AzureEnvironment.Azure,
AzureEnvironment.AzureChina,
AzureEnvironment.AzureGermanCloud,
AzureEnvironment.AzureUSGovernment
const staticEnvironments: Environment[] = [
Environment.AzureCloud,
Environment.ChinaCloud,
Environment.GermanCloud,
Environment.USGovernment
];

const azurePPE = 'AzurePPE';
Expand Down Expand Up @@ -378,7 +380,7 @@ export class AzureLoginHelper {
throw new AzureLoginError(localize('azure-account.malformedCredentials', "Stored credentials are invalid"));
}

tokenResponse = await codeFlowLogin.tokenWithAuthorizationCode(clientId, AzureEnvironment.Azure, redirectionUrl, tenantId, code);
tokenResponse = await codeFlowLogin.tokenWithAuthorizationCode(clientId, Environment.AzureCloud, redirectionUrl, tenantId, code);
}

if (!tokenResponse) {
Expand Down Expand Up @@ -460,18 +462,19 @@ export class AzureLoginHelper {
const key = `${environment} ${userId} ${tenantId}`;
if (!sessions[key]) {
sessions[key] = {
environment: (<any>AzureEnvironment)[environment],
environment: (<any>Environment)[environment],
userId,
tenantId,
credentials: new DeviceTokenCredentials({ environment: (<any>AzureEnvironment)[environment], username: userId, clientId, tokenCache: this.delayedCache, domain: tenantId })
credentials: new DeviceTokenCredentials({ environment: (<any>Environment)[environment], username: userId, clientId, tokenCache: this.delayedCache, domain: tenantId }),
credentialsV2: new DeviceTokenCredentials2(clientId, tenantId, userId, undefined, (<any>Environment)[environment], this.delayedCache)
};
this.api.sessions.push(sessions[key]);
}
}
return sessions;
}

private async updateSessions(environment: AzureEnvironment, tokenResponses: TokenResponse[]) {
private async updateSessions(environment: Environment, tokenResponses: TokenResponse[]) {
await clearTokenCache(this.tokenCache);
for (const tokenResponse of tokenResponses) {
await addTokenToCache(environment, this.tokenCache, tokenResponse);
Expand All @@ -482,7 +485,8 @@ export class AzureLoginHelper {
environment,
userId: tokenResponse.userId!,
tenantId: tokenResponse.tenantId!,
credentials: new DeviceTokenCredentials({ environment: environment, username: tokenResponse.userId, clientId, tokenCache: this.delayedCache, domain: tokenResponse.tenantId })
credentials: new DeviceTokenCredentials({ environment: (<any>environment), username: tokenResponse.userId, clientId, tokenCache: this.delayedCache, domain: tokenResponse.tenantId }),
credentialsV2: new DeviceTokenCredentials2(clientId, tokenResponse.tenantId, tokenResponse.userId, undefined, environment, this.delayedCache)
})));
this.onSessionsChanged.fire();
}
Expand Down Expand Up @@ -602,8 +606,8 @@ export class AzureLoginHelper {

private async loadSubscriptions() {
const lists = await Promise.all(this.api.sessions.map(session => {
const credentials = session.credentials;
const client = new SubscriptionClient.SubscriptionClient(credentials, session.environment.resourceManagerEndpointUrl);
const credentials = session.credentialsV2;
const client = new SubscriptionClient(credentials, { baseUri: session.environment.resourceManagerEndpointUrl });
return listAll(client.subscriptions, client.subscriptions.list())
.then(list => list.map(subscription => ({
session,
Expand Down Expand Up @@ -703,15 +707,15 @@ export class AzureLoginHelper {
}
}

function getSelectedEnvironment(): AzureEnvironment {
function getSelectedEnvironment(): Environment {
const envConfig = workspace.getConfiguration('azure');
const envSetting = envConfig.get<string>('cloud');
return getEnvironments().find(environment => environment.name === envSetting) || AzureEnvironment.Azure;
return getEnvironments().find(environment => environment.name === envSetting) || Environment.AzureCloud;
}

function getEnvironments() {
const config = workspace.getConfiguration('azure');
const ppe = config.get<AzureEnvironment>('ppe');
const ppe = config.get<Environment>('ppe');
if (ppe) {
return [
...staticEnvironments,
Expand All @@ -730,7 +734,7 @@ function getTenantId() {
return envConfig.get<string>('tenant') || commonTenantId;
}

async function deviceLogin(environment: AzureEnvironment, tenantId: string) {
async function deviceLogin(environment: Environment, tenantId: string) {
const deviceLogin = await deviceLogin1(environment, tenantId);
const message = showDeviceCodeMessage(deviceLogin);
const login2 = deviceLogin2(environment, tenantId, deviceLogin);
Expand All @@ -748,7 +752,7 @@ async function showDeviceCodeMessage(deviceLogin: UserCodeInfo): Promise<void> {
}
}

async function deviceLogin1(environment: AzureEnvironment, tenantId: string): Promise<UserCodeInfo> {
async function deviceLogin1(environment: Environment, tenantId: string): Promise<UserCodeInfo> {
return new Promise<UserCodeInfo>((resolve, reject) => {
const cache = new MemoryCache();
const context = new AuthenticationContext(`${environment.activeDirectoryEndpointUrl}${tenantId}`, validateAuthority, cache);
Expand All @@ -762,7 +766,7 @@ async function deviceLogin1(environment: AzureEnvironment, tenantId: string): Pr
});
}

async function deviceLogin2(environment: AzureEnvironment, tenantId: string, deviceLogin: UserCodeInfo) {
async function deviceLogin2(environment: Environment, tenantId: string, deviceLogin: UserCodeInfo) {
return new Promise<TokenResponse>((resolve, reject) => {
const tokenCache = new MemoryCache();
const context = new AuthenticationContext(`${environment.activeDirectoryEndpointUrl}${tenantId}`, validateAuthority, tokenCache);
Expand All @@ -785,7 +789,7 @@ async function redirectTimeout() {
}
}

export async function tokenFromRefreshToken(environment: AzureEnvironment, refreshToken: string, tenantId: string, resource?: string) {
export async function tokenFromRefreshToken(environment: Environment, refreshToken: string, tenantId: string, resource?: string) {
return new Promise<TokenResponse>((resolve, reject) => {
const tokenCache = new MemoryCache();
const context = new AuthenticationContext(`${environment.activeDirectoryEndpointUrl}${tenantId}`, validateAuthority, tokenCache);
Expand All @@ -801,11 +805,11 @@ export async function tokenFromRefreshToken(environment: AzureEnvironment, refre
});
}

async function tokensFromToken(environment: AzureEnvironment, firstTokenResponse: TokenResponse) {
async function tokensFromToken(environment: Environment, firstTokenResponse: TokenResponse) {
const tokenCache = new MemoryCache();
await addTokenToCache(environment, tokenCache, firstTokenResponse);
const credentials = new DeviceTokenCredentials({ username: firstTokenResponse.userId, clientId, tokenCache, environment });
const client = new SubscriptionClient.SubscriptionClient(credentials, environment.resourceManagerEndpointUrl);
const credentials = new DeviceTokenCredentials2(clientId, undefined, firstTokenResponse.userId, undefined, environment, tokenCache);
const client = new SubscriptionClient(credentials, { baseUri: environment.resourceManagerEndpointUrl });
const tenants = await listAll(client.tenants, client.tenants.list());
const responses = <TokenResponse[]>(await Promise.all<TokenResponse | null>(tenants.map((tenant, i) => {
if (tenant.tenantId === firstTokenResponse.tenantId) {
Expand All @@ -823,7 +827,7 @@ async function tokensFromToken(environment: AzureEnvironment, firstTokenResponse
return responses;
}

async function addTokenToCache(environment: AzureEnvironment, tokenCache: any, tokenResponse: TokenResponse) {
async function addTokenToCache(environment: Environment, tokenCache: any, tokenResponse: TokenResponse) {
return new Promise<any>((resolve, reject) => {
const driver = new CacheDriver(
{ _logContext: createLogContext('') },
Expand Down Expand Up @@ -921,7 +925,7 @@ function getErrorMessage(err: any): string | undefined {
return str;
}

async function becomeOnline(environment: AzureEnvironment, interval: number, token = new CancellationTokenSource().token) {
async function becomeOnline(environment: Environment, interval: number, token = new CancellationTokenSource().token) {
let o = isOnline(environment);
let d = delay(interval, false);
while (!token.isCancellationRequested && !await Promise.race([o, d])) {
Expand All @@ -931,7 +935,7 @@ async function becomeOnline(environment: AzureEnvironment, interval: number, tok
}
}

async function isOnline(environment: AzureEnvironment) {
async function isOnline(environment: Environment) {
try {
await new Promise<http.IncomingMessage | https.IncomingMessage>((resolve, reject) => {
const url = environment.activeDirectoryEndpointUrl;
Expand Down
2 changes: 1 addition & 1 deletion src/cloudConsole.ts
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,7 @@ export function createCloudConsole(api: AzureAccount, reporter: TelemetryReporte
// Additional tokens
const [graphToken, keyVaultToken] = await Promise.all([
tokenFromRefreshToken(session.environment, result.token.refreshToken, session.tenantId, session.environment.activeDirectoryGraphResourceId),
tokenFromRefreshToken(session.environment, result.token.refreshToken, session.tenantId, `https://${session.environment.keyVaultDnsSuffix.substr(1)}`)
tokenFromRefreshToken(session.environment, result.token.refreshToken, session.tenantId, `https://${session.environment.keyVaultDnsSuffix!.substr(1)}`)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if the is a good fallback for when keyVaultDnsSuffix is not available. Maybe CloudShell works without a KeyVault token? (It used to.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I just tried without a KeyVault token and it seemed to work fine without it. I'll change this to make the keyVault token optional

]);
const accessTokens: AccessTokens = {
resource: accessToken,
Expand Down
14 changes: 7 additions & 7 deletions src/codeFlowLogin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ import * as path from 'path';
import * as crypto from 'crypto';
import * as net from 'net';
import * as vscode from 'vscode';
import { AzureEnvironment } from 'ms-rest-azure';
import { Environment } from '@azure/ms-rest-azure-env';
import { TokenResponse, AuthenticationContext } from 'adal-node';

export const redirectUrlAAD = 'https://vscode-redirect.azurewebsites.net/';
const portADFS = 19472;
const redirectUrlADFS = `http://127.0.0.1:${portADFS}/`;

export function isADFS(environment: AzureEnvironment) {
export function isADFS(environment: Environment) {
const u = url.parse(environment.activeDirectoryEndpointUrl);
const pathname = (u.pathname || '').toLowerCase();
return pathname === '/adfs' || pathname.startsWith('/adfs/');
Expand Down Expand Up @@ -80,7 +80,7 @@ const handler = new UriEventHandler();

vscode.window.registerUriHandler(handler);

async function exchangeCodeForToken(clientId: string, environment: AzureEnvironment, tenantId: string, callbackUri: string, state: string) {
async function exchangeCodeForToken(clientId: string, environment: Environment, tenantId: string, callbackUri: string, state: string) {
let uriEventListener: vscode.Disposable;
return new Promise((resolve: (value: TokenResponse) => void , reject) => {
uriEventListener = handler.event(async (uri: vscode.Uri) => {
Expand Down Expand Up @@ -126,7 +126,7 @@ function getCallbackEnvironment(callbackUri: vscode.Uri): string {
}
}

async function loginWithoutLocalServer(clientId: string, environment: AzureEnvironment, adfs: boolean, tenantId: string): Promise<TokenResponse> {
async function loginWithoutLocalServer(clientId: string, environment: Environment, adfs: boolean, tenantId: string): Promise<TokenResponse> {
const callbackUri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://ms-vscode.azure-account`));
const callback = redirectUrlAAD;
const nonce = crypto.randomBytes(16).toString('base64');
Expand All @@ -150,7 +150,7 @@ async function loginWithoutLocalServer(clientId: string, environment: AzureEnvir
return Promise.race([exchangeCodeForToken(clientId, environment, tenantId, callback, state), timeoutPromise]);
}

export async function login(clientId: string, environment: AzureEnvironment, adfs: boolean, tenantId: string, openUri: (url: string) => Promise<void>, redirectTimeout: () => Promise<void>) {
export async function login(clientId: string, environment: Environment, adfs: boolean, tenantId: string, openUri: (url: string) => Promise<void>, redirectTimeout: () => Promise<void>) {
if (vscode.env.uiKind === vscode.UIKind.Web) {
return loginWithoutLocalServer(clientId, environment, adfs, tenantId);
}
Expand Down Expand Up @@ -344,7 +344,7 @@ async function callback(nonce: string, reqUrl: url.Url): Promise<string> {
throw new Error(error || 'No code received.');
}

export async function tokenWithAuthorizationCode(clientId: string, environment: AzureEnvironment, redirectUrl: string, tenantId: string, code: string) {
export async function tokenWithAuthorizationCode(clientId: string, environment: Environment, redirectUrl: string, tenantId: string, code: string) {
return new Promise<TokenResponse>((resolve, reject) => {
const context = new AuthenticationContext(`${environment.activeDirectoryEndpointUrl}${tenantId}`);
context.acquireTokenWithAuthorizationCode(code, redirectUrl, environment.activeDirectoryResourceId, clientId, <any>undefined, (err, response) => {
Expand All @@ -360,6 +360,6 @@ export async function tokenWithAuthorizationCode(clientId: string, environment:
}

if (require.main === module) {
login('aebc6443-996d-45c2-90f0-388ff96faa56', AzureEnvironment.Azure, false, 'common', async uri => console.log(`Open: ${uri}`), async () => console.log('Browser did not connect to local server within 10 seconds.'))
login('aebc6443-996d-45c2-90f0-388ff96faa56', Environment.AzureCloud, false, 'common', async uri => console.log(`Open: ${uri}`), async () => console.log('Browser did not connect to local server within 10 seconds.'))
.catch(console.error);
}
Loading