Skip to content

Commit

Permalink
node clientruntime updates (Azure#1244)
Browse files Browse the repository at this point in the history
* remove stale token from tokencache for sp auth as adal does not remove it.

* more improvements

* jshint fixes

* application token creds update

* update version

* auth updates to node azure clientruntime

* fix to make nodejs tests work for 6.x as well as 4.x
  • Loading branch information
amarzavery authored Jul 11, 2016
1 parent aaf8fd1 commit 7045fdb
Show file tree
Hide file tree
Showing 13 changed files with 533 additions and 466 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ function ApplicationTokenCredentials(clientId, domain, secret, options) {
options.tokenCache = new adal.MemoryCache();
}

if (options.tokenAudience && options.tokenAudience.toLowerCase() !== 'graph') {
throw new Error('Valid value for \'tokenAudience\' is \'graph\'.');
}

this.tokenAudience = options.tokenAudience;
this.environment = options.environment;
this.authorizationScheme = options.authorizationScheme;
this.tokenCache = options.tokenCache;
Expand All @@ -76,7 +81,9 @@ function _retrieveTokenFromCache(callback) {
//For service principal userId and clientId are the same thing. Since the token has _clientId property we shall
//retrieve token using it.
var self = this;
self.context.acquireToken(self.environment.activeDirectoryResourceId, null, self.clientId, function (err, result) {
var resource = self.environment.activeDirectoryResourceId;
if (self.tokenAudience && self.tokenAudience.toLowerCase() === 'graph') resource = self.environment.activeDirectoryGraphResourceId;
self.context.acquireToken(resource, null, self.clientId, function (err, result) {
if (err) {
//make sure to remove the stale token from the tokencache. ADAL gives the same error message "Entry not found in cache."
//for entry not being present in the cache and for accessToken being expired in the cache. We do not want the token cache
Expand Down Expand Up @@ -109,7 +116,9 @@ ApplicationTokenCredentials.prototype.getToken = function (callback) {
return callback(err);
} else {
//Some error occured in retrieving the token from cache. May be the cache was empty or the access token expired. Let's try again.
self.context.acquireTokenWithClientCredentials(self.environment.activeDirectoryResourceId, self.clientId, self.secret, function (err, tokenResponse) {
var resource = self.environment.activeDirectoryResourceId;
if (self.tokenAudience && self.tokenAudience.toLowerCase() === 'graph') resource = self.environment.activeDirectoryGraphResourceId;
self.context.acquireTokenWithClientCredentials(resource, self.clientId, self.secret, function (err, tokenResponse) {
if (err) {
return callback(new Error('Failed to acquire token for application with the provided secret. \n' + err));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ var AzureEnvironment = require('../azureEnvironment');
* @param {string} [options.username] The user name for account in the form: 'user@example.com'.
* @param {AzureEnvironment} [options.environment] The azure environment to authenticate with. Default environment is "Azure" popularly known as "Public Azure Cloud".
* @param {string} [options.domain] The domain or tenant id containing this application. Default value is 'common'
* @param {string} [options.tokenAudience] The audience for which the token is requested. Valid value is 'graph'. If tokenAudience is provided
* then domain should also be provided and its value should not be the default 'common' tenant. It must be a string (preferrably in a guid format).
* @param {string} [options.clientId] The active directory application client id.
* See {@link https://azure.microsoft.com/en-us/documentation/articles/active-directory-devquickstarts-dotnet/ Active Directory Quickstart for .Net}
* for an example.
Expand Down Expand Up @@ -55,7 +57,18 @@ function DeviceTokenCredentials(options) {
if (!options.tokenCache) {
options.tokenCache = new adal.MemoryCache();
}


if (options.tokenAudience) {
if (options.tokenAudience.toLowerCase() !== 'graph') {
throw new Error('Valid value for \'tokenAudience\' is \'graph\'.');
}
if (options.domain.toLowerCase() === 'common') {
throw new Error('If the tokenAudience is specified as \'graph\' then \'domain\' cannot be the default \'commmon\' tenant. ' +
'It must be the actual tenant (preferrably a string in a guid format).');
}
}

this.tokenAudience = options.tokenAudience;
this.username = options.username;
this.environment = options.environment;
this.domain = options.domain;
Expand All @@ -68,7 +81,9 @@ function DeviceTokenCredentials(options) {

DeviceTokenCredentials.prototype.retrieveTokenFromCache = function (callback) {
var self = this;
self.context.acquireToken(self.environment.activeDirectoryResourceId, self.username, self.clientId, function (err, result) {
var resource = self.environment.activeDirectoryResourceId;
if (self.tokenAudience && self.tokenAudience.toLowerCase() === 'graph') resource = self.environment.activeDirectoryGraphResourceId;
self.context.acquireToken(resource, self.username, self.clientId, function (err, result) {
if (err) return callback(err);
return callback(null, result.tokenType, result.accessToken);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ var AzureEnvironment = require('../azureEnvironment');
* @param {string} username The user name for the Organization Id account.
* @param {string} password The password for the Organization Id account.
* @param {object} [options] Object representing optional parameters.
* @param {string} [options.tokenAudience] The audience for which the token is requested. Valid value is 'graph'. If tokenAudience is provided
* then domain should also be provided its value should not be the default 'common' tenant. It must be a string (preferrably in a guid format).
* @param {AzureEnvironment} [options.environment] The azure environment to authenticate with.
* @param {string} [options.authorizationScheme] The authorization scheme. Default value is 'bearer'.
* @param {object} [options.tokenCache] The token cache. Default value is the MemoryCache object from adal.
Expand Down Expand Up @@ -55,7 +57,18 @@ function UserTokenCredentials(clientId, domain, username, password, options) {
if (!options.tokenCache) {
options.tokenCache = new adal.MemoryCache();
}


if (options.tokenAudience) {
if (options.tokenAudience.toLowerCase() !== 'graph') {
throw new Error('Valid value for \'tokenAudience\' is \'graph\'.');
}
if (domain.toLowerCase() === 'common') {
throw new Error('If the tokenAudience is specified as \'graph\' then \'domain\' cannot be the default \'commmon\' tenant. ' +
'It must be the actual tenant (preferrably a string in a guid format).');
}
}

this.tokenAudience = options.tokenAudience;
this.environment = options.environment;
this.authorizationScheme = options.authorizationScheme;
this.tokenCache = options.tokenCache;
Expand All @@ -68,7 +81,9 @@ function UserTokenCredentials(clientId, domain, username, password, options) {
}

function _retrieveTokenFromCache(callback) {
this.context.acquireToken(this.environment.activeDirectoryResourceId, this.username, this.clientId, function (err, result) {
var resource = this.environment.activeDirectoryResourceId;
if (this.tokenAudience && this.tokenAudience.toLowerCase() === 'graph') resource = this.environment.activeDirectoryGraphResourceId;
this.context.acquireToken(resource, this.username, this.clientId, function (err, result) {
if (err) return callback(err);
return callback(null, result);
});
Expand All @@ -86,8 +101,9 @@ UserTokenCredentials.prototype.getToken = function (callback) {
_retrieveTokenFromCache.call(this, function (err, result) {
if (err) {
//Some error occured in retrieving the token from cache. May be the cache was empty. Let's try again.
self.context.acquireTokenWithUsernamePassword(self.environment.activeDirectoryResourceId, self.username,
self.password, self.clientId, function (err, tokenResponse) {
var resource = self.environment.activeDirectoryResourceId;
if (self.tokenAudience && self.tokenAudience.toLowerCase() === 'graph') resource = self.environment.activeDirectoryGraphResourceId;
self.context.acquireTokenWithUsernamePassword(resource, self.username, self.password, self.clientId, function (err, tokenResponse) {
if (err) {
return callback(new Error('Failed to acquire token for the user. \n' + err));
}
Expand Down
40 changes: 33 additions & 7 deletions src/client/NodeJS/ms-rest-azure/lib/login.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ function _createCredentials(parameters) {
options.tokenCache = this.tokenCache;
options.username = this.username;
options.authorizationScheme = this.authorizationScheme;
options.tokenAudience = this.tokenAudience;
if (parameters) {
if (parameters.domain) {
options.domain = parameters.domain;
Expand All @@ -33,6 +34,9 @@ function _createCredentials(parameters) {
if (parameters.tokenCache) {
options.tokenCache = parameters.tokenCache;
}
if (parameters.tokenAudience) {
options.tokenAudience = parameters.tokenAudience;
}
}
var credentials;
if (UserTokenCredentials.prototype.isPrototypeOf(this)) {
Expand Down Expand Up @@ -135,7 +139,10 @@ function _crossCheckUserNameWithToken(usernameFromMethodCall, userIdFromToken) {
* See {@link https://azure.microsoft.com/en-us/documentation/articles/active-directory-devquickstarts-dotnet/ Active Directory Quickstart for .Net}
* for an example.
*
* @param {string} [options.domain] The domain or tenant id containing this application. Default value is 'common'
* @param {string} [options.tokenAudience] The audience for which the token is requested. Valid value is 'graph'.If tokenAudience is provided
* then domain should also be provided its value should not be the default 'common' tenant. It must be a string (preferrably in a guid format).
*
* @param {string} [options.domain] The domain or tenant id containing this application. Default value is 'common'.
*
* @param {AzureEnvironment} [options.environment] The azure environment to authenticate with. Default environment is "Public Azure".
*
Expand Down Expand Up @@ -177,6 +184,7 @@ exports.interactive = function interactive(options, callback) {
options.language = azureConstants.DEFAULT_LANGUAGE;
}

this.tokenAudience = options.tokenAudience;
this.environment = options.environment;
this.domain = options.domain;
this.clientId = options.clientId;
Expand Down Expand Up @@ -213,7 +221,12 @@ exports.interactive = function interactive(options, callback) {
//to build the list of subscriptions across all tenants. So let's build both at the same time :).
function (tenants, callback) {
tenantList = tenants;
getSubscriptionsFromTenants.call(self, tenants, callback);
if (self.tokenAudience && self.tokenAudience.toLowerCase() === 'graph') {
// we dont need to get the subscriptionList if the tokenAudience is graph as graph clients are tenant based.
return callback(null, []);
} else {
return getSubscriptionsFromTenants.call(self, tenants, callback);
}
}
], function(err, subscriptions) {
if (err) return callback(err);
Expand All @@ -231,6 +244,8 @@ exports.interactive = function interactive(options, callback) {
* @param {string} [options.clientId] The active directory application client id.
* See {@link https://azure.microsoft.com/en-us/documentation/articles/active-directory-devquickstarts-dotnet/ Active Directory Quickstart for .Net}
* for an example.
* @param {string} [options.tokenAudience] The audience for which the token is requested. Valid value is 'graph'. If tokenAudience is provided
* then domain should also be provided and its value should not be the default 'common' tenant. It must be a string (preferrably in a guid format).
* @param {string} [options.domain] The domain or tenant id containing this application. Default value 'common'.
* @param {AzureEnvironment} [options.environment] The azure environment to authenticate with.
* @param {string} [options.authorizationScheme] The authorization scheme. Default value is 'bearer'.
Expand Down Expand Up @@ -272,7 +287,12 @@ exports.withUsernamePassword = function withUsernamePassword(username, password,
},
function (tenants, callback) {
tenantList = tenants;
getSubscriptionsFromTenants.call(creds, tenants, callback);
if (options.tokenAudience && options.tokenAudience.toLowerCase() === 'graph') {
// we dont need to get the subscriptionList if the tokenAudience is graph as graph clients are tenant based.
return callback(null, []);
} else {
return getSubscriptionsFromTenants.call(options, tenants, callback);
}
},
], function (err, subscriptions) {
return callback(null, creds, subscriptions);
Expand All @@ -289,6 +309,7 @@ exports.withUsernamePassword = function withUsernamePassword(username, password,
* @param {string} secret The application secret for the service principal.
* @param {string} domain The domain or tenant id containing this application.
* @param {object} [options] Object representing optional parameters.
* @param {string} [options.tokenAudience] The audience for which the token is requested. Valid value is 'graph'.
* @param {AzureEnvironment} [options.environment] The azure environment to authenticate with.
* @param {string} [options.authorizationScheme] The authorization scheme. Default value is 'bearer'.
* @param {object} [options.tokenCache] The token cache. Default value is the MemoryCache object from adal.
Expand All @@ -313,10 +334,15 @@ exports.withServicePrincipalSecret = function withServicePrincipalSecret(clientI
}
creds.getToken(function (err) {
if (err) return callback(err);
getSubscriptionsFromTenants.call(creds, [domain], function (err, subscriptions) {
if (err) return callback(err);
return callback(null, creds, subscriptions);
});
if (options.tokenAudience && options.tokenAudience.toLowerCase() === 'graph') {
// we dont need to get the subscriptionList if the tokenAudience is graph as graph clients are tenant based.
return callback(null, creds, []);
} else {
getSubscriptionsFromTenants.call(creds, [domain], function (err, subscriptions) {
if (err) return callback(err);
return callback(null, creds, subscriptions);
});
}
});
};

Expand Down
4 changes: 2 additions & 2 deletions src/client/NodeJS/ms-rest-azure/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"email": "azsdkteam@microsoft.com",
"url": "https://github.com/Azure/AutoRest"
},
"version": "1.14.4",
"version": "1.14.5",
"description": "Client Runtime for Node.js Azure client libraries generated using AutoRest",
"tags": [ "node", "microsoft", "autorest", "azure", "clientruntime" ],
"keywords": [ "node", "microsoft", "autorest", "azure", "clientruntime" ],
Expand All @@ -15,7 +15,7 @@
"async": "0.2.7",
"uuid": "2.0.1",
"adal-node": "^0.1.17",
"ms-rest": "^1.14.3",
"ms-rest": "^1.14.4",
"moment": "^2.6.0",
"azure-arm-resource": "^1.4.4-preview"
},
Expand Down
6 changes: 3 additions & 3 deletions src/client/NodeJS/ms-rest/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"email": "azsdkteam@microsoft.com",
"url": "https://github.com/Azure/AutoRest"
},
"version": "1.14.3",
"version": "1.14.4",
"description": "Client Runtime for Node.js client libraries generated using AutoRest",
"tags": ["node", "microsoft", "autorest", "clientruntime"],
"keywords": ["node", "microsoft", "autorest", "clientruntime"],
Expand All @@ -15,10 +15,10 @@
"dependencies": {
"underscore": "^1.4.0",
"tunnel": "~0.0.2",
"request": "2.69.0",
"request": "2.72.0",
"duplexer": "~0.1.1",
"through": "~2.3.4",
"moment": "^2.9.0"
"moment": "^2.14.1"
},
"devDependencies": {
"jshint": "2.6.3",
Expand Down
Loading

0 comments on commit 7045fdb

Please sign in to comment.