| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607 |
- /*
- * @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 constants = require('./constants');
- var CacheDriver = require('./cache-driver');
- var Logger = require('./log').Logger;
- var Mex = require('./mex');
- var OAuth2Client = require('./oauth2client');
- var SelfSignedJwt = require('./self-signed-jwt');
- var UserRealm = require('./user-realm');
- var WSTrustRequest = require('./wstrust-request');
- var OAuth2Parameters = constants.OAuth2.Parameters;
- var TokenResponseFields = constants.TokenResponseFields;
- var OAuth2GrantType = constants.OAuth2.GrantType;
- var OAuth2Scope = constants.OAuth2.Scope;
- var Saml = constants.Saml;
- var AccountType = constants.UserRealm.AccountType;
- var WSTrustVersion = constants.WSTrustVersion;
- var DeviceCodeResponseParameters = constants.UserCodeResponseFields;
- /**
- * Constructs a new TokenRequest object.
- * @constructor
- * @private
- * @param {object} callContext Contains any context information that applies to the request.
- * @param {AuthenticationContext} authenticationContext
- * @param {string} resource
- * @param {string} clientId
- * @param {string} redirectUri
- */
- function TokenRequest(callContext, authenticationContext, clientId, resource, redirectUri) {
- this._log = new Logger('TokenRequest', callContext._logContext);
- this._callContext = callContext;
- this._authenticationContext = authenticationContext;
- this._resource = resource;
- this._clientId = clientId;
- this._redirectUri = redirectUri;
- // This should be set at the beginning of getToken
- // functions that have a userId.
- this._userId = null;
- this._userRealm = null;
- this._pollingClient = {};
- }
- TokenRequest.prototype._createUserRealmRequest = function(username) {
- return new UserRealm(this._callContext, username, this._authenticationContext.authority);
- };
- TokenRequest.prototype._createMex = function(mexEndpoint) {
- return new Mex(this._callContext, mexEndpoint);
- };
- TokenRequest.prototype._createWSTrustRequest = function(wstrustEndpoint, appliesTo, wstrustEndpointVersion) {
- return new WSTrustRequest(this._callContext, wstrustEndpoint, appliesTo, wstrustEndpointVersion);
- };
- TokenRequest.prototype._createOAuth2Client = function() {
- return new OAuth2Client(this._callContext, this._authenticationContext._authority);
- };
- TokenRequest.prototype._createSelfSignedJwt = function() {
- return new SelfSignedJwt(this._callContext, this._authenticationContext._authority, this._clientId);
- };
- TokenRequest.prototype._oauthGetToken = function(oauthParameters, callback) {
- var client = this._createOAuth2Client();
- client.getToken(oauthParameters, callback);
- };
- TokenRequest.prototype._oauthGetTokenByPolling = function(oauthParameters, refresh_interval, expires_in, callback){
- var client = this._createOAuth2Client();
- client.getTokenWithPolling(oauthParameters, refresh_interval, expires_in, callback);
- this._pollingClient = client;
- }
- TokenRequest.prototype._createCacheDriver = function() {
- return new CacheDriver(
- this._callContext,
- this._authenticationContext.authority,
- this._resource,
- this._clientId,
- this._authenticationContext.cache,
- this._getTokenWithTokenResponse.bind(this)
- );
- };
- /**
- * Used by the cache driver to refresh tokens.
- * @param {TokenResponse} entry A token response to refresh.
- * @param {string} resource The resource for which to get the token.
- * @param {AcquireTokenCallback} callback
- */
- TokenRequest.prototype._getTokenWithTokenResponse = function(entry, resource, callback) {
- this._log.verbose('Called to refresh a token from the cache.');
- var refreshToken = entry[TokenResponseFields.REFRESH_TOKEN];
- this._getTokenWithRefreshToken(refreshToken, resource, null, callback);
- };
- TokenRequest.prototype._createCacheQuery = function() {
- var query = {
- clientId : this._clientId
- };
- if (this._userId) {
- query.userId = this._userId;
- } else {
- this._log.verbose('No userId passed for cache query.');
- }
- return query;
- };
- TokenRequest.prototype._getTokenWithCacheWrapper = function(callback, getTokenFunc) {
- var self = this;
- this._cacheDriver = this._createCacheDriver();
- var cacheQuery = this._createCacheQuery();
- this._cacheDriver.find(cacheQuery, function(err, token) {
- if (err) {
- self._log.warn('Attempt to look for token in cahce resulted in Error');
- self._log.warn('Attempt to look for token in cache resulted in Error: ' + err.stack, true);
- }
- if (!token) {
- self._log.verbose('No appropriate cached token found.');
- getTokenFunc.call(self, function(err, tokenResponse) {
- if (err) {
- self._log.verbose('getTokenFunc returned with err');
- callback(err, tokenResponse);
- return;
- }
- self._log.verbose('Successfully retrieved token from authority');
- self._cacheDriver.add(tokenResponse, function() {
- callback(null, tokenResponse);
- });
- });
- } else {
- self._log.info('Returning cached token.');
- callback(err, token);
- }
- });
- };
- /**
- * Store token into cache.
- * @param {object} tokenResponse Token response to be added into the cache.
- */
- TokenRequest.prototype._addTokenIntoCache = function(tokenResponse, callback) {
- this._cacheDriver = this._createCacheDriver();
- this._log.verbose('Storing retrieved token into cache');
- this._cacheDriver.add(tokenResponse, function(err) {
- callback(err, tokenResponse);
- });
- };
- /**
- * Adds an OAuth parameter to the paramters object if the parameter is
- * not null or undefined.
- * @private
- * @param {object} parameters OAuth parameters object.
- * @param {string} key A member of the OAuth2Parameters constants.
- * @param {object} value
- */
- function _addParameterIfAvailable(parameters, key, value) {
- if (value) {
- parameters[key] = value;
- }
- }
- /**
- * Creates a set of basic, common, OAuthParameters based on values that the TokenRequest
- * was created with.
- * @private
- * @param {string} grantType A member of the OAuth2GrantType constants.
- * @return {object}
- */
- TokenRequest.prototype._createOAuthParameters = function(grantType) {
- var oauthParameters = {};
- oauthParameters[OAuth2Parameters.GRANT_TYPE] = grantType;
- if (OAuth2GrantType.AUTHORIZATION_CODE !== grantType &&
- OAuth2GrantType.CLIENT_CREDENTIALS !== grantType &&
- OAuth2GrantType.DEVICE_CODE != grantType) {
- oauthParameters[OAuth2Parameters.SCOPE] = OAuth2Scope.OPENID;
- }
- _addParameterIfAvailable(oauthParameters, OAuth2Parameters.CLIENT_ID, this._clientId);
- _addParameterIfAvailable(oauthParameters, OAuth2Parameters.RESOURCE, this._resource);
- _addParameterIfAvailable(oauthParameters, OAuth2Parameters.REDIRECT_URI, this._redirectUri);
- return oauthParameters;
- };
- /**
- * Get's a token from AAD using a username and password
- * @private
- * @param {string} username
- * @param {string} password
- * @param {AcquireTokenCallback} callback
- */
- TokenRequest.prototype._getTokenUsernamePasswordManaged = function(username, password, callback) {
- this._log.verbose('Acquiring token with username password for managed user');
- var oauthParameters = this._createOAuthParameters(OAuth2GrantType.PASSWORD);
- oauthParameters[OAuth2Parameters.PASSWORD] = password;
- oauthParameters[OAuth2Parameters.USERNAME] = username;
- this._oauthGetToken(oauthParameters, callback);
- };
- /**
- * Determines the OAuth SAML grant type to use based on the passed in TokenType
- * that was returned from a RSTR.
- * @param {string} wstrustResponse RSTR token type.
- * @return {string} An OAuth grant type.
- */
- TokenRequest.prototype._getSamlGrantType = function(wstrustResponse) {
- var tokenType = wstrustResponse.tokenType;
- switch (tokenType) {
- case Saml.TokenTypeV1:
- return OAuth2GrantType.SAML1;
- case Saml.TokenTypeV2:
- return OAuth2GrantType.SAML2;
- default:
- throw this._log.createError('RSTR returned unknown token type: ' + tokenType);
- }
- };
- /**
- * Performs an OAuth SAML Assertion grant type exchange. Uses a SAML token as the credential for getting
- * an OAuth access token.
- * @param {WSTrustResponse} wstrustResponse A response from a WSTrustRequest
- * @param {AcquireTokenCallback} callback callback
- */
- TokenRequest.prototype._performWSTrustAssertionOAuthExchange = function(wstrustResponse, callback) {
- this._log.verbose('Performing OAuth assertion grant type exchange.');
- var oauthParameters;
- try {
- var grantType = this._getSamlGrantType(wstrustResponse);
- var assertion = new Buffer(wstrustResponse.token).toString('base64');
- oauthParameters = this._createOAuthParameters(grantType);
- oauthParameters[OAuth2Parameters.ASSERTION] = assertion;
- } catch (err) {
- callback(err);
- return;
- }
- this._oauthGetToken(oauthParameters, callback);
- };
- /**
- * Exchange a username and password for a SAML token from an ADFS instance via WSTrust.
- * @param {string} wstrustEndpoint An url of an ADFS WSTrust endpoint.
- * @param {string} wstrustEndpointVersion The version of the wstrust endpoint.
- * @param {string} username username
- * @param {string} password password
- * @param {AcquireTokenCallback} callback callback
- */
- TokenRequest.prototype._performWSTrustExchange = function(wstrustEndpoint, wstrustEndpointVersion, username, password, callback) {
- var self = this;
- var wstrust = this._createWSTrustRequest(wstrustEndpoint, 'urn:federation:MicrosoftOnline', wstrustEndpointVersion);
- wstrust.acquireToken(username, password, function(rstErr, response) {
- if (rstErr) {
- callback(rstErr);
- return;
- }
- if (!response.token) {
- var rstrErr = self._log.createError('Unsucessful RSTR.\n\terror code: ' + response.errorCode + '\n\tfaultMessage: ' + response.faultMessage, true);
- callback(rstrErr);
- return;
- }
- callback(null, response);
- });
- };
- /**
- * Given a username and password this method invokes a WSTrust and OAuth exchange to get an access token.
- * @param {string} wstrustEndpoint An url of an ADFS WSTrust endpoint.
- * @param {string} username username
- * @param {string} password password
- * @param {AcquireTokenCallback} callback callback
- */
- TokenRequest.prototype._performUsernamePasswordForAccessTokenExchange = function(wstrustEndpoint, wstrustEndpointVersion, username, password, callback) {
- var self = this;
- this._performWSTrustExchange(wstrustEndpoint, wstrustEndpointVersion, username, password, function(err, wstrustResponse) {
- if (err) {
- callback(err);
- return;
- }
- self._performWSTrustAssertionOAuthExchange(wstrustResponse, callback);
- });
- };
- /**
- * Returns an Error object indicating that AAD did not return a WSTrust endpoint.
- * @return {Error}
- */
- TokenRequest.prototype._createADWSTrustEndpointError = function() {
- return this._log.createError('AAD did not return a WSTrust endpoint. Unable to proceed.');
- };
- /**
- * Gets an OAuth access token using a username and password via a federated ADFS instance.
- * @param {string} username username
- * @param {string} password password
- * @param {AcquireTokenCallback} callback callback
- */
- TokenRequest.prototype._getTokenUsernamePasswordFederated = function(username, password, callback) {
- this._log.verbose('Acquiring token with username password for federated user');
- var self = this;
- if (!this._userRealm.federationMetadataUrl) {
- this._log.warn('Unable to retrieve federationMetadataUrl from AAD. Attempting fallback to AAD supplied endpoint.');
- if (!this._userRealm.federationActiveAuthUrl) {
- callback(this._createADWSTrustEndpointError());
- return;
- }
- var wstrustVersion = this._parseWStrustVersionFromFederationActiveAuthUrl(this._userRealm.federationActiveAuthUrl);
- this._log.verbose('Wstrust endpoint version is: ' + wstrustVersion);
- this._performUsernamePasswordForAccessTokenExchange(this._userRealm.federationActiveAuthUrl, wstrustVersion, username, password, callback);
- return;
- } else {
- var mexEndpoint = this._userRealm.federationMetadataUrl;
- this._log.verbose('Attempting mex');
- this._log.verbose('Attempting mex at: ' + mexEndpoint, true);
- var mex = this._createMex(mexEndpoint);
- mex.discover(function(mexErr) {
- var wstrustEndpoint;
- wstrustVersion = WSTrustVersion.UNDEFINED;
- if (mexErr) {
- self._log.warn('MEX exchange failed. Attempting fallback to AAD supplied endpoint.');
- wstrustEndpoint = self._userRealm.federationActiveAuthUrl;
- wstrustVersion = self._parseWStrustVersionFromFederationActiveAuthUrl(self._userRealm.federationActiveAuthUrl);
- if (!wstrustEndpoint) {
- callback(self._createADWSTrustEndpointError());
- return;
- }
- } else {
- wstrustEndpoint = mex.usernamePasswordPolicy.url;
- wstrustVersion = mex.usernamePasswordPolicy.version;
- }
- self._performUsernamePasswordForAccessTokenExchange(wstrustEndpoint, wstrustVersion, username, password, callback);
- return;
- });
- }
- };
- /**
- * Gets wstrust endpoint version from the federation active auth url.
- * @private
- * @param {string} federationActiveAuthUrl federationActiveAuthUrl
- * @return {object} The wstrust endpoint version.
- */
- TokenRequest.prototype._parseWStrustVersionFromFederationActiveAuthUrl = function(federationActiveAuthUrl) {
- var wstrust2005Regex = /[/trust]?[2005][/usernamemixed]?/;
- var wstrust13Regex = /[/trust]?[13][/usernamemixed]?/;
- if (wstrust2005Regex.exec(federationActiveAuthUrl)) {
- return WSTrustVersion.WSTRUST2005;
- }
- else if (wstrust13Regex.exec(federationActiveAuthUrl)) {
- return WSTrustVersion.WSTRUST13;
- }
- return WSTrustVersion.UNDEFINED;
- };
- /**
- * Decides whether the username represents a managed or a federated user and then
- * obtains a token using the appropriate protocol flow.
- * @private
- * @param {string} username
- * @param {string} password
- * @param {AcquireTokenCallback} callback
- */
- TokenRequest.prototype.getTokenWithUsernamePassword = function(username, password, callback) {
- this._log.info('Acquiring token with username password');
- this._userId = username;
- this._getTokenWithCacheWrapper(callback, function(getTokenCompleteCallback) {
- var self = this;
- if(this._authenticationContext._authority._isAdfsAuthority) {
- this._log.info('Skipping user realm discovery for ADFS authority');
- self._getTokenUsernamePasswordManaged(username, password, getTokenCompleteCallback);
- return;
- }
- this._userRealm = this._createUserRealmRequest(username);
- this._userRealm.discover(function(err) {
- if (err) {
- getTokenCompleteCallback(err);
- return;
- }
- switch(self._userRealm.accountType) {
- case AccountType.Managed:
- self._getTokenUsernamePasswordManaged(username, password, getTokenCompleteCallback);
- return;
- case AccountType.Federated:
- self._getTokenUsernamePasswordFederated(username, password, getTokenCompleteCallback);
- return;
- default:
- getTokenCompleteCallback(self._log.createError('Server returned an unknown AccountType: ' + self._userRealm.AccountType));
- }
- });
- });
- };
- /**
- * Obtains a token using client credentials
- * @private
- * @param {string} clientSecret
- * @param {AcquireTokenCallback} callback
- */
- TokenRequest.prototype.getTokenWithClientCredentials = function(clientSecret, callback) {
- this._log.info('Getting token with client credentials.');
- this._getTokenWithCacheWrapper(callback, function(getTokenCompleteCallback) {
- var oauthParameters = this._createOAuthParameters(OAuth2GrantType.CLIENT_CREDENTIALS);
- oauthParameters[OAuth2Parameters.CLIENT_SECRET] = clientSecret;
- this._oauthGetToken(oauthParameters, getTokenCompleteCallback);
- });
- };
- /**
- * Obtains a token using an authorization code.
- * @private
- * @param {string} authorizationCode
- * @param {string} clientSecret
- * @param {AcquireTokenCallback} callback
- */
- TokenRequest.prototype.getTokenWithAuthorizationCode = function(authorizationCode, clientSecret, callback) {
- this._log.info('Getting token with auth code.');
- var oauthParameters = this._createOAuthParameters(OAuth2GrantType.AUTHORIZATION_CODE);
- oauthParameters[OAuth2Parameters.CODE] = authorizationCode;
- oauthParameters[OAuth2Parameters.CLIENT_SECRET] = clientSecret;
- this._oauthGetToken(oauthParameters, callback);
- };
- /**
- * Obtains a token using a refresh token.
- * @param {string} refreshToken
- * @param {string} resource
- * @param {string} [clientSecret]
- * @param {AcquireTokenCallback} callback
- */
- TokenRequest.prototype._getTokenWithRefreshToken = function(refreshToken, resource, clientSecret, callback) {
- this._log.info('Getting a new token from a refresh token.');
- var oauthParameters = this._createOAuthParameters(OAuth2GrantType.REFRESH_TOKEN);
- if (resource) {
- oauthParameters[OAuth2Parameters.RESOURCE] = resource;
- }
- if (clientSecret) {
- oauthParameters[OAuth2Parameters.CLIENT_SECRET] = clientSecret;
- }
- oauthParameters[OAuth2Parameters.REFRESH_TOKEN] = refreshToken;
- this._oauthGetToken(oauthParameters, callback);
- };
- /**
- * Obtains a token using a refresh token.
- * @param {string} refreshToken
- * @param {string} [clientSecret]
- * @param {AcquireTokenCallback} callback
- */
- TokenRequest.prototype.getTokenWithRefreshToken = function(refreshToken, clientSecret, callback) {
- this._getTokenWithRefreshToken(refreshToken, null, clientSecret, callback);
- };
- /**
- * Obtains a token from the cache, refreshing it or using a MRRT if necessary.
- * @param {string} [userId] The user associated with the cached token.
- * @param {AcquireTokenCallback} callback
- */
- TokenRequest.prototype.getTokenFromCacheWithRefresh = function(userId, callback) {
- var self = this;
- this._log.info('Getting token from cache with refresh if necessary.');
- this._userId = userId;
- this._getTokenWithCacheWrapper(callback, function(getTokenCompleteCallback) {
- // If this method was called then no cached entry was found. Since
- // this particular version of acquireToken can only retrieve tokens
- // from the cache, return an error.
- getTokenCompleteCallback(self._log.createError('Entry not found in cache.'));
- });
- };
- /**
- * Creates a self signed jwt.
- * @param {string} authorityUrl
- * @param {string} certificate A PEM encoded certificate private key.
- * @param {string} thumbprint
- * @return {string} A self signed JWT
- */
- TokenRequest.prototype._createJwt = function(authorityUrl, certificate, thumbprint) {
- var jwt;
- var ssj = this._createSelfSignedJwt();
- jwt = ssj.create(certificate, thumbprint);
- if (!jwt) {
- throw this._log.createError('Failed to create JWT');
- }
- return jwt;
- };
- /**
- * Obtains a token via a certificate. The certificate is used to generate a self signed
- * JWT token that is passed as a client_assertion.
- * @param {string} certificate A PEM encoded certificate private key.
- * @param {string} thumbprint A hex encoded thumbprint of the certificate.
- * @param {AcquireTokenCallback} callback
- */
- TokenRequest.prototype.getTokenWithCertificate = function(certificate, thumbprint, callback) {
- this._log.info('Getting a token via certificate.');
- var authorityUrl = this._authenticationContext._authority;
- var jwt;
- try {
- jwt = this._createJwt(authorityUrl, certificate, thumbprint);
- } catch (err) {
- callback(err);
- return;
- }
- var oauthParameters = this._createOAuthParameters(OAuth2GrantType.CLIENT_CREDENTIALS);
- oauthParameters[OAuth2Parameters.CLIENT_ASSERTION_TYPE] = OAuth2GrantType.JWT_BEARER;
- oauthParameters[OAuth2Parameters.CLIENT_ASSERTION] = jwt;
- this._getTokenWithCacheWrapper(callback, function(getTokenCompleteCallback) {
- this._oauthGetToken(oauthParameters, getTokenCompleteCallback);
- });
- };
- TokenRequest.prototype.getTokenWithDeviceCode = function(userCodeInfo, callback) {
- this._log.info('Getting a token via device code');
- var self = this;
- var oauthParameters = this._createOAuthParameters(OAuth2GrantType.DEVICE_CODE);
- oauthParameters[OAuth2Parameters.CODE] = userCodeInfo[DeviceCodeResponseParameters.DEVICE_CODE];
- var interval = userCodeInfo[DeviceCodeResponseParameters.INTERVAL];
- var expires_in = userCodeInfo[DeviceCodeResponseParameters.EXPIRES_IN];
- if (interval <= 0) {
- callback(new Error('invalid refresh interval'));
- }
- this._oauthGetTokenByPolling(oauthParameters, interval, expires_in, function(err, tokenResponse) {
- if (err) {
- self._log.verbose('Token polling request returend with err.');
- callback(err, tokenResponse);
- }
- else {
- self._addTokenIntoCache(tokenResponse, callback);
- }
- });
- };
- TokenRequest.prototype.cancelTokenRequestWithDeviceCode = function() {
- this._pollingClient.cancelPollingRequest();
- };
- module.exports = TokenRequest;
|