/* * @copyright * Copyright © Microsoft Open Technologies, Inc. * * All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http: *www.apache.org/licenses/LICENSE-2.0 * * THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS * OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION * ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A * PARTICULAR PURPOSE, MERCHANTABILITY OR NON-INFRINGEMENT. * * See the Apache License, Version 2.0 for the specific language * governing permissions and limitations under the License. */ 'use strict'; var argument = require('./argument'); var Authority = require('./authority').Authority; var TokenRequest = require('./token-request'); var CodeRequest = require('./code-request'); var createLogContext = require('./log').createLogContext; var MemoryCache = require('./memory-cache'); var util = require('./util'); var constants = require('./constants'); var globalADALOptions = {}; var globalCache = new MemoryCache(); /** * This function is used to add or remove entries from a TokenCache * @typedef {function} ModifyCacheFunction * @param {Array} entries An array of entries to either add or remove from the TokenCache * @param {function} callback A callback function to call when the add or remove operation is complete. * This function can take a single error argument. */ /** * This function is called by a TokenCache when a find operation completes. * @callback TokenCacheFindCallback * @param {Error} [err] If an error occurred during the find operation then it should be passed here. * @param {Array} [entries] If the find operation was succesful then the matched entries should be returned here. */ /** * This function is called by ADAL to query a TokenCache. The query parameter is * a flat object which must be compared against entries in the cache. An entry * matches if it has all of the fields in the query and the values of those fields match * the values in the query. A matched object may have more fields than the query object. * @typedef {function} FindCacheFunction * @param {object} query This object should be compared to cache entries and matches should be returned. * @param {TokenCacheFindCallback} callback This callback should be called when the find operation is complete. */ /** * This is an interface that can be implemented to provide custom token cache persistence. * @public * @class TokenCache * @property {ModifyCacheFunction} add Called by ADAL when entries should be added to the cache. * @property {ModifyCacheFunction} remove Called by ADAL when entries should be removed from the cache. * @property {FindCacheFunction} find Called when ADAL needs to find entries in the cache. */ /** * Creates a new AuthenticationContext object. By default the authority will be checked against * a list of known Azure Active Directory authorities. If the authority is not recognized as * one of these well known authorities then token acquisition will fail. This behavior can be * turned off via the validateAuthority parameter below. * @constructor * @param {string} authority A URL that identifies a token authority. * @param {bool} [validateAuthority] Turns authority validation on or off. This parameter default to true. * @param {TokenCache} [cache] Sets the token cache used by this AuthenticationContext instance. If this parameter is not set * then a default, in memory cache is used. The default in memory cache is global to the process and is * shared by all AuthenticationContexts that are created with an empty cache parameter. To control the * scope and lifetime of a cache you can either create a {@link MemoryCache} instance and pass it when * constructing an AuthenticationContext or implement a custom {@link TokenCache} and pass that. Cache * instances passed at AuthenticationContext construction time are only used by that instance of * the AuthenticationContext and are not shared unless it has been manually passed during the * construction of other AuthenticationContexts. * */ function AuthenticationContext(authority, validateAuthority, cache) { var validate = (validateAuthority === undefined || validateAuthority === null || validateAuthority); this._authority = new Authority(authority, validate); this._oauth2client = null; this._correlationId = null; this._callContext = { options : globalADALOptions }; this._cache = cache || globalCache; this._tokenRequestWithUserCode = {}; } /** * Gets the authority url this AuthenticationContext was constructed with. * @instance * @memberOf AuthenticationContext * @type {string} * @name authority */ Object.defineProperty(AuthenticationContext.prototype, 'authority', { get: function () { return this._authority.url; } }); /** * Gets/Sets the correlation id that will be used for the next acquireToken request. * @instance * @memberOf AuthenticationContext * @type {string} * @name correlationId */ Object.defineProperty(AuthenticationContext.prototype, 'correlationId', { get: function () { return this._correlationId; }, set: function (id) { this._correlationId = id; } }); /** * Get/Sets options that are applied to requests generated by this AuthenticationContext instance. * @instance * @memberOf AuthenticationContext * @type {object} * @name options */ Object.defineProperty(AuthenticationContext.prototype, 'options', { get: function() { return this._callContext.options; }, set: function (value) { this._callContext.options = value; } }); /** * Get the token cache used by this AuthenticationContext instance. * @instance * @memberOf AuthenticationContext * @type {object} * @name cache */ Object.defineProperty(AuthenticationContext.prototype, 'cache', { get: function() { return this._cache; }, }); /** * This will be returned in case the OAuth 2 service returns an error. * @typedef ErrorResponse * @property {string} [error] A server error. * @property {string} [errorDescription] A description of the error returned. */ /** * Contains tokens and metadata upon successful completion of an acquireToken call. * @typedef TokenResponse * @property {string} tokenType The type of token returned. * @property {string} accessToken The returned access token. * @property {string} [refreshToken] A refresh token. * @property {Date} [createdOn] The date on which the access token was created. * @property {Date} expiresOn The Date on which the access token expires. * @property {int} expiresIn The amount of time, in seconds, for which the token is valid. * @property {string} [userId] An id for the user. May be a displayable value if is_user_id_displayable is true. * @property {bool} [isUserIdDisplayable] Indicates whether the user_id property will be meaningful if displayed to a user. * @property {string} [tenantId] The identifier of the tenant under which the access token was issued. * @property {string} [givenName] The given name of the principal represented by the access token. * @property {string} [familyName] The family name of the principal represented by the access token. * @property {string} [identityProvider] Identifies the identity provider that issued the access token. */ /** * This is the callback that is passed to all acquireToken variants below. * @callback AcquireTokenCallback * @param {Error} [error] If the request fails this parameter will contain an Error object. * @param {TokenResponse|ErrorResponse} [response] On a succesful request returns a {@link TokenResposne}. */ /** * This function implements code that is common to all acquireToken flows. * @private * @param {AcquireTokenCallback} callback * @param {Function} tokenFunction This is the function to call to actually acquire the token after common flow has completed. */ AuthenticationContext.prototype._acquireToken = function(callback, tokenFunction) { var self = this; this._callContext._logContext = createLogContext(this.correlationId); this._authority.validate(this._callContext, function(err) { if (err) { callback(err); return; } tokenFunction.call(self); }); }; AuthenticationContext.prototype._acquireUserCode = function (callback, codeFunction) { var self = this; this._callContext._logContext = createLogContext(this.correlationId); this._authority.validate(this._callContext, function (err) { if (err) { callback(err); return; } codeFunction.call(self); }); }; /** * Gets a token for a given resource. * @param {string} resource A URI that identifies the resource for which the token is valid. * @param {string} [userId] The username of the user on behalf this application is authenticating. * @param {string} [clientId] The OAuth client id of the calling application. * @param {AcquireTokenCallback} callback The callback function. */ AuthenticationContext.prototype.acquireToken = function(resource, userId, clientId, callback) { argument.validateCallbackType(callback); try { argument.validateStringParameter(resource, 'resource'); argument.validateStringParameter(clientId, 'clientId'); } catch(err) { callback(err); return; } this._acquireToken(callback, function() { var tokenRequest = new TokenRequest(this._callContext, this, clientId, resource); tokenRequest.getTokenFromCacheWithRefresh(userId, callback); }); }; /** * Gets a token for a given resource. * @param {string} resource A URI that identifies the resource for which the token is valid. * @param {string} username The username of the user on behalf this application is authenticating. * @param {string} password The password of the user named in the username parameter. * @param {string} clientId The OAuth client id of the calling application. * @param {AcquireTokenCallback} callback The callback function. */ AuthenticationContext.prototype.acquireTokenWithUsernamePassword = function(resource, username, password, clientId, callback) { argument.validateCallbackType(callback); try { argument.validateStringParameter(resource, 'resource'); argument.validateStringParameter(username, 'username'); argument.validateStringParameter(password, 'password'); argument.validateStringParameter(clientId, 'clientId'); } catch(err) { callback(err); return; } this._acquireToken(callback, function() { var tokenRequest = new TokenRequest(this._callContext, this, clientId, resource); tokenRequest.getTokenWithUsernamePassword(username, password, callback); }); }; /** * Gets a token for a given resource. * @param {string} resource A URI that identifies the resource for which the token is valid. * @param {string} clientId The OAuth client id of the calling application. * @param {string} clientSecret The OAuth client secret of the calling application. * @param {AcquireTokenCallback} callback The callback function. */ AuthenticationContext.prototype.acquireTokenWithClientCredentials = function(resource, clientId, clientSecret, callback) { argument.validateCallbackType(callback); try { argument.validateStringParameter(resource, 'resource'); argument.validateStringParameter(clientId, 'clientId'); argument.validateStringParameter(clientSecret, 'clientSecret'); } catch (err) { callback(err); return; } this._acquireToken(callback, function() { var tokenRequest = new TokenRequest(this._callContext, this, clientId, resource); tokenRequest.getTokenWithClientCredentials(clientSecret, callback); }); }; /** * Gets a token for a given resource. * @param {string} authorizationCode An authorization code returned from a client. * @param {string} redirectUri The redirect uri that was used in the authorize call. * @param {string} resource A URI that identifies the resource for which the token is valid. * @param {string} clientId The OAuth client id of the calling application. * @param {string} clientSecret The OAuth client secret of the calling application. * @param {AcquireTokenCallback} callback The callback function. */ AuthenticationContext.prototype.acquireTokenWithAuthorizationCode = function(authorizationCode, redirectUri, resource, clientId, clientSecret, callback) { argument.validateCallbackType(callback); try { argument.validateStringParameter(resource, 'resource'); argument.validateStringParameter(authorizationCode, 'authorizationCode'); argument.validateStringParameter(redirectUri, 'redirectUri'); argument.validateStringParameter(clientId, 'clientId'); } catch(err) { callback(err); return; } this._acquireToken(callback, function() { var tokenRequest = new TokenRequest(this._callContext, this, clientId, resource, redirectUri); tokenRequest.getTokenWithAuthorizationCode(authorizationCode, clientSecret, callback); }); }; /** * Gets a new access token via a previously issued refresh token. * @param {string} refreshToken A refresh token returned in a tokne response from a previous invocation of acquireToken. * @param {string} clientId The OAuth client id of the calling application. * @param {string} [clientSecret] The OAuth client secret of the calling application. (Note: this parameter is a late addition. * This parameter may be ommitted entirely so that applications built before this change will continue * to work unchanged.) * @param {string} resource The OAuth resource for which a token is being request. This parameter is optional and can be set to null. * @param {AcquireTokenCallback} callback The callback function. */ AuthenticationContext.prototype.acquireTokenWithRefreshToken = function(refreshToken, clientId, clientSecret, resource, callback) { // Fix up the arguments. Older clients may pass fewer arguments as the clientSecret paramter did not always exist. // The code needs to make adjustments for those clients. var clientSecretPresent = (5 === arguments.length); var actualClientSecret = clientSecretPresent ? clientSecret : null; var actualCallback = clientSecretPresent ? arguments[4] : arguments[3]; var actualResource = clientSecretPresent ? arguments[3] : arguments[2]; argument.validateCallbackType(actualCallback); try { argument.validateStringParameter(refreshToken, 'refreshToken'); argument.validateStringParameter(clientId, 'clientId'); } catch(err) { callback(err); return; } this._acquireToken(callback, function() { var tokenRequest = new TokenRequest(this._callContext, this, clientId, actualResource); tokenRequest.getTokenWithRefreshToken(refreshToken, actualClientSecret, actualCallback); }); }; /** * Gets a new access token using via a certificate credential. * @param {string} resource A URI that identifies the resource for which the token is valid. * @param {string} clientId The OAuth client id of the calling application. * @param {string} certificate A PEM encoded certificate private key. * @param {string} thumbprint A hex encoded thumbprint of the certificate. * @param {AcquireTokenCallback} callback The callback function. */ AuthenticationContext.prototype.acquireTokenWithClientCertificate = function(resource, clientId, certificate, thumbprint, callback) { argument.validateCallbackType(callback); try { argument.validateStringParameter(resource, 'resource'); argument.validateStringParameter(certificate, 'certificate'); argument.validateStringParameter(thumbprint, 'thumbprint'); } catch(err) { callback(err); return; } this._acquireToken(callback, function() { var tokenRequest = new TokenRequest(this._callContext, this, clientId, resource); tokenRequest.getTokenWithCertificate(certificate, thumbprint, callback); }); }; /** * Gets the userCodeInfo which contains user_code, device_code for authenticating user on device. * @param {string} resource A URI that identifies the resource for which the device_code and user_code is valid for. * @param {string} clientId The OAuth client id of the calling application. * @param {string} language The language code specifying how the message should be localized to. * @param {AcquireTokenCallback} callback The callback function. */ AuthenticationContext.prototype.acquireUserCode = function(resource, clientId, language, callback) { argument.validateCallbackType(callback); try { argument.validateStringParameter(resource, 'resource'); argument.validateStringParameter(clientId, 'clientId'); } catch (err) { callback(err); return; } this._acquireUserCode(callback, function () { var codeRequest = new CodeRequest(this._callContext, this, clientId, resource); codeRequest.getUserCodeInfo(language, callback); }); }; /** * Gets a new access token using via a device code. * @note This method doesn't look up the cache, it only stores the returned token into cache. To look up cache before making a new request, * please use acquireToken. * @param {string} clientId The OAuth client id of the calling application. * @param {object} userCodeInfo Contains device_code, retry interval, and expire time for the request for get the token. * @param {AcquireTokenCallback} callback The callback function. */ AuthenticationContext.prototype.acquireTokenWithDeviceCode = function(resource, clientId, userCodeInfo, callback){ argument.validateCallbackType(callback); try{ argument.validateUserCodeInfo(userCodeInfo); } catch (err) { callback(err); return; } var self = this; this._acquireToken(callback, function() { var tokenRequest = new TokenRequest(this._callContext, this, clientId, resource, null); self._tokenRequestWithUserCode[userCodeInfo[constants.UserCodeResponseFields.DEVICE_CODE]] = tokenRequest; tokenRequest.getTokenWithDeviceCode(userCodeInfo, callback); }) }; /** * Cancels the polling request to get token with device code. * @param {object} userCodeInfo Contains device_code, retry interval, and expire time for the request for get the token. * @param {AcquireTokenCallback} callback The callback function. */ AuthenticationContext.prototype.cancelRequestToGetTokenWithDeviceCode = function (userCodeInfo, callback) { argument.validateCallbackType(callback); try { argument.validateUserCodeInfo(userCodeInfo); } catch (err) { callback(err); return; } if (!this._tokenRequestWithUserCode || !this._tokenRequestWithUserCode[userCodeInfo[constants.UserCodeResponseFields.DEVICE_CODE]]) { callback(new Error('No acquireTokenWithDeviceCodeRequest existed to be cancelled')); return; } var tokenRequestToBeCancelled = this._tokenRequestWithUserCode[userCodeInfo[constants.UserCodeResponseFields.DEVICE_CODE]]; tokenRequestToBeCancelled.cancelTokenRequestWithDeviceCode(); delete this._tokenRequestWithUserCode[constants.UserCodeResponseFields.DEVICE_CODE]; }; var exports = { AuthenticationContext : AuthenticationContext, setGlobalADALOptions : function(options) { globalADALOptions = options; }, getGlobalADALOptions : function() { return globalADALOptions; } }; util.adalInit(); module.exports = exports;