UNPKG

azure

Version:
173 lines (156 loc) 7.53 kB
// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. 'use strict'; const msrest = require('ms-rest'); const adal = require('adal-node'); const Constants = msrest.Constants; const AzureEnvironment = require('../azureEnvironment'); function _removeInvalidEntry(query, callback) { /* jshint validthis: true */ let self = this; self.tokenCache.find(query, function (err, entries) { if (err) return callback(err); if (entries && entries.length > 0) { return self.tokenCache.remove(entries, callback); } else { return callback(); } }); } 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. /* jshint validthis: true */ let self = this; let resource = self.environment.activeDirectoryResourceId; if (self.tokenAudience) { resource = self.tokenAudience; if (self.tokenAudience.toLowerCase() === 'graph') { resource = self.environment.activeDirectoryGraphResourceId; } else if (self.tokenAudience.toLowerCase() === 'batch') { resource = self.environment.batchResourceId; } } 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 //to contain the expired token hence we will search for it and delete it explicitly over here. self._removeInvalidEntry({ _clientId: self.clientId }, function (erronRemove) { if (erronRemove) { return callback(new Error('Error occurred while removing the expired token for service principal from token cache.\n' + erronRemove.message)); } else { return callback(err); } }); } else { return callback(null, result); } }); } /** * Creates a new ApplicationTokenCredentials object. * See {@link https://azure.microsoft.com/en-us/documentation/articles/active-directory-devquickstarts-dotnet/ Active Directory Quickstart for .Net} * for detailed instructions on creating an Azure Active Directory application. * @constructor * @param {string} clientId The active directory application client id. * @param {string} domain The domain or tenant id containing this application. * @param {string} secret The authentication secret for the application. * @param {object} [options] Object representing optional parameters. * @param {string} [options.tokenAudience] The audience for which the token is requested. Valid values are 'graph', 'batch' or any other resource like 'https://vault.azure.com/'. * If tokenAudience is 'graph' 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 {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. */ class ApplicationTokenCredentials { constructor(clientId, domain, secret, options) { if (!Boolean(clientId) || typeof clientId.valueOf() !== 'string') { throw new Error('clientId must be a non empty string.'); } if (!Boolean(domain) || typeof domain.valueOf() !== 'string') { throw new Error('domain must be a non empty string.'); } if (!Boolean(secret) || typeof secret.valueOf() !== 'string') { throw new Error('secret must be a non empty string.'); } if (!options) { options = {}; } if (!options.environment) { options.environment = AzureEnvironment.Azure; } if (!options.authorizationScheme) { options.authorizationScheme = Constants.HeaderConstants.AUTHORIZATION_SCHEME; } if (!options.tokenCache) { options.tokenCache = new adal.MemoryCache(); } if (options.tokenAudience && options.tokenAudience.toLowerCase() === 'graph' && 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; this.clientId = clientId; this.domain = domain; this.secret = secret; let authorityUrl = this.environment.activeDirectoryEndpointUrl + this.domain; this.context = new adal.AuthenticationContext(authorityUrl, this.environment.validateAuthority, this.tokenCache); this._removeInvalidEntry = _removeInvalidEntry; this._retrieveTokenFromCache = _retrieveTokenFromCache; } /** * Tries to get the token from cache initially. If that is unsuccessfull then it tries to get the token from ADAL. * @param {function} callback The callback in the form (err, result) * @return {function} callback * {Error} [err] The error if any * {object} [tokenResponse] The tokenResponse (tokenType and accessToken are the two important properties). */ getToken(callback) { let self = this; self._retrieveTokenFromCache(function (err, result) { if (err) { if (err.message.match(/.*while removing the expired token for service principal.*/i) !== null) { 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. let resource = self.environment.activeDirectoryResourceId; if (self.tokenAudience) { resource = self.tokenAudience; if (self.tokenAudience.toLowerCase() === 'graph') { resource = self.environment.activeDirectoryGraphResourceId; } else if (self.tokenAudience.toLowerCase() === 'batch') { resource = self.environment.batchResourceId; } } 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)); } return callback(null, tokenResponse); }); } } else { return callback(null, result); } }); } /** * Signs a request with the Authentication header. * * @param {webResource} The WebResource to be signed. * @param {function(error)} callback The callback function. * @return {undefined} */ signRequest(webResource, callback) { this.getToken(function (err, result) { if (err) return callback(err); webResource.headers[Constants.HeaderConstants.AUTHORIZATION] = `${result.tokenType} ${result.accessToken}`; return callback(null); }); } } module.exports = ApplicationTokenCredentials;