mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-31 01:25:38 -05:00
Add token expiration handling for AzureMFA auth (#16936)
* refresh azure account token if it's expired before sending query/list requests * fix several connection checks && add more logging * fix async UI glitch during token refreshing * cleanup * minor fix * add test for refreshAzureAccountTokenIfNecessary * address comments * comments * comments * comments * error handling
This commit is contained in:
@@ -120,7 +120,12 @@ suite('SQL ConnectionManagementService tests', () => {
|
||||
connectionStore.setup(x => x.addSavedPassword(TypeMoq.It.is<IConnectionProfile>(
|
||||
c => c.serverName === connectionProfileWithEmptyUnsavedPassword.serverName))).returns(
|
||||
() => Promise.resolve({ profile: connectionProfileWithEmptyUnsavedPassword, savedCred: false }));
|
||||
connectionStore.setup(x => x.isPasswordRequired(TypeMoq.It.isAny())).returns(() => true);
|
||||
connectionStore.setup(x => x.isPasswordRequired(TypeMoq.It.isAny())).returns((profile) => {
|
||||
if (profile.authenticationType === Constants.azureMFA) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
connectionStore.setup(x => x.getConnectionProfileGroups(false, undefined)).returns(() => [root]);
|
||||
connectionStore.setup(x => x.savePassword(TypeMoq.It.isAny())).returns(() => Promise.resolve(true));
|
||||
|
||||
@@ -1693,6 +1698,78 @@ suite('SQL ConnectionManagementService tests', () => {
|
||||
assert.strictEqual(profileWithCredentials.options['azureAccountToken'], testToken);
|
||||
});
|
||||
|
||||
test('refreshAzureAccountTokenIfNecessary refreshes Azure access token if existing token is expired', async () => {
|
||||
const uri: string = 'Editor Uri';
|
||||
// Set up a connection profile that uses Azure
|
||||
const azureConnectionProfile = ConnectionProfile.fromIConnectionProfile(capabilitiesService, connectionProfile);
|
||||
azureConnectionProfile.authenticationType = 'AzureMFA';
|
||||
const username = 'testuser@microsoft.com';
|
||||
azureConnectionProfile.azureAccount = username;
|
||||
const servername = 'test-database.database.windows.net';
|
||||
azureConnectionProfile.serverName = servername;
|
||||
const providerId = 'azure_PublicCloud';
|
||||
azureConnectionProfile.azureTenantId = 'testTenant';
|
||||
|
||||
const expiredToken = {
|
||||
token: 'expiredToken',
|
||||
tokenType: 'Bearer',
|
||||
expiresOn: 0,
|
||||
};
|
||||
|
||||
const freshToken = {
|
||||
token: 'freshToken',
|
||||
tokenType: 'Bearer',
|
||||
expiresOn: new Date().getTime() / 1000 + 7200,
|
||||
};
|
||||
|
||||
// every connectionStatusManager.connect will call accountManagementService.getAccountSecurityToken twice
|
||||
accountManagementService.setup(x => x.getAccountSecurityToken(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(expiredToken));
|
||||
accountManagementService.setup(x => x.getAccountSecurityToken(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(expiredToken));
|
||||
accountManagementService.setup(x => x.getAccountSecurityToken(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(freshToken));
|
||||
accountManagementService.setup(x => x.getAccountSecurityToken(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(freshToken));
|
||||
accountManagementService.setup(x => x.getAccountSecurityToken(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(expiredToken));
|
||||
accountManagementService.setup(x => x.getAccountSecurityToken(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(expiredToken));
|
||||
|
||||
accountManagementService.setup(x => x.getAccounts()).returns(() => {
|
||||
return Promise.resolve<azdata.Account[]>([
|
||||
{
|
||||
key: {
|
||||
accountId: username,
|
||||
providerId: providerId
|
||||
},
|
||||
displayInfo: undefined,
|
||||
isStale: false,
|
||||
properties: undefined
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
connectionStore.setup(x => x.addSavedPassword(TypeMoq.It.is(profile => profile.authenticationType === 'AzureMFA'))).returns(profile => Promise.resolve({
|
||||
profile: profile,
|
||||
savedCred: false
|
||||
}));
|
||||
|
||||
(connectionManagementService as any)._connectionStatusManager = connectionStatusManager;
|
||||
await connect(uri, undefined, false, azureConnectionProfile);
|
||||
|
||||
const oldProfile = connectionStatusManager.getConnectionProfile(uri);
|
||||
assert.strictEqual(oldProfile.options['expiresOn'], expiredToken.expiresOn);
|
||||
|
||||
const refreshRes1 = await connectionManagementService.refreshAzureAccountTokenIfNecessary(uri);
|
||||
assert.strictEqual(refreshRes1, true);
|
||||
|
||||
// first refresh should give us the new token
|
||||
const newProfile1 = connectionStatusManager.getConnectionProfile(uri);
|
||||
assert.strictEqual(newProfile1.options['expiresOn'], freshToken.expiresOn);
|
||||
|
||||
const refreshRes2 = await connectionManagementService.refreshAzureAccountTokenIfNecessary(uri);
|
||||
assert.strictEqual(refreshRes2, true);
|
||||
|
||||
// second refresh should be a no-op
|
||||
const newProfile2 = connectionStatusManager.getConnectionProfile(uri);
|
||||
assert.strictEqual(newProfile2.options['expiresOn'], freshToken.expiresOn);
|
||||
});
|
||||
|
||||
test('addSavedPassword fills in Azure access token for selected tenant', async () => {
|
||||
// Set up a connection profile that uses Azure
|
||||
let azureConnectionProfile = ConnectionProfile.fromIConnectionProfile(capabilitiesService, connectionProfile);
|
||||
|
||||
Reference in New Issue
Block a user