mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-04-01 09:30:31 -04:00
Large cleanup of AzureCore - Introduction of getAccountSecurityToken and deprecation of getSecurityToken (#11446)
* do a large cleanup of azurecore * Fix tests * Rework Device Code * Fix tests * Fix AE scenario * Fix firewall rule - clenaup logging * Shorthand syntax * Fix firewall tests * Start on tests for azureAuth * Add more tests * Address comments * Add a few more important tests * Don't throw error on old code * Fill in todo
This commit is contained in:
@@ -3,104 +3,39 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import * as azdata from 'azdata';
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
import axios, { AxiosResponse, AxiosRequestConfig } from 'axios';
|
||||
import * as qs from 'qs';
|
||||
import * as url from 'url';
|
||||
|
||||
import {
|
||||
AzureAccountProviderMetadata,
|
||||
Tenant,
|
||||
AzureAccount,
|
||||
Resource,
|
||||
AzureAccountProviderMetadata,
|
||||
AzureAuthType,
|
||||
Subscription,
|
||||
Deferred
|
||||
Deferred,
|
||||
Resource,
|
||||
Tenant
|
||||
} from '../interfaces';
|
||||
import * as url from 'url';
|
||||
|
||||
import { SimpleTokenCache } from '../simpleTokenCache';
|
||||
import { MemoryDatabase } from '../utils/memoryDatabase';
|
||||
import axios, { AxiosRequestConfig, AxiosResponse } from 'axios';
|
||||
import { Logger } from '../../utils/Logger';
|
||||
import * as qs from 'qs';
|
||||
import { AzureAuthError } from './azureAuthError';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export interface AccountKey {
|
||||
/**
|
||||
* Account Key - uniquely identifies an account
|
||||
*/
|
||||
key: string
|
||||
}
|
||||
|
||||
export interface AccessToken extends AccountKey {
|
||||
/**
|
||||
* Access Token
|
||||
*/
|
||||
token: string;
|
||||
}
|
||||
|
||||
export interface RefreshToken extends AccountKey {
|
||||
/**
|
||||
* Refresh Token
|
||||
*/
|
||||
token: string;
|
||||
|
||||
/**
|
||||
* Account Key
|
||||
*/
|
||||
key: string
|
||||
}
|
||||
|
||||
export interface TokenResponse {
|
||||
[tenantId: string]: Token
|
||||
}
|
||||
|
||||
export interface Token extends AccountKey {
|
||||
/**
|
||||
* Access token
|
||||
*/
|
||||
token: string;
|
||||
|
||||
/**
|
||||
* TokenType
|
||||
*/
|
||||
tokenType: string;
|
||||
}
|
||||
|
||||
export interface TokenClaims { // https://docs.microsoft.com/en-us/azure/active-directory/develop/id-tokens
|
||||
aud: string;
|
||||
iss: string;
|
||||
iat: number;
|
||||
idp: string,
|
||||
nbf: number;
|
||||
exp: number;
|
||||
c_hash: string;
|
||||
at_hash: string;
|
||||
aio: string;
|
||||
preferred_username: string;
|
||||
email: string;
|
||||
name: string;
|
||||
nonce: string;
|
||||
oid: string;
|
||||
roles: string[];
|
||||
rh: string;
|
||||
sub: string;
|
||||
tid: string;
|
||||
unique_name: string;
|
||||
uti: string;
|
||||
ver: string;
|
||||
}
|
||||
|
||||
export type TokenRefreshResponse = { accessToken: AccessToken, refreshToken: RefreshToken, tokenClaims: TokenClaims, expiresOn: string };
|
||||
|
||||
export abstract class AzureAuth implements vscode.Disposable {
|
||||
protected readonly memdb = new MemoryDatabase();
|
||||
protected readonly memdb = new MemoryDatabase<string>();
|
||||
|
||||
protected readonly WorkSchoolAccountType: string = 'work_school';
|
||||
protected readonly MicrosoftAccountType: string = 'microsoft';
|
||||
|
||||
protected readonly loginEndpointUrl: string;
|
||||
protected readonly commonTenant: Tenant;
|
||||
public readonly commonTenant: Tenant;
|
||||
protected readonly redirectUri: string;
|
||||
protected readonly scopes: string[];
|
||||
protected readonly scopesString: string;
|
||||
@@ -141,185 +76,227 @@ export abstract class AzureAuth implements vscode.Disposable {
|
||||
this.scopesString = this.scopes.join(' ');
|
||||
}
|
||||
|
||||
public abstract async login(): Promise<AzureAccount | azdata.PromptFailedResult>;
|
||||
|
||||
public abstract async autoOAuthCancelled(): Promise<void>;
|
||||
|
||||
public abstract async promptForConsent(resourceId: string, tenant: string): Promise<{ tokenRefreshResponse: TokenRefreshResponse, authCompleteDeferred: Deferred<void> } | undefined>;
|
||||
|
||||
public dispose() { }
|
||||
|
||||
public async refreshAccess(oldAccount: azdata.Account): Promise<azdata.Account> {
|
||||
const response = await this.getCachedToken(oldAccount.key);
|
||||
if (!response) {
|
||||
oldAccount.isStale = true;
|
||||
return oldAccount;
|
||||
}
|
||||
|
||||
const refreshToken = response.refreshToken;
|
||||
if (!refreshToken || !refreshToken.key) {
|
||||
oldAccount.isStale = true;
|
||||
return oldAccount;
|
||||
}
|
||||
|
||||
public async startLogin(): Promise<AzureAccount | azdata.PromptFailedResult> {
|
||||
let loginComplete: Deferred<void>;
|
||||
try {
|
||||
// Refresh the access token
|
||||
const tokenResponse = await this.refreshAccessToken(oldAccount.key, refreshToken);
|
||||
const tenants = await this.getTenants(tokenResponse.accessToken);
|
||||
|
||||
// Recreate account object
|
||||
const newAccount = this.createAccount(tokenResponse.tokenClaims, tokenResponse.accessToken.key, tenants);
|
||||
|
||||
const subscriptions = await this.getSubscriptions(newAccount);
|
||||
newAccount.properties.subscriptions = subscriptions;
|
||||
|
||||
return newAccount;
|
||||
const result = await this.login(this.commonTenant, this.metadata.settings.microsoftResource);
|
||||
loginComplete = result.authComplete;
|
||||
if (!result?.response) {
|
||||
Logger.error('Authentication failed');
|
||||
return {
|
||||
canceled: false
|
||||
};
|
||||
}
|
||||
const account = await this.hydrateAccount(result.response.accessToken, result.response.tokenClaims);
|
||||
loginComplete?.resolve();
|
||||
return account;
|
||||
} catch (ex) {
|
||||
oldAccount.isStale = true;
|
||||
if (ex.message) {
|
||||
await vscode.window.showErrorMessage(ex.message);
|
||||
if (ex instanceof AzureAuthError) {
|
||||
if (loginComplete) {
|
||||
loginComplete.reject(ex.getPrintableString());
|
||||
} else {
|
||||
vscode.window.showErrorMessage(ex.getPrintableString());
|
||||
}
|
||||
}
|
||||
Logger.error(ex);
|
||||
return undefined;
|
||||
}
|
||||
return oldAccount;
|
||||
}
|
||||
|
||||
private getHomeTenant(account: AzureAccount): Tenant {
|
||||
// Home is defined by the API
|
||||
// Lets pick the home tenant - and fall back to commonTenant if they don't exist
|
||||
return account.properties.tenants.find(t => t.tenantCategory === 'Home') ?? account.properties.tenants[0] ?? this.commonTenant;
|
||||
}
|
||||
|
||||
public async getSecurityToken(account: azdata.Account, azureResource: azdata.AzureResource): Promise<TokenResponse | undefined> {
|
||||
public async refreshAccess(account: AzureAccount): Promise<AzureAccount> {
|
||||
try {
|
||||
const tenant = this.getHomeTenant(account);
|
||||
const tokenResult = await this.getAccountSecurityToken(account, tenant.id, azdata.AzureResource.MicrosoftResourceManagement);
|
||||
if (!tokenResult) {
|
||||
account.isStale = true;
|
||||
return account;
|
||||
}
|
||||
|
||||
return await this.hydrateAccount(tokenResult, this.getTokenClaims(tokenResult.token));
|
||||
} catch (ex) {
|
||||
if (ex instanceof AzureAuthError) {
|
||||
vscode.window.showErrorMessage(ex.getPrintableString());
|
||||
}
|
||||
Logger.error(ex);
|
||||
account.isStale = true;
|
||||
return account;
|
||||
}
|
||||
}
|
||||
|
||||
public async hydrateAccount(token: Token | AccessToken, tokenClaims: TokenClaims): Promise<AzureAccount> {
|
||||
const tenants = await this.getTenants({ ...token });
|
||||
const account = this.createAccount(tokenClaims, token.key, tenants);
|
||||
return account;
|
||||
}
|
||||
|
||||
public async getAccountSecurityToken(account: AzureAccount, tenantId: string, azureResource: azdata.AzureResource): Promise<Token | undefined> {
|
||||
if (account.isStale === true) {
|
||||
Logger.log('Account was stale, no tokens being fetched');
|
||||
Logger.log('Account was stale. No tokens being fetched.');
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const resource = this.resources.find(s => s.azureResourceId === azureResource);
|
||||
if (!resource) {
|
||||
Logger.log('Invalid resource, not fetching', azureResource);
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const azureAccount = account as AzureAccount;
|
||||
const response: TokenResponse = {};
|
||||
const tenant = account.properties.tenants.find(t => t.id === tenantId);
|
||||
|
||||
for (const tenant of azureAccount.properties.tenants) {
|
||||
let cachedTokens = await this.getCachedToken(account.key, resource.id, tenant.id);
|
||||
// Check expiration
|
||||
if (cachedTokens) {
|
||||
const expiresOn = Number(this.memdb.get(this.createMemdbString(account.key.accountId, tenant.id, resource.id)));
|
||||
const currentTime = new Date().getTime() / 1000;
|
||||
if (!tenant) {
|
||||
throw new AzureAuthError(localize('azure.tenantNotFound', "Specifed tenant with ID '{0}' not found.", tenantId), `Tenant ${tenantId} not found.`, undefined);
|
||||
}
|
||||
|
||||
if (!Number.isNaN(expiresOn)) {
|
||||
const remainingTime = expiresOn - currentTime;
|
||||
const fiveMinutes = 5 * 60;
|
||||
// If the remaining time is less than five minutes, assume the token has expired. It's too close to expiration to be meaningful.
|
||||
if (remainingTime < fiveMinutes) {
|
||||
cachedTokens = undefined;
|
||||
}
|
||||
} else {
|
||||
// No expiration date, assume expired.
|
||||
cachedTokens = undefined;
|
||||
Logger.log('Assuming expired token due to no expiration date - this is expected on first launch.');
|
||||
}
|
||||
const cachedTokens = await this.getSavedToken(tenant, resource, account.key);
|
||||
|
||||
// Let's check to see if we can just use the cached tokens to return to the user
|
||||
if (cachedTokens?.accessToken) {
|
||||
let expiry = Number(cachedTokens.expiresOn);
|
||||
if (Number.isNaN(expiry)) {
|
||||
Logger.log('Expiration time was not defined. This is expected on first launch');
|
||||
expiry = 0;
|
||||
}
|
||||
const currentTime = new Date().getTime() / 1000;
|
||||
|
||||
// Refresh
|
||||
if (!cachedTokens) {
|
||||
let accessToken = cachedTokens.accessToken;
|
||||
const remainingTime = expiry - currentTime;
|
||||
const maxTolerance = 2 * 60; // two minutes
|
||||
|
||||
const baseToken = await this.getCachedToken(account.key);
|
||||
if (!baseToken) {
|
||||
account.isStale = true;
|
||||
Logger.log('Base token was empty, account is stale.');
|
||||
return undefined;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.refreshAccessToken(account.key, baseToken.refreshToken, tenant, resource);
|
||||
} catch (ex) {
|
||||
Logger.log(`Could not refresh access token for ${JSON.stringify(tenant)} - silently removing the tenant from the user's account.`);
|
||||
Logger.error(`Actual error: ${JSON.stringify(ex?.response?.data ?? ex.message ?? ex, undefined, 2)}`);
|
||||
azureAccount.properties.tenants = azureAccount.properties.tenants.filter(t => t.id !== tenant.id);
|
||||
continue;
|
||||
}
|
||||
|
||||
cachedTokens = await this.getCachedToken(account.key, resource.id, tenant.id);
|
||||
if (!cachedTokens) {
|
||||
Logger.log('Refresh access tokens didn not set cache');
|
||||
return undefined;
|
||||
}
|
||||
if (remainingTime < maxTolerance) {
|
||||
const result = await this.refreshToken(tenant, resource, cachedTokens.refreshToken);
|
||||
accessToken = result.accessToken;
|
||||
}
|
||||
const { accessToken } = cachedTokens;
|
||||
response[tenant.id] = {
|
||||
token: accessToken.token,
|
||||
key: accessToken.key,
|
||||
// Let's just return here.
|
||||
if (accessToken) {
|
||||
return {
|
||||
...accessToken,
|
||||
tokenType: 'Bearer'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// User didn't have any cached tokens, or the cached tokens weren't useful.
|
||||
// For most users we can use the refresh token from the general microsoft resource to an access token of basically any type of resource we want.
|
||||
const baseTokens = await this.getSavedToken(this.commonTenant, this.metadata.settings.microsoftResource, account.key);
|
||||
if (!baseTokens) {
|
||||
Logger.error('User had no base tokens for the basic resource registered. This should not happen and indicates something went wrong with the authentication cycle');
|
||||
const msg = localize('azure.noBaseToken', 'Something failed with the authentication, or your tokens have been deleted from the system. Please try adding your account to Azure Data Studio again.');
|
||||
account.isStale = true;
|
||||
throw new AzureAuthError(msg, 'No base token found', undefined);
|
||||
}
|
||||
// Let's try to convert the access token type, worst case we'll have to prompt the user to do an interactive authentication.
|
||||
const result = await this.refreshToken(tenant, resource, baseTokens.refreshToken);
|
||||
if (result.accessToken) {
|
||||
return {
|
||||
...result.accessToken,
|
||||
tokenType: 'Bearer'
|
||||
};
|
||||
}
|
||||
|
||||
if (azureAccount.properties.subscriptions) {
|
||||
azureAccount.properties.subscriptions.forEach(subscription => {
|
||||
// Make sure that tenant has information populated.
|
||||
if (response[subscription.tenantId]) {
|
||||
response[subscription.id] = {
|
||||
...response[subscription.tenantId]
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return response;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public async clearCredentials(account: azdata.AccountKey): Promise<void> {
|
||||
try {
|
||||
return this.deleteAccountCache(account);
|
||||
} catch (ex) {
|
||||
const msg = localize('azure.cacheErrrorRemove', "Error when removing your account from the cache.");
|
||||
vscode.window.showErrorMessage(msg);
|
||||
Logger.error('Error when removing tokens.', ex);
|
||||
}
|
||||
}
|
||||
|
||||
protected toBase64UrlEncoding(base64string: string): string {
|
||||
return base64string.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_'); // Need to use base64url encoding
|
||||
}
|
||||
|
||||
protected async makePostRequest(uri: string, postData: { [key: string]: string }, validateStatus = false) {
|
||||
try {
|
||||
const config: AxiosRequestConfig = {
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
}
|
||||
protected abstract async login(tenant: Tenant, resource: Resource): Promise<{ response: OAuthTokenResponse, authComplete: Deferred<void> }>;
|
||||
|
||||
/**
|
||||
* Refreshes a token, if a refreshToken is passed in then we use that. If it is not passed in then we will prompt the user for consent.
|
||||
* @param tenant
|
||||
* @param resource
|
||||
* @param refreshToken
|
||||
*/
|
||||
public async refreshToken(tenant: Tenant, resource: Resource, refreshToken: RefreshToken | undefined): Promise<OAuthTokenResponse> {
|
||||
if (refreshToken) {
|
||||
const postData: RefreshTokenPostData = {
|
||||
grant_type: 'refresh_token',
|
||||
client_id: this.clientId,
|
||||
refresh_token: refreshToken.token,
|
||||
tenant: tenant.id,
|
||||
resource: resource.endpoint
|
||||
};
|
||||
|
||||
if (validateStatus) {
|
||||
config.validateStatus = () => true;
|
||||
}
|
||||
|
||||
return await axios.post(uri, qs.stringify(postData), config);
|
||||
} catch (ex) {
|
||||
Logger.log('Unexpected error making Azure auth request', 'azureCore.postRequest', JSON.stringify(ex?.response?.data, undefined, 2));
|
||||
throw ex;
|
||||
return this.getToken(tenant, resource, postData);
|
||||
}
|
||||
|
||||
return this.handleInteractionRequired(tenant, resource);
|
||||
}
|
||||
|
||||
protected async makeGetRequest(token: string, uri: string): Promise<AxiosResponse<any>> {
|
||||
try {
|
||||
const config = {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
public async getToken(tenant: Tenant, resource: Resource, postData: AuthorizationCodePostData | TokenPostData | RefreshTokenPostData): Promise<OAuthTokenResponse> {
|
||||
const tokenUrl = `${this.loginEndpointUrl}${tenant.id}/oauth2/token`;
|
||||
const response = await this.makePostRequest(tokenUrl, postData);
|
||||
|
||||
if (response.data.error === 'interaction_required') {
|
||||
return this.handleInteractionRequired(tenant, resource);
|
||||
}
|
||||
|
||||
if (response.data.error) {
|
||||
Logger.error('Response error!', response.data);
|
||||
throw new AzureAuthError(localize('azure.responseError', "Token retrival failed with an error. Open developer tools to view the error"), 'Token retrival failed', undefined);
|
||||
}
|
||||
|
||||
const accessTokenString = response.data.access_token;
|
||||
const refreshTokenString = response.data.refresh_token;
|
||||
const expiresOnString = response.data.expires_on;
|
||||
|
||||
return this.getTokenHelper(tenant, resource, accessTokenString, refreshTokenString, expiresOnString);
|
||||
}
|
||||
|
||||
public async getTokenHelper(tenant: Tenant, resource: Resource, accessTokenString: string, refreshTokenString: string, expiresOnString: string): Promise<OAuthTokenResponse> {
|
||||
if (!accessTokenString) {
|
||||
const msg = localize('azure.accessTokenEmpty', 'No access token returned from Microsoft OAuth');
|
||||
throw new AzureAuthError(msg, 'Access token was empty', undefined);
|
||||
}
|
||||
|
||||
const tokenClaims: TokenClaims = this.getTokenClaims(accessTokenString);
|
||||
|
||||
const userKey = tokenClaims.sub ?? tokenClaims.oid;
|
||||
|
||||
if (!userKey) {
|
||||
const msg = localize('azure.noUniqueIdentifier', "The user had no unique identifier within AAD");
|
||||
throw new AzureAuthError(msg, 'No unique identifier', undefined);
|
||||
}
|
||||
|
||||
const accessToken: AccessToken = {
|
||||
token: accessTokenString,
|
||||
key: userKey
|
||||
};
|
||||
let refreshToken: RefreshToken;
|
||||
|
||||
if (refreshTokenString) {
|
||||
refreshToken = {
|
||||
token: refreshTokenString,
|
||||
key: userKey
|
||||
};
|
||||
|
||||
return await axios.get(uri, config);
|
||||
} catch (ex) {
|
||||
// Intercept and print error
|
||||
Logger.log('Unexpected error making Azure auth request', 'azureCore.getRequest', JSON.stringify(ex?.response?.data, undefined, 2));
|
||||
// rethrow error
|
||||
throw ex;
|
||||
}
|
||||
|
||||
const result: OAuthTokenResponse = {
|
||||
accessToken,
|
||||
refreshToken,
|
||||
tokenClaims,
|
||||
expiresOn: expiresOnString
|
||||
};
|
||||
|
||||
const accountKey: azdata.AccountKey = {
|
||||
providerId: this.metadata.id,
|
||||
accountId: userKey
|
||||
};
|
||||
|
||||
await this.saveToken(tenant, resource, accountKey, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected async getTenants(token: AccessToken): Promise<Tenant[]> {
|
||||
|
||||
|
||||
//#region tenant calls
|
||||
public async getTenants(token: AccessToken): Promise<Tenant[]> {
|
||||
interface TenantResponse { // https://docs.microsoft.com/en-us/rest/api/resources/tenants/list
|
||||
id: string
|
||||
tenantId: string
|
||||
@@ -329,7 +306,7 @@ export abstract class AzureAuth implements vscode.Disposable {
|
||||
|
||||
const tenantUri = url.resolve(this.metadata.settings.armResource.endpoint, 'tenants?api-version=2019-11-01');
|
||||
try {
|
||||
const tenantResponse = await this.makeGetRequest(token.token, tenantUri);
|
||||
const tenantResponse = await this.makeGetRequest(tenantUri, token.token);
|
||||
Logger.pii('getTenants', tenantResponse.data);
|
||||
const tenants: Tenant[] = tenantResponse.data.value.map((tenantInfo: TenantResponse) => {
|
||||
return {
|
||||
@@ -353,97 +330,88 @@ export abstract class AzureAuth implements vscode.Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
protected async getSubscriptions(account: AzureAccount): Promise<Subscription[]> {
|
||||
interface SubscriptionResponse { // https://docs.microsoft.com/en-us/rest/api/resources/subscriptions/list
|
||||
subscriptionId: string
|
||||
tenantId: string
|
||||
displayName: string
|
||||
}
|
||||
const allSubs: Subscription[] = [];
|
||||
const tokens = await this.getSecurityToken(account, azdata.AzureResource.ResourceManagement);
|
||||
if (!tokens) {
|
||||
Logger.log('There were no resource management tokens to retrieve subscriptions from. Account is stale.');
|
||||
account.isStale = true;
|
||||
}
|
||||
//#endregion
|
||||
|
||||
for (const tenant of account.properties.tenants) {
|
||||
const token = tokens[tenant.id];
|
||||
const subscriptionUri = url.resolve(this.metadata.settings.armResource.endpoint, 'subscriptions?api-version=2019-11-01');
|
||||
try {
|
||||
const subscriptionResponse = await this.makeGetRequest(token.token, subscriptionUri);
|
||||
Logger.pii('getSubscriptions', subscriptionResponse.data);
|
||||
const subscriptions: Subscription[] = subscriptionResponse.data.value.map((subscriptionInfo: SubscriptionResponse) => {
|
||||
return {
|
||||
id: subscriptionInfo.subscriptionId,
|
||||
displayName: subscriptionInfo.displayName,
|
||||
tenantId: subscriptionInfo.tenantId
|
||||
} as Subscription;
|
||||
});
|
||||
allSubs.push(...subscriptions);
|
||||
} catch (ex) {
|
||||
Logger.error(ex);
|
||||
throw new Error('Error retrieving subscription information');
|
||||
}
|
||||
//#region token management
|
||||
private async saveToken(tenant: Tenant, resource: Resource, accountKey: azdata.AccountKey, { accessToken, refreshToken, expiresOn }: OAuthTokenResponse) {
|
||||
const msg = localize('azure.cacheErrorAdd', "Error when adding your account to the cache.");
|
||||
if (!tenant.id || !resource.id) {
|
||||
Logger.pii('Tenant ID or resource ID was undefined', tenant, resource);
|
||||
throw new AzureAuthError(msg, 'Adding account to cache failed', undefined);
|
||||
}
|
||||
return allSubs;
|
||||
}
|
||||
|
||||
protected async getToken(postData: { [key: string]: string }, tenant = this.commonTenant, resourceId: string = '', resourceEndpoint: string = ''): Promise<TokenRefreshResponse | undefined> {
|
||||
try {
|
||||
let refreshResponse: TokenRefreshResponse;
|
||||
|
||||
try {
|
||||
const tokenUrl = `${this.loginEndpointUrl}${tenant.id}/oauth2/token`;
|
||||
const tokenResponse = await this.makePostRequest(tokenUrl, postData);
|
||||
Logger.pii(JSON.stringify(tokenResponse.data));
|
||||
const tokenClaims = this.getTokenClaims(tokenResponse.data.access_token);
|
||||
|
||||
const accessToken: AccessToken = {
|
||||
token: tokenResponse.data.access_token,
|
||||
key: tokenClaims.oid ?? tokenClaims.email ?? tokenClaims.unique_name ?? tokenClaims.name,
|
||||
};
|
||||
|
||||
const refreshToken: RefreshToken = {
|
||||
token: tokenResponse.data.refresh_token,
|
||||
key: accessToken.key
|
||||
};
|
||||
const expiresOn = tokenResponse.data.expires_on;
|
||||
|
||||
refreshResponse = { accessToken, refreshToken, tokenClaims, expiresOn };
|
||||
} catch (ex) {
|
||||
Logger.pii(JSON.stringify(ex?.response?.data));
|
||||
if (ex?.response?.data?.error === 'interaction_required') {
|
||||
const shouldOpenLink = await this.openConsentDialog(tenant, resourceId);
|
||||
if (shouldOpenLink === true) {
|
||||
const { tokenRefreshResponse, authCompleteDeferred } = await this.promptForConsent(resourceEndpoint, tenant.id);
|
||||
refreshResponse = tokenRefreshResponse;
|
||||
authCompleteDeferred.resolve();
|
||||
} else {
|
||||
vscode.window.showInformationMessage(localize('azure.noConsentToReauth', "The authentication failed since Azure Data Studio was unable to open re-authentication page."));
|
||||
}
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
this.memdb.set(this.createMemdbString(refreshResponse.accessToken.key, tenant.id, resourceId), refreshResponse.expiresOn);
|
||||
return refreshResponse;
|
||||
} catch (err) {
|
||||
const msg = localize('azure.noToken', "Retrieving the Azure token failed. Please sign in again.");
|
||||
vscode.window.showErrorMessage(msg);
|
||||
throw new Error(err);
|
||||
await this.tokenCache.saveCredential(`${accountKey.accountId}_access_${resource.id}_${tenant.id}`, JSON.stringify(accessToken));
|
||||
await this.tokenCache.saveCredential(`${accountKey.accountId}_refresh_${resource.id}_${tenant.id}`, JSON.stringify(refreshToken));
|
||||
this.memdb.set(`${accountKey.accountId}_${tenant.id}_${resource.id}`, expiresOn);
|
||||
} catch (ex) {
|
||||
Logger.error(ex);
|
||||
throw new AzureAuthError(msg, 'Adding account to cache failed', ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async openConsentDialog(tenant: Tenant, resourceId: string): Promise<boolean> {
|
||||
public async getSavedToken(tenant: Tenant, resource: Resource, accountKey: azdata.AccountKey): Promise<{ accessToken: AccessToken, refreshToken: RefreshToken, expiresOn: string }> {
|
||||
const getMsg = localize('azure.cacheErrorGet', "Error when getting your account from the cache");
|
||||
const parseMsg = localize('azure.cacheErrorParse', "Error when parsing your account from the cache");
|
||||
|
||||
if (!tenant.id || !resource.id) {
|
||||
Logger.pii('Tenant ID or resource ID was undefined', tenant, resource);
|
||||
throw new AzureAuthError(getMsg, 'Getting account from cache failed', undefined);
|
||||
}
|
||||
|
||||
let accessTokenString: string;
|
||||
let refreshTokenString: string;
|
||||
let expiresOn: string;
|
||||
try {
|
||||
accessTokenString = await this.tokenCache.getCredential(`${accountKey.accountId}_access_${resource.id}_${tenant.id}`);
|
||||
refreshTokenString = await this.tokenCache.getCredential(`${accountKey.accountId}_refresh_${resource.id}_${tenant.id}`);
|
||||
expiresOn = this.memdb.get(`${accountKey.accountId}_${tenant.id}_${resource.id}`);
|
||||
} catch (ex) {
|
||||
Logger.error(ex);
|
||||
throw new AzureAuthError(getMsg, 'Getting account from cache failed', ex);
|
||||
}
|
||||
|
||||
try {
|
||||
if (!accessTokenString) {
|
||||
return undefined;
|
||||
}
|
||||
const accessToken: AccessToken = JSON.parse(accessTokenString);
|
||||
let refreshToken: RefreshToken;
|
||||
if (refreshTokenString) {
|
||||
refreshToken = JSON.parse(refreshTokenString);
|
||||
}
|
||||
|
||||
return {
|
||||
accessToken, refreshToken, expiresOn
|
||||
};
|
||||
} catch (ex) {
|
||||
Logger.error(ex);
|
||||
throw new AzureAuthError(parseMsg, 'Parsing account from cache failed', ex);
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region interaction handling
|
||||
|
||||
public async handleInteractionRequired(tenant: Tenant, resource: Resource): Promise<OAuthTokenResponse | undefined> {
|
||||
const shouldOpen = await this.askUserForInteraction(tenant, resource);
|
||||
if (shouldOpen) {
|
||||
const result = await this.login(tenant, resource);
|
||||
result?.authComplete?.resolve();
|
||||
return result?.response;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asks the user if they would like to do the interaction based authentication as required by OAuth2
|
||||
* @param tenant
|
||||
* @param resource
|
||||
*/
|
||||
private async askUserForInteraction(tenant: Tenant, resource: Resource): Promise<boolean> {
|
||||
if (!tenant.displayName && !tenant.id) {
|
||||
throw new Error('Tenant did not have display name or id');
|
||||
}
|
||||
|
||||
if (tenant.id === 'common') {
|
||||
throw new Error('Common tenant should not need consent');
|
||||
}
|
||||
|
||||
const getTenantConfigurationSet = (): Set<string> => {
|
||||
const configuration = vscode.workspace.getConfiguration('azure.tenant.config');
|
||||
let values: string[] = configuration.get('filter') ?? [];
|
||||
@@ -486,7 +454,7 @@ export abstract class AzureAuth implements vscode.Disposable {
|
||||
}
|
||||
|
||||
};
|
||||
const messageBody = localize('azurecore.consentDialog.body', "Your tenant '{0} ({1})' requires you to re-authenticate again to access {2} resources. Press Open to start the authentication process.", tenant.displayName, tenant.id, resourceId);
|
||||
const messageBody = localize('azurecore.consentDialog.body', "Your tenant '{0} ({1})' requires you to re-authenticate again to access {2} resources. Press Open to start the authentication process.", tenant.displayName, tenant.id, resource.id);
|
||||
const result = await vscode.window.showInformationMessage(messageBody, { modal: true }, openItem, closeItem, dontAskAgainItem);
|
||||
|
||||
if (result.action) {
|
||||
@@ -495,113 +463,9 @@ export abstract class AzureAuth implements vscode.Disposable {
|
||||
|
||||
return result.booleanResult;
|
||||
}
|
||||
//#endregion
|
||||
|
||||
protected getTokenClaims(accessToken: string): TokenClaims | undefined {
|
||||
try {
|
||||
const split = accessToken.split('.');
|
||||
return JSON.parse(Buffer.from(split[1], 'base64').toString('binary'));
|
||||
} catch (ex) {
|
||||
throw new Error('Unable to read token claims: ' + JSON.stringify(ex));
|
||||
}
|
||||
}
|
||||
|
||||
private async refreshAccessToken(account: azdata.AccountKey, rt: RefreshToken, tenant: Tenant = this.commonTenant, resource?: Resource): Promise<TokenRefreshResponse> {
|
||||
const postData: { [key: string]: string } = {
|
||||
grant_type: 'refresh_token',
|
||||
refresh_token: rt.token,
|
||||
client_id: this.clientId,
|
||||
tenant: tenant.id,
|
||||
};
|
||||
|
||||
if (resource) {
|
||||
postData.resource = resource.endpoint;
|
||||
}
|
||||
|
||||
const getTokenResponse = await this.getToken(postData, tenant, resource?.id, resource?.endpoint);
|
||||
|
||||
const accessToken = getTokenResponse?.accessToken;
|
||||
const refreshToken = getTokenResponse?.refreshToken;
|
||||
|
||||
if (!accessToken || !refreshToken) {
|
||||
Logger.log('Access or refresh token were undefined');
|
||||
const msg = localize('azure.refreshTokenError', "Error when refreshing your account.");
|
||||
throw new Error(msg);
|
||||
}
|
||||
|
||||
await this.setCachedToken(account, accessToken, refreshToken, resource?.id, tenant?.id);
|
||||
|
||||
return getTokenResponse;
|
||||
}
|
||||
|
||||
|
||||
public async setCachedToken(account: azdata.AccountKey, accessToken: AccessToken, refreshToken: RefreshToken, resourceId?: string, tenantId?: string): Promise<void> {
|
||||
const msg = localize('azure.cacheErrorAdd', "Error when adding your account to the cache.");
|
||||
resourceId = resourceId ?? '';
|
||||
tenantId = tenantId ?? '';
|
||||
if (!accessToken || !accessToken.token || !refreshToken.token || !accessToken.key) {
|
||||
throw new Error(msg);
|
||||
}
|
||||
|
||||
try {
|
||||
await this.tokenCache.saveCredential(`${account.accountId}_access_${resourceId}_${tenantId}`, JSON.stringify(accessToken));
|
||||
await this.tokenCache.saveCredential(`${account.accountId}_refresh_${resourceId}_${tenantId}`, JSON.stringify(refreshToken));
|
||||
} catch (ex) {
|
||||
Logger.error('Error when storing tokens.', ex);
|
||||
throw new Error(msg);
|
||||
}
|
||||
}
|
||||
|
||||
public async getCachedToken(account: azdata.AccountKey, resourceId?: string, tenantId?: string): Promise<{ accessToken: AccessToken, refreshToken: RefreshToken } | undefined> {
|
||||
resourceId = resourceId ?? '';
|
||||
tenantId = tenantId ?? '';
|
||||
|
||||
let accessToken: AccessToken;
|
||||
let refreshToken: RefreshToken;
|
||||
try {
|
||||
accessToken = JSON.parse(await this.tokenCache.getCredential(`${account.accountId}_access_${resourceId}_${tenantId}`));
|
||||
refreshToken = JSON.parse(await this.tokenCache.getCredential(`${account.accountId}_refresh_${resourceId}_${tenantId}`));
|
||||
} catch (ex) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!accessToken || !refreshToken) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!refreshToken.token || !refreshToken.key) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!accessToken.token || !accessToken.key) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
accessToken,
|
||||
refreshToken
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
public createMemdbString(accountKey: string, tenantId: string, resourceId: string): string {
|
||||
return `${accountKey}_${tenantId}_${resourceId}`;
|
||||
}
|
||||
|
||||
public async deleteAccountCache(account: azdata.AccountKey): Promise<void> {
|
||||
const results = await this.tokenCache.findCredentials(account.accountId);
|
||||
|
||||
for (let { account } of results) {
|
||||
await this.tokenCache.clearCredential(account);
|
||||
}
|
||||
}
|
||||
|
||||
public async deleteAllCache(): Promise<void> {
|
||||
const results = await this.tokenCache.findCredentials('');
|
||||
|
||||
for (let { account } of results) {
|
||||
await this.tokenCache.clearCredential(account);
|
||||
}
|
||||
}
|
||||
//#region data modeling
|
||||
|
||||
public createAccount(tokenClaims: TokenClaims, key: string, tenants: Tenant[]): AzureAccount {
|
||||
// Determine if this is a microsoft account
|
||||
@@ -663,4 +527,184 @@ export abstract class AzureAuth implements vscode.Disposable {
|
||||
|
||||
return account;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region network functions
|
||||
public async makePostRequest(url: string, postData: AuthorizationCodePostData | TokenPostData | DeviceCodeStartPostData | DeviceCodeCheckPostData): Promise<AxiosResponse<any>> {
|
||||
const config: AxiosRequestConfig = {
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
validateStatus: () => true // Never throw
|
||||
};
|
||||
|
||||
// Intercept response and print out the response for future debugging
|
||||
const response = await axios.post(url, qs.stringify(postData), config);
|
||||
Logger.pii(url, postData, response.data);
|
||||
return response;
|
||||
}
|
||||
|
||||
private async makeGetRequest(url: string, token: string): Promise<AxiosResponse<any>> {
|
||||
const config: AxiosRequestConfig = {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`
|
||||
},
|
||||
validateStatus: () => true // Never throw
|
||||
};
|
||||
|
||||
const response = await axios.get(url, config);
|
||||
Logger.pii(url, response.data);
|
||||
return response;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region inconsequential
|
||||
protected getTokenClaims(accessToken: string): TokenClaims | undefined {
|
||||
try {
|
||||
const split = accessToken.split('.');
|
||||
return JSON.parse(Buffer.from(split[1], 'base64').toString('binary'));
|
||||
} catch (ex) {
|
||||
throw new Error('Unable to read token claims: ' + JSON.stringify(ex));
|
||||
}
|
||||
}
|
||||
|
||||
protected toBase64UrlEncoding(base64string: string): string {
|
||||
return base64string.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_'); // Need to use base64url encoding
|
||||
}
|
||||
|
||||
public async deleteAllCache(): Promise<void> {
|
||||
const results = await this.tokenCache.findCredentials('');
|
||||
|
||||
for (let { account } of results) {
|
||||
await this.tokenCache.clearCredential(account);
|
||||
}
|
||||
}
|
||||
|
||||
public async clearCredentials(account: azdata.AccountKey): Promise<void> {
|
||||
try {
|
||||
return this.deleteAccountCache(account);
|
||||
} catch (ex) {
|
||||
const msg = localize('azure.cacheErrrorRemove', "Error when removing your account from the cache.");
|
||||
vscode.window.showErrorMessage(msg);
|
||||
Logger.error('Error when removing tokens.', ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async deleteAccountCache(account: azdata.AccountKey): Promise<void> {
|
||||
const results = await this.tokenCache.findCredentials(account.accountId);
|
||||
|
||||
for (let { account } of results) {
|
||||
await this.tokenCache.clearCredential(account);
|
||||
}
|
||||
}
|
||||
|
||||
public async dispose() { }
|
||||
|
||||
public async autoOAuthCancelled(): Promise<void> { }
|
||||
|
||||
//#endregion
|
||||
}
|
||||
|
||||
//#region models
|
||||
|
||||
export interface AccountKey {
|
||||
/**
|
||||
* Account Key - uniquely identifies an account
|
||||
*/
|
||||
key: string
|
||||
}
|
||||
|
||||
export interface AccessToken extends AccountKey {
|
||||
/**
|
||||
* Access Token
|
||||
*/
|
||||
token: string;
|
||||
}
|
||||
|
||||
export interface RefreshToken extends AccountKey {
|
||||
/**
|
||||
* Refresh Token
|
||||
*/
|
||||
token: string;
|
||||
|
||||
/**
|
||||
* Account Key
|
||||
*/
|
||||
key: string
|
||||
}
|
||||
|
||||
export interface MultiTenantTokenResponse {
|
||||
[tenantId: string]: Token
|
||||
}
|
||||
|
||||
export interface Token extends AccountKey {
|
||||
/**
|
||||
* Access token
|
||||
*/
|
||||
token: string;
|
||||
|
||||
/**
|
||||
* TokenType
|
||||
*/
|
||||
tokenType: string;
|
||||
}
|
||||
|
||||
export interface TokenClaims { // https://docs.microsoft.com/en-us/azure/active-directory/develop/id-tokens
|
||||
aud: string;
|
||||
iss: string;
|
||||
iat: number;
|
||||
idp: string,
|
||||
nbf: number;
|
||||
exp: number;
|
||||
c_hash: string;
|
||||
at_hash: string;
|
||||
aio: string;
|
||||
preferred_username: string;
|
||||
email: string;
|
||||
name: string;
|
||||
nonce: string;
|
||||
oid: string;
|
||||
roles: string[];
|
||||
rh: string;
|
||||
sub: string;
|
||||
tid: string;
|
||||
unique_name: string;
|
||||
uti: string;
|
||||
ver: string;
|
||||
}
|
||||
|
||||
export type OAuthTokenResponse = { accessToken: AccessToken, refreshToken: RefreshToken, tokenClaims: TokenClaims, expiresOn: string };
|
||||
|
||||
export interface TokenPostData {
|
||||
grant_type: 'refresh_token' | 'authorization_code' | 'urn:ietf:params:oauth:grant-type:device_code';
|
||||
client_id: string;
|
||||
resource: string;
|
||||
}
|
||||
|
||||
export interface RefreshTokenPostData extends TokenPostData {
|
||||
grant_type: 'refresh_token';
|
||||
refresh_token: string;
|
||||
client_id: string;
|
||||
tenant: string
|
||||
}
|
||||
|
||||
export interface AuthorizationCodePostData extends TokenPostData {
|
||||
grant_type: 'authorization_code';
|
||||
code: string;
|
||||
code_verifier: string;
|
||||
redirect_uri: string;
|
||||
}
|
||||
|
||||
export interface DeviceCodeStartPostData extends Omit<TokenPostData, 'grant_type'> {
|
||||
|
||||
}
|
||||
|
||||
export interface DeviceCodeCheckPostData extends Omit<TokenPostData, 'resource'> {
|
||||
grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
|
||||
tenant: string,
|
||||
code: string
|
||||
}
|
||||
//#endregion
|
||||
|
||||
@@ -3,49 +3,37 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import { AuthorizationCodePostData, AzureAuth, OAuthTokenResponse } from './azureAuth';
|
||||
import { AzureAccountProviderMetadata, AzureAuthType, Deferred, Resource, Tenant } from '../interfaces';
|
||||
import * as vscode from 'vscode';
|
||||
import * as crypto from 'crypto';
|
||||
import { SimpleTokenCache } from '../simpleTokenCache';
|
||||
import { SimpleWebServer } from '../utils/simpleWebServer';
|
||||
import { AzureAuthError } from './azureAuthError';
|
||||
import { Logger } from '../../utils/Logger';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { promises as fs } from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as http from 'http';
|
||||
import * as qs from 'qs';
|
||||
import { promises as fs } from 'fs';
|
||||
|
||||
import {
|
||||
AzureAuth,
|
||||
AccessToken,
|
||||
RefreshToken,
|
||||
TokenClaims,
|
||||
TokenRefreshResponse,
|
||||
} from './azureAuth';
|
||||
|
||||
import {
|
||||
AzureAccountProviderMetadata,
|
||||
AzureAuthType,
|
||||
Deferred
|
||||
} from '../interfaces';
|
||||
|
||||
import { SimpleWebServer } from '../utils/simpleWebServer';
|
||||
import { SimpleTokenCache } from '../simpleTokenCache';
|
||||
import { Logger } from '../../utils/Logger';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
function parseQuery(uri: vscode.Uri) {
|
||||
return uri.query.split('&').reduce((prev: any, current) => {
|
||||
const queryString = current.split('=');
|
||||
prev[queryString[0]] = queryString[1];
|
||||
return prev;
|
||||
}, {});
|
||||
interface AuthCodeResponse {
|
||||
authCode: string;
|
||||
codeVerifier: string;
|
||||
redirectUri: string;
|
||||
}
|
||||
|
||||
interface AuthCodeResponse {
|
||||
authCode: string,
|
||||
codeVerifier: string
|
||||
interface CryptoValues {
|
||||
nonce: string;
|
||||
codeVerifier: string;
|
||||
codeChallenge: string;
|
||||
}
|
||||
|
||||
|
||||
export class AzureAuthCodeGrant extends AzureAuth {
|
||||
private static readonly USER_FRIENDLY_NAME: string = localize('azure.azureAuthCodeGrantName', "Azure Auth Code Grant");
|
||||
private static readonly USER_FRIENDLY_NAME: string = localize('azure.azureAuthCodeGrantName', 'Azure Auth Code Grant');
|
||||
private server: SimpleWebServer;
|
||||
|
||||
constructor(
|
||||
@@ -57,105 +45,52 @@ export class AzureAuthCodeGrant extends AzureAuth {
|
||||
super(metadata, tokenCache, context, uriEventEmitter, AzureAuthType.AuthCodeGrant, AzureAuthCodeGrant.USER_FRIENDLY_NAME);
|
||||
}
|
||||
|
||||
public async promptForConsent(resourceEndpoint: string, tenant: string = this.commonTenant.id): Promise<{ tokenRefreshResponse: TokenRefreshResponse, authCompleteDeferred: Deferred<void> } | undefined> {
|
||||
|
||||
protected async login(tenant: Tenant, resource: Resource): Promise<{ response: OAuthTokenResponse, authComplete: Deferred<void> }> {
|
||||
let authCompleteDeferred: Deferred<void>;
|
||||
let authCompletePromise = new Promise<void>((resolve, reject) => authCompleteDeferred = { resolve, reject });
|
||||
|
||||
let authResponse: AuthCodeResponse;
|
||||
|
||||
if (vscode.env.uiKind === vscode.UIKind.Web) {
|
||||
authResponse = await this.loginWithoutLocalServer(resourceEndpoint, tenant);
|
||||
authResponse = await this.loginWeb(tenant, resource);
|
||||
} else {
|
||||
authResponse = await this.loginWithLocalServer(authCompletePromise, resourceEndpoint, tenant);
|
||||
}
|
||||
|
||||
let tokenClaims: TokenClaims;
|
||||
let accessToken: AccessToken;
|
||||
let refreshToken: RefreshToken;
|
||||
let expiresOn: string;
|
||||
|
||||
try {
|
||||
const { accessToken: at, refreshToken: rt, tokenClaims: tc, expiresOn: eo } = await this.getTokenWithAuthCode(authResponse.authCode, authResponse.codeVerifier, this.redirectUri);
|
||||
tokenClaims = tc;
|
||||
accessToken = at;
|
||||
refreshToken = rt;
|
||||
expiresOn = eo;
|
||||
} catch (ex) {
|
||||
if (ex.msg) {
|
||||
vscode.window.showErrorMessage(ex.msg);
|
||||
}
|
||||
Logger.error(ex);
|
||||
}
|
||||
|
||||
if (!accessToken) {
|
||||
const msg = localize('azure.tokenFail', "Failure when retrieving tokens.");
|
||||
authCompleteDeferred.reject(new Error(msg));
|
||||
throw Error('Failure when retrieving tokens');
|
||||
authResponse = await this.loginDesktop(tenant, resource, authCompletePromise);
|
||||
}
|
||||
|
||||
return {
|
||||
tokenRefreshResponse: { accessToken, refreshToken, tokenClaims, expiresOn },
|
||||
authCompleteDeferred
|
||||
response: await this.getTokenWithAuthorizationCode(tenant, resource, authResponse),
|
||||
authComplete: authCompleteDeferred
|
||||
};
|
||||
}
|
||||
|
||||
public async autoOAuthCancelled(): Promise<void> {
|
||||
return this.server.shutdown();
|
||||
}
|
||||
|
||||
public async loginWithLocalServer(authCompletePromise: Promise<void>, resourceId: string, tenant: string = this.commonTenant.id): Promise<AuthCodeResponse | undefined> {
|
||||
this.server = new SimpleWebServer();
|
||||
const nonce = crypto.randomBytes(16).toString('base64');
|
||||
let serverPort: string;
|
||||
|
||||
try {
|
||||
serverPort = await this.server.startup();
|
||||
} catch (err) {
|
||||
const msg = localize('azure.serverCouldNotStart', 'Server could not start. This could be a permissions error or an incompatibility on your system. You can try enabling device code authentication from settings.');
|
||||
vscode.window.showErrorMessage(msg);
|
||||
Logger.error(JSON.stringify(err));
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// The login code to use
|
||||
let loginUrl: string;
|
||||
let codeVerifier: string;
|
||||
{
|
||||
codeVerifier = this.toBase64UrlEncoding(crypto.randomBytes(32).toString('base64'));
|
||||
const state = `${serverPort},${encodeURIComponent(nonce)}`;
|
||||
const codeChallenge = this.toBase64UrlEncoding(crypto.createHash('sha256').update(codeVerifier).digest('base64'));
|
||||
const loginQuery = {
|
||||
response_type: 'code',
|
||||
response_mode: 'query',
|
||||
client_id: this.clientId,
|
||||
redirect_uri: this.redirectUri,
|
||||
state,
|
||||
prompt: 'select_account',
|
||||
code_challenge_method: 'S256',
|
||||
code_challenge: codeChallenge,
|
||||
resource: resourceId
|
||||
};
|
||||
loginUrl = `${this.loginEndpointUrl}${tenant}/oauth2/authorize?${qs.stringify(loginQuery)}`;
|
||||
}
|
||||
|
||||
await vscode.env.openExternal(vscode.Uri.parse(`http://localhost:${serverPort}/signin?nonce=${encodeURIComponent(nonce)}`));
|
||||
|
||||
const authCode = await this.addServerListeners(this.server, nonce, loginUrl, authCompletePromise);
|
||||
|
||||
return {
|
||||
authCode,
|
||||
codeVerifier
|
||||
/**
|
||||
* Requests an OAuthTokenResponse from Microsoft OAuth
|
||||
*
|
||||
* @param tenant
|
||||
* @param resource
|
||||
* @param authCode
|
||||
* @param redirectUri
|
||||
* @param codeVerifier
|
||||
*/
|
||||
private async getTokenWithAuthorizationCode(tenant: Tenant, resource: Resource, { authCode, redirectUri, codeVerifier }: AuthCodeResponse): Promise<OAuthTokenResponse | undefined> {
|
||||
const postData: AuthorizationCodePostData = {
|
||||
grant_type: 'authorization_code',
|
||||
code: authCode,
|
||||
client_id: this.clientId,
|
||||
code_verifier: codeVerifier,
|
||||
redirect_uri: redirectUri,
|
||||
resource: resource.endpoint
|
||||
};
|
||||
|
||||
return this.getToken(tenant, resource, postData);
|
||||
}
|
||||
|
||||
public async loginWithoutLocalServer(resourceId: string, tenant: string = this.commonTenant.id): Promise<AuthCodeResponse | undefined> {
|
||||
private async loginWeb(tenant: Tenant, resource: Resource): Promise<AuthCodeResponse> {
|
||||
const callbackUri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://microsoft.azurecore`));
|
||||
const nonce = crypto.randomBytes(16).toString('base64');
|
||||
const { nonce, codeVerifier, codeChallenge } = this.createCryptoValues();
|
||||
const port = (callbackUri.authority.match(/:([0-9]*)$/) || [])[1] || (callbackUri.scheme === 'https' ? 443 : 80);
|
||||
const state = `${port},${encodeURIComponent(nonce)},${encodeURIComponent(callbackUri.query)}`;
|
||||
|
||||
const codeVerifier = this.toBase64UrlEncoding(crypto.randomBytes(32).toString('base64'));
|
||||
const codeChallenge = this.toBase64UrlEncoding(crypto.createHash('sha256').update(codeVerifier).digest('base64'));
|
||||
|
||||
const loginQuery = {
|
||||
response_type: 'code',
|
||||
response_mode: 'query',
|
||||
@@ -165,26 +100,27 @@ export class AzureAuthCodeGrant extends AzureAuth {
|
||||
prompt: 'select_account',
|
||||
code_challenge_method: 'S256',
|
||||
code_challenge: codeChallenge,
|
||||
resource: resourceId
|
||||
resource: resource.id
|
||||
};
|
||||
|
||||
const signInUrl = `${this.loginEndpointUrl}${tenant}/oauth2/authorize?${qs.stringify(loginQuery)}`;
|
||||
await vscode.env.openExternal(vscode.Uri.parse(signInUrl));
|
||||
|
||||
const authCode = await this.handleCodeResponse(state);
|
||||
const authCode = await this.handleWebResponse(state);
|
||||
|
||||
return {
|
||||
authCode,
|
||||
codeVerifier
|
||||
codeVerifier,
|
||||
redirectUri: this.redirectUri
|
||||
};
|
||||
}
|
||||
|
||||
public async handleCodeResponse(state: string): Promise<string> {
|
||||
private async handleWebResponse(state: string): Promise<string> {
|
||||
let uriEventListener: vscode.Disposable;
|
||||
return new Promise((resolve: (value: any) => void, reject) => {
|
||||
uriEventListener = this.uriEventEmitter.event(async (uri: vscode.Uri) => {
|
||||
try {
|
||||
const query = parseQuery(uri);
|
||||
const query = this.parseQuery(uri);
|
||||
const code = query.code;
|
||||
if (query.state !== state && decodeURIComponent(query.state) !== state) {
|
||||
reject(new Error('State mismatch'));
|
||||
@@ -200,33 +136,46 @@ export class AzureAuthCodeGrant extends AzureAuth {
|
||||
});
|
||||
}
|
||||
|
||||
public async login(): Promise<azdata.Account | azdata.PromptFailedResult> {
|
||||
const { tokenRefreshResponse, authCompleteDeferred } = await this.promptForConsent(this.metadata.settings.signInResourceId);
|
||||
const { accessToken, refreshToken, tokenClaims } = tokenRefreshResponse;
|
||||
private parseQuery(uri: vscode.Uri): { [key: string]: string } {
|
||||
return uri.query.split('&').reduce((prev: any, current) => {
|
||||
const queryString = current.split('=');
|
||||
prev[queryString[0]] = queryString[1];
|
||||
return prev;
|
||||
}, {});
|
||||
}
|
||||
|
||||
const tenants = await this.getTenants(accessToken);
|
||||
private async loginDesktop(tenant: Tenant, resource: Resource, authCompletePromise: Promise<void>): Promise<AuthCodeResponse> {
|
||||
this.server = new SimpleWebServer();
|
||||
let serverPort: string;
|
||||
|
||||
try {
|
||||
await this.setCachedToken({ accountId: accessToken.key, providerId: this.metadata.id }, accessToken, refreshToken);
|
||||
serverPort = await this.server.startup();
|
||||
} catch (ex) {
|
||||
Logger.error(ex);
|
||||
if (ex.msg) {
|
||||
vscode.window.showErrorMessage(ex.msg);
|
||||
authCompleteDeferred.reject(ex);
|
||||
} else {
|
||||
authCompleteDeferred.reject(new Error('There was an issue when storing the cache.'));
|
||||
}
|
||||
|
||||
return { canceled: false } as azdata.PromptFailedResult;
|
||||
const msg = localize('azure.serverCouldNotStart', 'Server could not start. This could be a permissions error or an incompatibility on your system. You can try enabling device code authentication from settings.');
|
||||
throw new AzureAuthError(msg, 'Server could not start', ex);
|
||||
}
|
||||
const { nonce, codeVerifier, codeChallenge } = this.createCryptoValues();
|
||||
const state = `${serverPort},${encodeURIComponent(nonce)}`;
|
||||
const loginQuery = {
|
||||
response_type: 'code',
|
||||
response_mode: 'query',
|
||||
client_id: this.clientId,
|
||||
redirect_uri: this.redirectUri,
|
||||
state,
|
||||
prompt: 'select_account',
|
||||
code_challenge_method: 'S256',
|
||||
code_challenge: codeChallenge,
|
||||
resource: resource.endpoint
|
||||
};
|
||||
const loginUrl = `${this.loginEndpointUrl}${tenant.id}/oauth2/authorize?${qs.stringify(loginQuery)}`;
|
||||
await vscode.env.openExternal(vscode.Uri.parse(`http://localhost:${serverPort}/signin?nonce=${encodeURIComponent(nonce)}`));
|
||||
const authCode = await this.addServerListeners(this.server, nonce, loginUrl, authCompletePromise);
|
||||
return {
|
||||
authCode,
|
||||
codeVerifier,
|
||||
redirectUri: this.redirectUri
|
||||
};
|
||||
|
||||
const account = this.createAccount(tokenClaims, accessToken.key, tenants);
|
||||
|
||||
const subscriptions = await this.getSubscriptions(account);
|
||||
account.properties.subscriptions = subscriptions;
|
||||
|
||||
authCompleteDeferred.resolve();
|
||||
return account;
|
||||
}
|
||||
|
||||
private async addServerListeners(server: SimpleWebServer, nonce: string, loginUrl: string, authComplete: Promise<void>): Promise<string> {
|
||||
@@ -266,7 +215,7 @@ export class AzureAuthCodeGrant extends AzureAuth {
|
||||
|
||||
if (receivedNonce !== nonce) {
|
||||
res.writeHead(400, { 'content-type': 'text/html' });
|
||||
res.write(localize('azureAuth.nonceError', "Authentication failed due to a nonce mismatch, please close Azure Data Studio and try again."));
|
||||
res.write(localize('azureAuth.nonceError', 'Authentication failed due to a nonce mismatch, please close Azure Data Studio and try again.'));
|
||||
res.end();
|
||||
Logger.error('nonce no match', receivedNonce, nonce);
|
||||
return;
|
||||
@@ -283,7 +232,7 @@ export class AzureAuthCodeGrant extends AzureAuth {
|
||||
const stateSplit = state.split(',');
|
||||
if (stateSplit.length !== 2) {
|
||||
res.writeHead(400, { 'content-type': 'text/html' });
|
||||
res.write(localize('azureAuth.stateError', "Authentication failed due to a state mismatch, please close ADS and try again."));
|
||||
res.write(localize('azureAuth.stateError', 'Authentication failed due to a state mismatch, please close ADS and try again.'));
|
||||
res.end();
|
||||
reject(new Error('State mismatch'));
|
||||
return;
|
||||
@@ -291,7 +240,7 @@ export class AzureAuthCodeGrant extends AzureAuth {
|
||||
|
||||
if (stateSplit[1] !== encodeURIComponent(nonce)) {
|
||||
res.writeHead(400, { 'content-type': 'text/html' });
|
||||
res.write(localize('azureAuth.nonceError', "Authentication failed due to a nonce mismatch, please close Azure Data Studio and try again."));
|
||||
res.write(localize('azureAuth.nonceError', 'Authentication failed due to a nonce mismatch, please close Azure Data Studio and try again.'));
|
||||
res.end();
|
||||
reject(new Error('Nonce mismatch'));
|
||||
return;
|
||||
@@ -310,20 +259,14 @@ export class AzureAuthCodeGrant extends AzureAuth {
|
||||
});
|
||||
}
|
||||
|
||||
private async getTokenWithAuthCode(authCode: string, codeVerifier: string, redirectUri: string): Promise<TokenRefreshResponse | undefined> {
|
||||
const postData = {
|
||||
grant_type: 'authorization_code',
|
||||
code: authCode,
|
||||
client_id: this.clientId,
|
||||
code_verifier: codeVerifier,
|
||||
redirect_uri: redirectUri,
|
||||
resource: this.metadata.settings.signInResourceId
|
||||
|
||||
private createCryptoValues(): CryptoValues {
|
||||
const nonce = crypto.randomBytes(16).toString('base64');
|
||||
const codeVerifier = this.toBase64UrlEncoding(crypto.randomBytes(32).toString('base64'));
|
||||
const codeChallenge = this.toBase64UrlEncoding(crypto.createHash('sha256').update(codeVerifier).digest('base64'));
|
||||
|
||||
return {
|
||||
nonce, codeVerifier, codeChallenge
|
||||
};
|
||||
|
||||
return this.getToken(postData);
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this.server?.shutdown().catch(console.error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export class AzureAuthError extends Error {
|
||||
private readonly _originalMessage: string;
|
||||
|
||||
constructor(localizedMessage: string, _originalMessage: string, private readonly originalException: any) {
|
||||
super(localizedMessage);
|
||||
|
||||
}
|
||||
|
||||
get originalMessage(): string {
|
||||
return this._originalMessage;
|
||||
}
|
||||
|
||||
getPrintableString(): string {
|
||||
return JSON.stringify({
|
||||
originalMessage: this.originalMessage,
|
||||
originalException: this.originalException
|
||||
}, undefined, 2);
|
||||
}
|
||||
}
|
||||
@@ -8,21 +8,24 @@ import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import {
|
||||
AzureAuth,
|
||||
TokenClaims,
|
||||
AccessToken,
|
||||
RefreshToken,
|
||||
OAuthTokenResponse,
|
||||
DeviceCodeStartPostData,
|
||||
DeviceCodeCheckPostData,
|
||||
|
||||
} from './azureAuth';
|
||||
|
||||
import {
|
||||
AzureAccountProviderMetadata,
|
||||
AzureAccount,
|
||||
AzureAuthType,
|
||||
Tenant,
|
||||
Resource,
|
||||
Deferred,
|
||||
// Tenant,
|
||||
// Subscription
|
||||
} from '../interfaces';
|
||||
|
||||
import { SimpleTokenCache } from '../simpleTokenCache';
|
||||
import { Logger } from '../../utils/Logger';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
interface DeviceCodeLogin { // https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-device-code
|
||||
@@ -43,7 +46,6 @@ interface DeviceCodeLoginResult {
|
||||
}
|
||||
|
||||
export class AzureDeviceCode extends AzureAuth {
|
||||
|
||||
private static readonly USER_FRIENDLY_NAME: string = localize('azure.azureDeviceCodeAuth', "Azure Device Code");
|
||||
private readonly pageTitle: string;
|
||||
constructor(
|
||||
@@ -56,60 +58,42 @@ export class AzureDeviceCode extends AzureAuth {
|
||||
this.pageTitle = localize('addAccount', "Add {0} account", this.metadata.displayName);
|
||||
|
||||
}
|
||||
protected async login(tenant: Tenant, resource: Resource): Promise<{ response: OAuthTokenResponse, authComplete: Deferred<void> }> {
|
||||
let authCompleteDeferred: Deferred<void>;
|
||||
let authCompletePromise = new Promise<void>((resolve, reject) => authCompleteDeferred = { resolve, reject });
|
||||
|
||||
public async promptForConsent(resourceId: string, tenant: string = this.commonTenant.id): Promise<undefined> {
|
||||
vscode.window.showErrorMessage(localize('azure.deviceCodeDoesNotSupportConsent', "Device code authentication does not support prompting for consent. Switch the authentication method in settings to code grant."));
|
||||
return undefined;
|
||||
const uri = `${this.loginEndpointUrl}/${this.commonTenant.id}/oauth2/devicecode`;
|
||||
const postData: DeviceCodeStartPostData = {
|
||||
client_id: this.clientId,
|
||||
resource: resource.endpoint
|
||||
};
|
||||
|
||||
const postResult = await this.makePostRequest(uri, postData);
|
||||
|
||||
const initialDeviceLogin: DeviceCodeLogin = postResult.data;
|
||||
|
||||
await azdata.accounts.beginAutoOAuthDeviceCode(this.metadata.id, this.pageTitle, initialDeviceLogin.message, initialDeviceLogin.user_code, initialDeviceLogin.verification_url);
|
||||
|
||||
const finalDeviceLogin = await this.setupPolling(initialDeviceLogin);
|
||||
|
||||
const accessTokenString = finalDeviceLogin.access_token;
|
||||
const refreshTokenString = finalDeviceLogin.refresh_token;
|
||||
|
||||
const currentTime = new Date().getTime() / 1000;
|
||||
const expiresOn = `${currentTime + finalDeviceLogin.expires_in}`;
|
||||
|
||||
const result = await this.getTokenHelper(tenant, resource, accessTokenString, refreshTokenString, expiresOn);
|
||||
this.closeOnceComplete(authCompletePromise).catch(Logger.error);
|
||||
|
||||
return {
|
||||
response: result,
|
||||
authComplete: authCompleteDeferred
|
||||
};
|
||||
}
|
||||
|
||||
public async login(): Promise<AzureAccount | azdata.PromptFailedResult> {
|
||||
try {
|
||||
const uri = `${this.loginEndpointUrl}/${this.commonTenant}/oauth2/devicecode`;
|
||||
const postResult = await this.makePostRequest(uri, {
|
||||
client_id: this.clientId,
|
||||
resource: this.metadata.settings.signInResourceId
|
||||
});
|
||||
|
||||
const initialDeviceLogin: DeviceCodeLogin = postResult.data;
|
||||
|
||||
await azdata.accounts.beginAutoOAuthDeviceCode(this.metadata.id, this.pageTitle, initialDeviceLogin.message, initialDeviceLogin.user_code, initialDeviceLogin.verification_url);
|
||||
|
||||
const finalDeviceLogin = await this.setupPolling(initialDeviceLogin);
|
||||
|
||||
let tokenClaims: TokenClaims;
|
||||
let accessToken: AccessToken;
|
||||
let refreshToken: RefreshToken;
|
||||
// let tenants: Tenant[];
|
||||
// let subscriptions: Subscription[];
|
||||
|
||||
tokenClaims = this.getTokenClaims(finalDeviceLogin.access_token);
|
||||
|
||||
accessToken = {
|
||||
token: finalDeviceLogin.access_token,
|
||||
key: tokenClaims.email || tokenClaims.unique_name || tokenClaims.name,
|
||||
};
|
||||
|
||||
refreshToken = {
|
||||
token: finalDeviceLogin.refresh_token,
|
||||
key: accessToken.key,
|
||||
};
|
||||
|
||||
await this.setCachedToken({ accountId: accessToken.key, providerId: this.metadata.id }, accessToken, refreshToken);
|
||||
|
||||
const tenants = await this.getTenants(accessToken);
|
||||
const account = this.createAccount(tokenClaims, accessToken.key, tenants);
|
||||
const subscriptions = await this.getSubscriptions(account);
|
||||
account.properties.subscriptions = subscriptions;
|
||||
return account;
|
||||
} catch (ex) {
|
||||
console.log(ex);
|
||||
if (ex.msg) {
|
||||
vscode.window.showErrorMessage(ex.msg);
|
||||
}
|
||||
return { canceled: false };
|
||||
} finally {
|
||||
azdata.accounts.endAutoOAuthDeviceCode();
|
||||
}
|
||||
private async closeOnceComplete(promise: Promise<void>): Promise<void> {
|
||||
await promise;
|
||||
azdata.accounts.endAutoOAuthDeviceCode();
|
||||
}
|
||||
|
||||
|
||||
@@ -141,14 +125,14 @@ export class AzureDeviceCode extends AzureAuth {
|
||||
const msg = localize('azure.deviceCodeCheckFail', "Error encountered when trying to check for login results");
|
||||
try {
|
||||
const uri = `${this.loginEndpointUrl}/${this.commonTenant}/oauth2/token`;
|
||||
const postData = {
|
||||
const postData: DeviceCodeCheckPostData = {
|
||||
grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
|
||||
client_id: this.clientId,
|
||||
tenant: this.commonTenant.id,
|
||||
code: info.device_code
|
||||
};
|
||||
|
||||
const postResult = await this.makePostRequest(uri, postData, true);
|
||||
const postResult = await this.makePostRequest(uri, postData);
|
||||
|
||||
const result: DeviceCodeLoginResult = postResult.data;
|
||||
|
||||
@@ -164,5 +148,4 @@ export class AzureDeviceCode extends AzureAuth {
|
||||
public async autoOAuthCancelled(): Promise<void> {
|
||||
return azdata.accounts.endAutoOAuthDeviceCode();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,14 +10,15 @@ import * as nls from 'vscode-nls';
|
||||
import {
|
||||
AzureAccountProviderMetadata,
|
||||
AzureAuthType,
|
||||
Deferred
|
||||
Deferred,
|
||||
AzureAccount
|
||||
} from './interfaces';
|
||||
|
||||
import { SimpleTokenCache } from './simpleTokenCache';
|
||||
import { AzureAuth, TokenResponse } from './auths/azureAuth';
|
||||
import { Logger } from '../utils/Logger';
|
||||
import { MultiTenantTokenResponse, Token, AzureAuth } from './auths/azureAuth';
|
||||
import { AzureAuthCodeGrant } from './auths/azureAuthCodeGrant';
|
||||
import { AzureDeviceCode } from './auths/azureDeviceCode';
|
||||
import { Logger } from '../utils/Logger';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
@@ -101,14 +102,29 @@ export class AzureAccountProvider implements azdata.AccountProvider, vscode.Disp
|
||||
}
|
||||
|
||||
|
||||
getSecurityToken(account: azdata.Account, resource: azdata.AzureResource): Thenable<TokenResponse | undefined> {
|
||||
getSecurityToken(account: azdata.Account, resource: azdata.AzureResource): Thenable<MultiTenantTokenResponse | undefined> {
|
||||
return this._getSecurityToken(account, resource);
|
||||
}
|
||||
|
||||
private async _getSecurityToken(account: azdata.Account, resource: azdata.AzureResource): Promise<TokenResponse | undefined> {
|
||||
getAccountSecurityToken(account: azdata.Account, tenant: string, resource: azdata.AzureResource): Thenable<Token | undefined> {
|
||||
return this._getAccountSecurityToken(account, tenant, resource);
|
||||
}
|
||||
|
||||
private async _getAccountSecurityToken(account: azdata.Account, tenant: string, resource: azdata.AzureResource): Promise<Token | undefined> {
|
||||
await this.initCompletePromise;
|
||||
const azureAuth = this.getAuthMethod(undefined);
|
||||
return azureAuth?.getSecurityToken(account, resource);
|
||||
return azureAuth?.getAccountSecurityToken(account, tenant, resource);
|
||||
}
|
||||
|
||||
private async _getSecurityToken(account: azdata.Account, resource: azdata.AzureResource): Promise<MultiTenantTokenResponse | undefined> {
|
||||
vscode.window.showInformationMessage(localize('azure.deprecatedGetSecurityToken', "A call was made to azdata.accounts.getSecurityToken, this method is deprecated and will be removed in future releases. Please use getAccountSecurityToken instead."));
|
||||
const azureAccount = account as AzureAccount;
|
||||
const response: MultiTenantTokenResponse = {};
|
||||
for (const tenant of azureAccount.properties.tenants) {
|
||||
response[tenant.id] = await this._getAccountSecurityToken(account, tenant.id, resource);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
prompt(): Thenable<azdata.Account | azdata.PromptFailedResult> {
|
||||
@@ -134,7 +150,7 @@ export class AzureAccountProvider implements azdata.AccountProvider, vscode.Disp
|
||||
}
|
||||
|
||||
if (this.authMappings.size === 1) {
|
||||
return this.getAuthMethod(undefined).login();
|
||||
return this.getAuthMethod(undefined).startLogin();
|
||||
}
|
||||
|
||||
const options: Option[] = [];
|
||||
@@ -150,7 +166,7 @@ export class AzureAccountProvider implements azdata.AccountProvider, vscode.Disp
|
||||
return { canceled: true };
|
||||
}
|
||||
|
||||
return pick.azureAuth.login();
|
||||
return pick.azureAuth.startLogin();
|
||||
}
|
||||
|
||||
refresh(account: azdata.Account): Thenable<azdata.Account | azdata.PromptFailedResult> {
|
||||
|
||||
@@ -64,11 +64,6 @@ interface Settings {
|
||||
*/
|
||||
clientId?: string;
|
||||
|
||||
/**
|
||||
* Identifier of the resource to request when signing in
|
||||
*/
|
||||
signInResourceId?: string;
|
||||
|
||||
/**
|
||||
* Information that describes the Microsoft resource management resource
|
||||
*/
|
||||
@@ -177,10 +172,6 @@ interface AzureAccountProperties {
|
||||
*/
|
||||
tenants: Tenant[];
|
||||
|
||||
/**
|
||||
* A list of subscriptions the user belongs to
|
||||
*/
|
||||
subscriptions?: Subscription[];
|
||||
}
|
||||
|
||||
export interface Subscription {
|
||||
|
||||
@@ -17,7 +17,6 @@ const publicAzureSettings: ProviderSettings = {
|
||||
settings: {
|
||||
host: 'https://login.microsoftonline.com/',
|
||||
clientId: 'a69788c6-1d43-44ed-9ca3-b83e194da255',
|
||||
signInResourceId: 'https://management.core.windows.net/',
|
||||
microsoftResource: {
|
||||
id: 'marm',
|
||||
endpoint: 'https://management.core.windows.net/',
|
||||
@@ -72,7 +71,6 @@ const usGovAzureSettings: ProviderSettings = {
|
||||
settings: {
|
||||
host: 'https://login.microsoftonline.us/',
|
||||
clientId: 'a69788c6-1d43-44ed-9ca3-b83e194da255',
|
||||
signInResourceId: 'https://management.core.usgovcloudapi.net/',
|
||||
microsoftResource: {
|
||||
id: 'marm',
|
||||
endpoint: 'https://management.core.usgovcloudapi.net/',
|
||||
@@ -121,7 +119,6 @@ const usNatAzureSettings: ProviderSettings = {
|
||||
settings: {
|
||||
host: 'https://login.microsoftonline.eaglex.ic.gov/',
|
||||
clientId: 'a69788c6-1d43-44ed-9ca3-b83e194da255',
|
||||
signInResourceId: 'https://management.core.eaglex.ic.gov/',
|
||||
microsoftResource: {
|
||||
id: 'marm',
|
||||
endpoint: 'https://management.azure.eaglex.ic.gov/',
|
||||
@@ -171,7 +168,6 @@ const germanyAzureSettings: ProviderSettings = {
|
||||
settings: {
|
||||
host: 'https://login.microsoftazure.de/',
|
||||
clientId: 'a69788c6-1d43-44ed-9ca3-b83e194da255',
|
||||
signInResourceId: 'https://management.core.cloudapi.de/',
|
||||
graphResource: {
|
||||
id: 'https://graph.cloudapi.de/',
|
||||
endpoint: 'https://graph.cloudapi.de'
|
||||
@@ -197,7 +193,6 @@ const chinaAzureSettings: ProviderSettings = {
|
||||
settings: {
|
||||
host: 'https://login.chinacloudapi.cn/',
|
||||
clientId: 'a69788c6-1d43-44ed-9ca3-b83e194da255',
|
||||
signInResourceId: 'https://management.core.chinacloudapi.cn/',
|
||||
graphResource: {
|
||||
id: 'https://graph.chinacloudapi.cn/',
|
||||
endpoint: 'https://graph.chinacloudapi.cn'
|
||||
|
||||
Reference in New Issue
Block a user