Skip to content
This repository was archived by the owner on Aug 7, 2021. It is now read-only.

Commit a5fce9a

Browse files
committed
Merge pull request #77 from AzureAD/dev
merge from dev
2 parents ba22e61 + c3d1acb commit a5fce9a

14 files changed

+1013
-26
lines changed

changelog.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
Version 0.1.16
2+
--------------
3+
Release Date: 3 Sep 2015
4+
* Add support for device profile
5+
16
Version 0.1.15
27
--------------
38
Release Date: 4 Aug 2015

lib/argument.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
'use strict';
2222

2323
var _ = require('underscore');
24+
var constants = require('./constants');
25+
26+
var UserCodeResponseFields = constants.UserCodeResponseFields;
2427

2528
var argumentValidation = {
2629
/**
@@ -47,6 +50,24 @@ var argumentValidation = {
4750
if (!callback || !_.isFunction(callback)) {
4851
throw new Error('acquireToken requires a function callback parameter.');
4952
}
53+
},
54+
55+
validateUserCodeInfo : function(userCodeInfo) {
56+
if (!userCodeInfo){
57+
throw new Error('The userCodeInfo parameter is required');
58+
}
59+
60+
if (!userCodeInfo.hasOwnProperty(UserCodeResponseFields.DEVICE_CODE)){
61+
throw new Error('The userCodeInfo is missing device_code');
62+
}
63+
64+
if (!userCodeInfo.hasOwnProperty(UserCodeResponseFields.INTERVAL)){
65+
throw new Error('The userCodeInfo is missing interval');
66+
}
67+
68+
if (!userCodeInfo.hasOwnProperty(UserCodeResponseFields.EXPIRES_IN)){
69+
throw new Error('The userCodeInfo is missing expires_in');
70+
}
5071
}
5172
};
5273

lib/authentication-context.js

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,11 @@
2323
var argument = require('./argument');
2424
var Authority = require('./authority').Authority;
2525
var TokenRequest = require('./token-request');
26+
var CodeRequest = require('./code-request');
2627
var createLogContext = require('./log').createLogContext;
2728
var MemoryCache = require('./memory-cache');
2829
var util = require('./util');
29-
30+
var constants = require('./constants');
3031

3132
var globalADALOptions = {};
3233
var globalCache = new MemoryCache();
@@ -93,6 +94,7 @@ function AuthenticationContext(authority, validateAuthority, cache) {
9394
this._correlationId = null;
9495
this._callContext = { options : globalADALOptions };
9596
this._cache = cache || globalCache;
97+
this._tokenRequestWithUserCode = {};
9698
}
9799

98100
/**
@@ -202,6 +204,19 @@ AuthenticationContext.prototype._acquireToken = function(callback, tokenFunction
202204
});
203205
};
204206

207+
AuthenticationContext.prototype._acquireUserCode = function (callback, codeFunction) {
208+
var self = this;
209+
this._callContext._logContext = createLogContext(this.correlationId);
210+
this._authority.validate(this._callContext, function (err) {
211+
if (err) {
212+
callback(err);
213+
return;
214+
}
215+
216+
codeFunction.call(self);
217+
});
218+
};
219+
205220
/**
206221
* Gets a token for a given resource.
207222
* @param {string} resource A URI that identifies the resource for which the token is valid.
@@ -360,6 +375,80 @@ AuthenticationContext.prototype.acquireTokenWithClientCertificate = function(res
360375
});
361376
};
362377

378+
/**
379+
* Gets the userCodeInfo which contains user_code, device_code for authenticating user on device.
380+
* @param {string} resource A URI that identifies the resource for which the device_code and user_code is valid for.
381+
* @param {string} clientId The OAuth client id of the calling application.
382+
* @param {string} language The language code specifying how the message should be localized to.
383+
* @param {AcquireTokenCallback} callback The callback function.
384+
*/
385+
AuthenticationContext.prototype.acquireUserCode = function(resource, clientId, language, callback) {
386+
argument.validateCallbackType(callback);
387+
388+
try {
389+
argument.validateStringParameter(resource, 'resource');
390+
argument.validateStringParameter(clientId, 'clientId');
391+
} catch (err) {
392+
callback(err);
393+
return;
394+
}
395+
396+
this._acquireUserCode(callback, function () {
397+
var codeRequest = new CodeRequest(this._callContext, this, clientId, resource);
398+
codeRequest.getUserCodeInfo(language, callback);
399+
});
400+
};
401+
402+
/**
403+
* Gets a new access token using via a device code.
404+
* @param {string} clientId The OAuth client id of the calling application.
405+
* @param {object} userCodeInfo Contains device_code, retry interval, and expire time for the request for get the token.
406+
* @param {AcquireTokenCallback} callback The callback function.
407+
*/
408+
AuthenticationContext.prototype.acquireTokenWithDeviceCode = function(resource, clientId, userCodeInfo, callback){
409+
argument.validateCallbackType(callback);
410+
411+
try{
412+
argument.validateUserCodeInfo(userCodeInfo);
413+
} catch (err) {
414+
callback(err);
415+
return;
416+
}
417+
418+
var self = this;
419+
this._acquireToken(callback, function() {
420+
var tokenRequest = new TokenRequest(this._callContext, this, clientId, resource, null);
421+
self._tokenRequestWithUserCode[userCodeInfo[constants.OAuth2.DeviceCodeResponseParameters.DEVICE_CODE]] = tokenRequest;
422+
tokenRequest.getTokenWithDeviceCode(userCodeInfo, callback);
423+
})
424+
};
425+
426+
/**
427+
* Cancels the polling request to get token with device code.
428+
* @param {object} userCodeInfo Contains device_code, retry interval, and expire time for the request for get the token.
429+
* @param {AcquireTokenCallback} callback The callback function.
430+
*/
431+
AuthenticationContext.prototype.cancelRequestToGetTokenWithDeviceCode = function (userCodeInfo, callback) {
432+
argument.validateCallbackType(callback);
433+
434+
try {
435+
argument.validateUserCodeInfo(userCodeInfo);
436+
} catch (err) {
437+
callback(err);
438+
return;
439+
}
440+
441+
if (!this._tokenRequestWithUserCode || !this._tokenRequestWithUserCode[userCodeInfo[constants.OAuth2.DeviceCodeResponseParameters.DEVICE_CODE]]) {
442+
callback(new Error('No acquireTokenWithDeviceCodeRequest existed to be cancelled'));
443+
return;
444+
}
445+
446+
var tokenRequestToBeCancelled = this._tokenRequestWithUserCode[userCodeInfo[constants.OAuth2.DeviceCodeResponseParameters.DEVICE_CODE]];
447+
tokenRequestToBeCancelled.cancelTokenRequestWithDeviceCode();
448+
449+
delete this._tokenRequestWithUserCode[constants.OAuth2.DeviceCodeResponseParameters.DEVICE_CODE];
450+
};
451+
363452
var exports = {
364453
AuthenticationContext : AuthenticationContext,
365454
setGlobalADALOptions : function(options) {

lib/authority.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ function Authority(authorityUrl, validateAuthority) {
4949

5050
this._authorizationEndpoint = null;
5151
this._tokenEndpoint = null;
52+
this._deviceCodeEndpoint = null;
5253
}
5354

5455
/**
@@ -77,6 +78,12 @@ Object.defineProperty(Authority.prototype, 'tokenEndpoint', {
7778
}
7879
});
7980

81+
Object.defineProperty(Authority.prototype, 'deviceCodeEndpoint', {
82+
get: function() {
83+
return this._deviceCodeEndpoint;
84+
}
85+
});
86+
8087
/**
8188
* Checks the authority url to ensure that it meets basic requirements such as being over SSL. If it does not then
8289
* this method will throw if any of the checks fail.
@@ -214,12 +221,19 @@ Authority.prototype._validateViaInstanceDiscovery = function(callback) {
214221
* @param {Authority.GetOauthEndpointsCallback} callback The callback function.
215222
*/
216223
Authority.prototype._getOAuthEndpoints = function(tenantDiscoveryEndpoint, callback) {
217-
if (this._tokenEndpoint) {
224+
if (this._tokenEndpoint && this._deviceCodeEndpoint) {
218225
callback();
219226
return;
220227
} else {
221228
// fallback to the well known token endpoint path.
222-
this._tokenEndpoint = url.format(this._url) + AADConstants.TOKEN_ENDPOINT_PATH;
229+
if (!this._tokenEndpoint){
230+
this._tokenEndpoint = url.format(this._url) + AADConstants.TOKEN_ENDPOINT_PATH;
231+
}
232+
233+
if (!this._deviceCodeEndpoint){
234+
this._deviceCodeEndpoint = url.format(this._url) + AADConstants.DEVICE_ENDPOINT_PATH;
235+
}
236+
223237
callback();
224238
return;
225239
}

lib/code-request.js

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
* @copyright
3+
* Copyright © Microsoft Open Technologies, Inc.
4+
*
5+
* All Rights Reserved
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http: *www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS
14+
* OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
15+
* ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A
16+
* PARTICULAR PURPOSE, MERCHANTABILITY OR NON-INFRINGEMENT.
17+
*
18+
* See the Apache License, Version 2.0 for the specific language
19+
* governing permissions and limitations under the License.
20+
*/
21+
'use strict';
22+
23+
var constants = require('./constants');
24+
var Logger = require('./log').Logger;
25+
var Mex = require('./mex');
26+
var OAuth2Client = require('./oauth2client');
27+
28+
var OAuth2Parameters = constants.OAuth2.Parameters;
29+
var TokenResponseFields = constants.TokenResponseFields;
30+
var OAuth2GrantType = constants.OAuth2.GrantType;
31+
var OAuth2Scope = constants.OAuth2.Scope;
32+
33+
/**
34+
* Constructs a new CodeRequest object.
35+
* @constructor
36+
* @private
37+
* @param {object} callContext Contains any context information that applies to the request.
38+
* @param {AuthenticationContext} authenticationContext
39+
* @param {string} resource
40+
* @param {string} clientId
41+
*/
42+
// TODO: probably need to modify the parameter list.
43+
function CodeRequest(callContext, authenticationContext, clientId, resource) {
44+
this._log = new Logger('DeviceCodeRequest', callContext._logContext);
45+
this._callContext = callContext;
46+
this._authenticationContext = authenticationContext;
47+
this._resource = resource;
48+
this._clientId = clientId;
49+
50+
// This should be set at the beginning of getToken
51+
// functions that have a userId.
52+
this._userId = null;
53+
};
54+
55+
/**
56+
* Get user code info.
57+
* @private
58+
* @param {object} oauthParameters containing all the parameters needed to get the user code info.
59+
* @param {callback} callback
60+
*/
61+
CodeRequest.prototype._getUserCodeInfo = function (oauthParameters, callback) {
62+
var oauth2Client = this._createOAuth2Client();
63+
oauth2Client.getUserCodeInfo(oauthParameters, callback);
64+
};
65+
66+
CodeRequest.prototype._createOAuth2Client = function () {
67+
return new OAuth2Client(this._callContext, this._authenticationContext._authority);
68+
};
69+
70+
/**
71+
* Creates a set of basic, common, OAuthParameters based on values that the CodeRequest was created with.
72+
* @private
73+
* @return {object} containing all the basic parameters.
74+
*/
75+
CodeRequest.prototype._createOAuthParameters = function () {
76+
var oauthParameters = {};
77+
78+
oauthParameters[OAuth2Parameters.CLIENT_ID] = this._clientId;
79+
oauthParameters[OAuth2Parameters.RESOURCE] = this._resource;
80+
81+
return oauthParameters;
82+
};
83+
84+
/**
85+
* Get the user code information.
86+
* @param {string} language optional parameter used to get the user code info.
87+
* @param {callback} callback
88+
*/
89+
CodeRequest.prototype.getUserCodeInfo = function(language, callback) {
90+
this._log.info('Getting user code info.');
91+
92+
var oauthParameters = this._createOAuthParameters();
93+
if (language){
94+
oauthParameters[OAuth2Parameters.LANGUAGE] = language;
95+
}
96+
97+
this._getUserCodeInfo(oauthParameters, callback);
98+
};
99+
module.exports = CodeRequest;

lib/constants.js

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@ var Constants = {
3636
AAD_API_VERSION : 'api-version',
3737
USERNAME : 'username',
3838
PASSWORD : 'password',
39-
REFRESH_TOKEN : 'refresh_token'
39+
REFRESH_TOKEN : 'refresh_token',
40+
LANGUAGE : 'mkt',
41+
DEVICE_CODE : 'device_code',
4042
},
4143

4244
GrantType : {
@@ -46,7 +48,8 @@ var Constants = {
4648
JWT_BEARER : 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
4749
PASSWORD : 'password',
4850
SAML1 : 'urn:ietf:params:oauth:grant-type:saml1_1-bearer',
49-
SAML2 : 'urn:ietf:params:oauth:grant-type:saml2-bearer'
51+
SAML2 : 'urn:ietf:params:oauth:grant-type:saml2-bearer',
52+
DEVICE_CODE: 'device_code'
5053
},
5154

5255
ResponseParameters : {
@@ -63,6 +66,17 @@ var Constants = {
6366
ERROR_DESCRIPTION : 'error_description'
6467
},
6568

69+
DeviceCodeResponseParameters : {
70+
USER_CODE : 'user_code',
71+
DEVICE_CODE : 'device_code',
72+
VERIFICATION_URL : 'verification_url',
73+
EXPIRES_IN : 'expires_in',
74+
INTERVAL: 'interval',
75+
MESSAGE: 'message',
76+
ERROR: 'error',
77+
ERROR_DESCRIPTION: 'error_description'
78+
},
79+
6680
Scope : {
6781
OPENID : 'openid'
6882
},
@@ -88,6 +102,17 @@ var Constants = {
88102
ERROR_DESCRIPTION : 'errorDescription'
89103
},
90104

105+
UserCodeResponseFields : {
106+
USER_CODE : 'user_code',
107+
DEVICE_CODE: 'device_code',
108+
VERIFICATION_URL: 'verification_url',
109+
EXPIRES_IN: 'expires_in',
110+
INTERVAL: 'interval',
111+
MESSAGE: 'message',
112+
ERROR: 'error',
113+
ERROR_DESCRIPTION: 'error_description'
114+
},
115+
91116
IdTokenFields : {
92117
USER_ID : 'userId',
93118
IS_USER_ID_DISPLAYABLE : 'isUserIdDisplayable',
@@ -117,7 +142,8 @@ var Constants = {
117142
WELL_KNOWN_AUTHORITY_HOSTS : ['login.windows.net', 'login.microsoftonline.com', 'login.chinacloudapi.cn', 'login.cloudgovapi.us'],
118143
INSTANCE_DISCOVERY_ENDPOINT_TEMPLATE : 'https://{authorize_host}/common/discovery/instance?authorization_endpoint={authorize_endpoint}&api-version=1.0',
119144
AUTHORIZE_ENDPOINT_PATH : '/oauth2/authorize',
120-
TOKEN_ENDPOINT_PATH : '/oauth2/token'
145+
TOKEN_ENDPOINT_PATH : '/oauth2/token',
146+
DEVICE_ENDPOINT_PATH : '/oauth2/devicecode'
121147
},
122148

123149
UserRealm : {

0 commit comments

Comments
 (0)