Skip to content
This repository has been archived by the owner on Aug 28, 2023. It is now read-only.

Commit

Permalink
Merge pull request #321 from AzureAD/dev
Browse files Browse the repository at this point in the history
Release 3.0.7
  • Loading branch information
lovemaths authored Jun 16, 2017
2 parents d1fb360 + 21663dc commit 0908008
Show file tree
Hide file tree
Showing 15 changed files with 216 additions and 76 deletions.
24 changes: 23 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,26 @@
<a name="3.0.6"></a>
<a name="3.0.7"></a>
# 3.0.7

## OIDCStrategy

### Bug fixes

* [#301](https://github.com/AzureAD/passport-azure-ad/issues/301) Error: a key with kid %s cannot be found

* [#309](https://github.com/AzureAD/passport-azure-ad/issues/309) "State" gets encoded and causes invalid state error

* [#317](https://github.com/AzureAD/passport-azure-ad/issues/317) Undefined "token_type"

## BearerStrategy

### New features

* [#296](https://github.com/AzureAD/passport-azure-ad/issues/296) scope validation for BearerStrategy

### Bug fixes

* [#301](https://github.com/AzureAD/passport-azure-ad/issues/301) Error: a key with kid %s cannot be found

# 3.0.6

## OIDCStrategy
Expand Down
33 changes: 1 addition & 32 deletions Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,42 +15,11 @@ module.exports = function loadGrunt(grunt) {
},
src: ['test/Chai-passport_test/*_test.js'],
},
},
eslint: {
options: {},
gruntfile: {
src: 'Gruntfile.js',
},
lib: {
src: ['lib/**/*.js'],
},
examples: {
src: ['examples/*/*.js'],
},
test: {
src: ['test/**/*.js'],
},
},
watch: {
gruntfile: {
files: '<%= eslint.gruntfile.src %>',
tasks: ['eslint:gruntfile'],
},
lib: {
files: '<%= eslint.lib.src %>',
tasks: ['eslint:lib', 'nodeunit', 'mochaTest'],
},
test: {
files: '<%= eslint.test.src %>',
tasks: ['eslint:test', 'nodeunit', 'mochaTest'],
},
},
}
});

// These plugins provide necessary tasks.
grunt.loadNpmTasks('grunt-contrib-nodeunit');
grunt.loadNpmTasks('grunt-eslint');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-mocha-test');

grunt.registerTask('printMsg_nodeunit', () => {
Expand Down
15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ and with [Microsoft Active Directory Federation Services](http://en.wikipedia.or
_passport-azure-ad_ has a known security vulnerability affecting versions <1.4.6 and 2.0.0. Please update to >=1.4.6 or >=2.0.1 immediately. For more details, see the [security notice](https://github.com/AzureAD/passport-azure-ad/blob/master/SECURITY-NOTICE.MD).

## 2. Versions
Current version - 3.0.4
Current version - 3.0.7
Minimum recommended version - 1.4.6
You can find the changes for each version in the [change log](https://github.com/AzureAD/passport-azure-ad/blob/master/CHANGELOG.md).

Expand Down Expand Up @@ -431,6 +431,7 @@ var options = {
audience: config.creds.audience,
loggingLevel: config.creds.loggingLevel,
clockSkew: config.creds.clockSkew,
scope: config.creds.scope
};

var bearerStrategy = new BearerStrategy(options,
Expand Down Expand Up @@ -508,6 +509,11 @@ var bearerStrategy = new BearerStrategy(options,
* `allowMultiAudiencesInToken` (Conditional)

Required if you allow access_token whose `aud` claim contains multiple values.

* `scope` (Optional)

This value is an array of scopes you accept. If this value is provided, we will check if the token contains one of
these accepted scopes. If this value is not provided, we won't check token scopes.

* `audience` (Optional)

Expand Down Expand Up @@ -609,6 +615,11 @@ following:

You will also need to click the 'Run now' button in the 'B2C_1_signup' blade to create an user.

For B2C application, you will also need to create at least one scope and provide it to test parameters. See [how to create scope for B2C access token](https://azure.microsoft.com/en-us/blog/azure-ad-b2c-access-tokens-now-in-public-preview/). In the bearer_b2c_test, We will use OIDCStrategy to get a B2C
access token for the scope, and use BearerStrategy to validate the scope. Note for scope we use the full url in
`b2c_params.scopeForOIDC` but only the name in `b2c_params.scopeForBearer`. For example,
`b2c_params.scopeForOIDC=['https://sijun1b2c.onmicrosoft.com/oidc-b2c/read']` and `b2c_params.scopeForBearer=['read']`.

#### 6.2.2. Fill the test parameters

Open `test/End_to_end_test/script.js`, set `is_test_parameters_completed` parameter to true. For `test_parameters` variable, fill in the tenant id/client id/client secret of your applications, and the username/password of your application user. The 'oid' value is the object id of your application user. To find the 'oid' value, go to your tenant, click 'Users and groups', find your user and click it. The Object ID value will show up in the new blade.
Expand All @@ -620,7 +631,7 @@ For `thumbprint` and `privatePEMKey` parameters, you need to specify a certifica
Type the following commands to run the tests:

```
$ cd test/End_to_end_test/app
$ cd test/End_to_end_test
$ npm install
$ npm install grunt -g
$ grunt run_tests_with_e2e
Expand Down
5 changes: 2 additions & 3 deletions lib/aadutils.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,8 @@ exports.merge = (a, b) => {
*/

exports.uid = (len) => {
return crypto.randomBytes(Math.ceil(len * 3 / 4))
.toString('base64')
.slice(0, len);
var bytes = crypto.randomBytes(Math.ceil(len * 3 / 4));
return base64url.encode(bytes).slice(0,len);
};

function prepadSigned(hexStr) {
Expand Down
50 changes: 43 additions & 7 deletions lib/bearerstrategy.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,11 @@ const B2C_PREFIX = 'b2c_1_';
* (2) Description:
* The default value is false
*
* - `scope` (1) Optional
* (2) Array of accepted scopes.
* (3) Description:
* If `scope` is provided, we will validate if access token contains any of the scopes listed in `scope`.
*
* - `loggingLevel` (1) Optional
* (2) must be 'info', 'warn', 'error'
* (3) Description:
Expand Down Expand Up @@ -235,6 +240,10 @@ function Strategy(options, verifyFn) {
if (!options.identityMetadata || !UrlValidator.isHttpsUri(options.identityMetadata))
throw new Error('In BearerStrategy constructor: identityMetadata must be provided and must be a https url');

// if scope is provided, it must be an array
if (options.scope && (!Array.isArray(options.scope) || options.scope.length === 0))
throw new Error('In BearerStrategy constructor: scope must be a non-empty array');

//---------------------------------------------------------------------------
// treatment of common endpoint and issuer
//---------------------------------------------------------------------------
Expand Down Expand Up @@ -287,12 +296,16 @@ Strategy.prototype.jwtVerify = function jwtVerifyFunc(req, token, metadata, opti
// When we generate the PEMkey, there are two different types of token signatures
// we have to validate here. One provides x5t and the other a kid. We need to call
// the right one.
if (decoded.header.x5t) {
PEMkey = metadata.generateOidcPEM(decoded.header.x5t);
} else if (decoded.header.kid) {
PEMkey = metadata.generateOidcPEM(decoded.header.kid);
} else {
return self.failWithLog('In Strategy.prototype.jwtVerify: We did not receive a token we know how to validate');
try {
if (decoded.header.x5t) {
PEMkey = metadata.generateOidcPEM(decoded.header.x5t);
} else if (decoded.header.kid) {
PEMkey = metadata.generateOidcPEM(decoded.header.kid);
} else {
return self.failWithLog('In Strategy.prototype.jwtVerify: We did not receive a token we know how to validate');
}
} catch (error) {
return self.failWithLog('In Strategy.prototype.jwtVerify: We did not receive a token we know how to validate');
}

log.info('PEMkey generated: ' + PEMkey);
Expand All @@ -302,7 +315,26 @@ Strategy.prototype.jwtVerify = function jwtVerifyFunc(req, token, metadata, opti
if (err.message)
return self.failWithLog(err.message);
else
return self.failWithLog('In Strategy.prototype.jwtVerify: cannot verify id token');
return self.failWithLog('In Strategy.prototype.jwtVerify: cannot verify token');
}

// scope validation
if (optionsToValidate.scope) {
if (!verifiedToken.scp)
return self.failWithLog('In Strategy.prototype.jwtVerify: scope is not found in token');

// split scope by blanks and remove empty elements in the array
var scopesInToken = verifiedToken.scp.split(/[ ]+/).filter(Boolean);
var hasValidScopeInToken = false;
for (var i = 0; i < scopesInToken.length; i++) {
if (optionsToValidate.scope.indexOf(scopesInToken[i]) !== -1) {
hasValidScopeInToken = true;
break;
}
}

if (!hasValidScopeInToken)
return self.failWithLog(`In Strategy.prototype.jwtVerify: none of the scopes '${verifiedToken.scp}' in token is accepted`);
}

log.info('In Strategy.prototype.jwtVerify: VerifiedToken: ', verifiedToken);
Expand Down Expand Up @@ -398,6 +430,10 @@ Strategy.prototype.authenticate = function authenticateStrategy(req) {
// clock skew
optionsToValidate.clockSkew = self._options.clockSkew;

// set up scope
if (self._options.scope)
optionsToValidate.scope = self._options.scope;

log.info(`In Strategy.prototype.authenticate: we will validate the following options: ${optionsToValidate}`);

return next();
Expand Down
24 changes: 16 additions & 8 deletions lib/oidcstrategy.js
Original file line number Diff line number Diff line change
Expand Up @@ -951,12 +951,16 @@ Strategy.prototype._validateResponse = function validateResponse(params, options

// get Pem Key
var PEMkey = null;
if (decoded.header.kid) {
PEMkey = params.metadata.generateOidcPEM(decoded.header.kid);
} else if (decoded.header.x5t) {
PEMkey = params.metadata.generateOidcPEM(decoded.header.x5t);
} else {
return next(new Error('In _validateResponse: We did not receive a token we know how to validate'));
try {
if (decoded.header.kid) {
PEMkey = params.metadata.generateOidcPEM(decoded.header.kid);
} else if (decoded.header.x5t) {
PEMkey = params.metadata.generateOidcPEM(decoded.header.x5t);
} else {
return next(new Error('In _validateResponse: We did not receive a token we know how to validate'));
}
} catch (error) {
return next(new Error('In _validateResponse: failed to generate PEM key due to: ' + error.message));
}
log.info('PEMkey generated: ' + PEMkey);

Expand Down Expand Up @@ -1146,9 +1150,13 @@ Strategy.prototype._authCodeFlowHandler = function authCodeFlowHandler(params, o
// id_token should be present
if (!items.id_token)
return next(new Error('In _authCodeFlowHandler: id_token is not received'));


// if we get access token, we must have token_type as well
if (items.access_token && !items.token_type)
return next(new Error('In _authCodeFlowHandler: token_type is missing'));

// token_type must be 'Bearer' ignoring the case
if (items.token_type.toLowerCase() !== 'bearer') {
if (items.token_type && items.token_type.toLowerCase() !== 'bearer') {
log.info('token_type received is: ', items.token_type);
return next(new Error(`In _authCodeFlowHandler: token_type received is not 'Bearer' ignoring the case`));
}
Expand Down
15 changes: 2 additions & 13 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "passport-azure-ad",
"version": "3.0.6",
"version": "3.0.7",
"license": "MIT",
"keywords": [
"azure active directory",
Expand All @@ -23,24 +23,13 @@
},
"main": "./lib",
"devDependencies": {
"adal-node": "^0.1.22",
"azure-keyvault": "^0.11.0",
"chai": "2.x.x",
"chai-passport-strategy": "1.x.x",
"chromedriver": "^2.24.1",
"eslint": "^2.9.0",
"eslint-config-oniyi": "^3.0.0",
"grunt": "^1.0.1",
"grunt-contrib-jshint": "^1.0.0",
"grunt-contrib-nodeunit": "^1.0.0",
"grunt-contrib-uglify": "^1.0.1",
"grunt-contrib-watch": "^1.0.0",
"grunt-eslint": "^18.1.0",
"grunt-mocha-test": "^0.12.7",
"mocha": "^3.0.2",
"nodeunit": "^0.9.1",
"selenium-webdriver": "^3.0.0",
"server-graceful-shutdown": "^0.1.2"
"nodeunit": "^0.9.1"
},
"dependencies": {
"async": "^1.5.2",
Expand Down
56 changes: 56 additions & 0 deletions test/Chai-passport_test/aadutils_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/**
* Copyright (c) Microsoft Corporation
* All Rights Reserved
* MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this
* software and associated documentation files (the 'Software'), to deal in the Software
* without restriction, including without limitation the rights to use, copy, modify,
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
* OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
* OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

'use strict';

var chai = require('chai');
var expect = chai.expect;
var aadutils = require('../../lib/aadutils');

const TEST_TIMEOUT = 1000000; // 1000 seconds

describe('uid test', function() {
this.timeout(TEST_TIMEOUT);

it('should return an id with the required length and no url unsafe characters', function(done) {
// generate and test uid 10 times

for (let i = 0; i < 10; i++) {
let uid = aadutils.uid(32);
let uid_url_safe = uid.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');

expect(uid.length).to.equal(32);
expect(uid).to.equal(uid_url_safe);
}

for (let i = 0; i < 10; i++) {
let uid = aadutils.uid(24);
let uid_url_safe = uid.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');

expect(uid.length).to.equal(24);
expect(uid).to.equal(uid_url_safe);
}

done();
});
});
17 changes: 16 additions & 1 deletion test/End_to_end_test/bearer_b2c_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ const LOGIN_WAITING_TIME = 3000; // 3 second
var test_parameters = {};

var client_config, server_config, server_config_with_req, server_config_allow_multiAud,
server_config_with_scope, server_config_with_wrong_scope,
server_config_wrong_issuer, server_config_wrong_policyName, server_config_wrong_identityMetadata,
server_config_wrong_audience, server_config_wrong_issuer_no_validateIssuer = {};

Expand Down Expand Up @@ -82,7 +83,7 @@ var apply_test_parameters = (done) => {
isB2C: true,
issuer: ['https://login.microsoftonline.com/' + test_parameters.tenantID + '/v2.0/'],
passReqToCallback: false,
scope: ['offline_access', test_parameters.clientID],
scope: ['offline_access'].concat(test_parameters.scopeForOIDC),
loggingLevel: null,
nonceLifetime: null,
};
Expand All @@ -106,6 +107,12 @@ var apply_test_parameters = (done) => {
server_config_allow_multiAud = JSON.parse(JSON.stringify(server_config));
server_config_allow_multiAud.allowMultiAudiencesInToken = false;

server_config_with_scope = JSON.parse(JSON.stringify(server_config));
server_config_with_scope.scope = ['some_irrelevent_scope', test_parameters.scopeForBearer[0]];

server_config_with_wrong_scope = JSON.parse(JSON.stringify(server_config));
server_config_with_wrong_scope.scope = ['some_irrelevent_scope'];

server_config_wrong_issuer = JSON.parse(JSON.stringify(server_config));
server_config_wrong_issuer.issuer = 'wrong_issuer';

Expand Down Expand Up @@ -189,6 +196,14 @@ describe('bearer b2c test', function() {
checkResult(server_config_wrong_issuer_no_validateIssuer, 'succeeded', done);
});

it('should succeed', function(done) {
checkResult(server_config_with_scope, 'succeeded', done);
});

it('should fail with wrong scope', function(done) {
checkResult(server_config_with_wrong_scope, 'Unauthorized', done);
});

it('should fail with wrong audience', function(done) {
checkResult(server_config_wrong_audience, 'Unauthorized', done);
});
Expand Down
Loading

0 comments on commit 0908008

Please sign in to comment.