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 #360 from AzureAD/dev
Browse files Browse the repository at this point in the history
release 3.0.10
  • Loading branch information
lovemaths authored Jan 30, 2018
2 parents dec301d + b604599 commit 7f0197c
Show file tree
Hide file tree
Showing 12 changed files with 102 additions and 212 deletions.
11 changes: 10 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
<a name="3.0.9"></a>
<a name="3.0.10"></a>

# 3.0.10

## BearerStrategy

### New features

* [#354](https://github.com/AzureAD/passport-azure-ad/issues/354) Support passing tenant name or id in BearerStrategy

# 3.0.9

## OIDCStrategy
Expand Down
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@

# Microsoft Azure Active Directory Passport.js Plug-In
=============

---

_passport-azure-ad_ is a collection of [Passport](http://passportjs.org/) Strategies
to help you integrate with Azure Active Directory. It includes OpenID Connect,
Expand All @@ -16,7 +17,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.9
Current version - 3.0.10
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 @@ -497,7 +498,7 @@ var bearerStrategy = new BearerStrategy(options,

* `validateIssuer` (Conditional)

Required to set to false if you don't want to validate issuer, default value is true. We validate the `iss` claim in id_token against user provided `issuer` values and the issuer value we get from tenant-specific endpoint. If you use common endpoint for `identityMetadata` and you want to validate issuer, then you must provide `issuer`.
Required to set to false if you don't want to validate issuer, default value is true. We validate the `iss` claim in id_token against user provided `issuer` values and the issuer value we get from tenant-specific endpoint. If you use common endpoint for `identityMetadata` and you want to validate issuer, then you must provide `issuer`, or provide `tenantIdOrName` in passport.authenticate.

* `issuer` (Conditional)

Expand Down Expand Up @@ -550,6 +551,8 @@ In the following example, we are using passport to protect '/api/tasks'. User se

* `session`: if you don't want a persistent login session, you can use `session: false`. The default value is true.

* `tenantIdOrName`: if you use common endpoint, you can use this option to dynamically provide the tenant.

Example:

```
Expand Down
39 changes: 29 additions & 10 deletions lib/bearerstrategy.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,12 @@ const ttl = 1800; // 30 minutes cache
*
* - `validateIssuer` (1) Required to set to false if you don't want to validate issuer, default value is true
* (2) Description:
* For common endpoint, you should either set `validateIssuer` to false, or provide the `issuer`, since
* we cannot grab the `issuer` value from metadata.
* For common endpoint, you should either set `validateIssuer` to false, or provide the `issuer`, or provide `tenantIdOrName`
* in passport.authenticate, since otherwise we cannot grab the `issuer` value from metadata.
* For non-common endpoint, we use the `issuer` from metadata, and `validateIssuer` should be always true
*
* - `issuer` (1) Required if you are using common endpoint and set `validateIssuer` to true, or if you want to specify the allowed issuers
* - `issuer` (1) Required if set `validateIssuer` to true, but there is no way to get the issuer. For example, when you are using
* common endpoint, but you don't provide `tenantIdOrName` in passport.authenticate.
* (2) must be a string or an array of strings
* (3) Description:
* For common endpoint, we use the `issuer` provided.
Expand Down Expand Up @@ -249,10 +250,6 @@ function Strategy(options, verifyFn) {
// check if we are using the common endpoint
options._isCommonEndpoint = (options.identityMetadata.indexOf('/common/') != -1);

// issuer validation for common endpoint is not supported
if (options._isCommonEndpoint && options.validateIssuer && !options.issuer)
throw new Error(`In BearerStrategy constructor: you are using common endpoint, please either set 'validateIssuer' to false, or provide 'issuer' value in options`);

// give a warning if user is not validating issuer
if (!options.validateIssuer)
log.warn(`Production environments should always validate the issuer.`);
Expand All @@ -267,8 +264,6 @@ function Strategy(options, verifyFn) {
if (options.isB2C) {
if (!options.policyName || !CONSTANTS.POLICY_REGEX.test(options.policyName))
throw new Error('In BearerStrategy constructor: invalid policy for B2C');
if (options._isCommonEndpoint)
throw new Error(`In BearerStrategy constructor: common endpoint is not supported for B2C, please replace 'common' with your tenant name or tenant guid in 'identityMetadata'`);
}

// if logging level specified, switch to it.
Expand Down Expand Up @@ -351,10 +346,11 @@ Strategy.prototype.jwtVerify = function jwtVerifyFunc(req, token, metadata, opti
* We let the metadata loading happen in `authenticate` function, and use waterfall
* to make sure the authentication code runs after the metadata loading is finished.
*/
Strategy.prototype.authenticate = function authenticateStrategy(req) {
Strategy.prototype.authenticate = function authenticateStrategy(req, options) {
const self = this;
var params = {};
var optionsToValidate = {};
var tenantIdOrName = options && options.tenantIdOrName;

/* Some introduction to async.waterfall (from the following link):
* http://stackoverflow.com/questions/28908180/what-is-a-simple-implementation-of-async-waterfall
Expand Down Expand Up @@ -393,6 +389,29 @@ Strategy.prototype.authenticate = function authenticateStrategy(req) {
]
);

// if we are not using the common endpoint, but we have tenantIdOrName, just ignore it
if (!self._options._isCommonEndpoint && tenantIdOrName) {
log.info(`identityMetadata is tenant-specific, so we ignore the tenantIdOrName '${tenantIdOrName}'`);
tenantIdOrName = null;
}

// if we are using common endpoint and we are given the tenantIdOrName, let's replace it
if (self._options._isCommonEndpoint && tenantIdOrName) {
params.metadataURL = params.metadataURL.replace('/common/', `/${tenantIdOrName}/`);
log.info(`we are replacing 'common' with the tenantIdOrName ${tenantIdOrName}`);
}

// if we are using the common endpoint and we want to validate issuer, then user has to
// provide issuer in config, or provide tenant id or name using tenantIdOrName option in
// passport.authenticate. Otherwise we won't know the issuer.
if (self._options._isCommonEndpoint && self._options.validateIssuer &&
(!self._options.issuer && !tenantIdOrName))
return next(new Error('In passport.authenticate: issuer or tenantIdOrName must be provided in order to validate issuer on common endpoint'));

// for B2C, if we are using common endpoint, we must have tenantIdOrName provided
if (self._options.isB2C && self._options._isCommonEndpoint && !tenantIdOrName)
return next(new Error('In passport.authenticate: we are using common endpoint for B2C but tenantIdOrName is not provided'));

if (self._options.isB2C)
params.metadataURL = aadutils.concatUrl(params.metadataURL, `p=${self._options.policyName}`);

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "passport-azure-ad",
"version": "3.0.9",
"version": "3.0.10",
"license": "MIT",
"keywords": [
"azure active directory",
Expand Down
2 changes: 1 addition & 1 deletion test/End_to_end_test/app/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ module.exports = function(strategyOptions) {
app.use(passport.session());
app.use(app.router);

app.get('/api', passport.authenticate('oauth-bearer', { session: false }),
app.get('/api', passport.authenticate('oauth-bearer', { session: false, tenantIdOrName: strategyOptions.tenantIdOrName }),
function(req, res) {
res.send('succeeded');
}
Expand Down
10 changes: 9 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,7 +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_common_endpoint_dynamic_tenant, 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 @@ -110,6 +110,10 @@ var apply_test_parameters = (done) => {
server_config_with_scope = JSON.parse(JSON.stringify(server_config));
server_config_with_scope.scope = ['some_irrelevent_scope', test_parameters.scopeForBearer[0]];

server_config_common_endpoint_dynamic_tenant = JSON.parse(JSON.stringify(server_config));
server_config_common_endpoint_dynamic_tenant.identityMetadata = 'https://#.microsoftonline.com/common/v2.0/.well-known/openid-configuration';
server_config_common_endpoint_dynamic_tenant.tenantIdOrName = test_parameters.tenantID;

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

Expand Down Expand Up @@ -200,6 +204,10 @@ describe('bearer b2c test', function() {
checkResult(server_config_with_scope, 'succeeded', done);
});

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

it('should fail with wrong scope', function(done) {
checkResult(server_config_with_wrong_scope, 'Unauthorized', done);
});
Expand Down
9 changes: 9 additions & 0 deletions test/End_to_end_test/bearer_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ server_config_wrong_issuer, server_config_wrong_identityMetadata,
server_config_wrong_audience, server_config_wrong_issuer_no_validateIssuer,
server_config_multiple_audience,
server_config_common_endpoint, server_config_common_endpoint_with_req,
server_config_common_endpoint_dynamic_tenant,
server_config_common_endpoint_allow_multiAud, server_config_common_endpoint_wrong_issuer,
server_config_common_endpoint_wrong_audience,
server_config_common_endpoint_wrong_issuer_no_validateIssuer = {};
Expand Down Expand Up @@ -131,6 +132,10 @@ var apply_test_parameters = (done) => {
server_config_common_endpoint.identityMetadata = 'https://#.microsoftonline.com/common/.well-known/openid-configuration';
server_config_common_endpoint.issuer = 'https://sts.windows.net/' + test_parameters.tenantID + '/';

server_config_common_endpoint_dynamic_tenant = JSON.parse(JSON.stringify(server_config));
server_config_common_endpoint_dynamic_tenant.identityMetadata = 'https://#.microsoftonline.com/common/.well-known/openid-configuration';
server_config_common_endpoint_dynamic_tenant.tenantIdOrName = test_parameters.tenantID;

server_config_common_endpoint_with_req = JSON.parse(JSON.stringify(server_config_common_endpoint));
server_config_common_endpoint_with_req.passReqToCallback = true;

Expand Down Expand Up @@ -253,6 +258,10 @@ describe('bearer test', function() {
checkResult(server_config_common_endpoint, 'succeeded', done);
});

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

it('should succeed', function(done) {
checkResult(server_config_common_endpoint_with_req, 'succeeded', done);
});
Expand Down
78 changes: 3 additions & 75 deletions test/End_to_end_test/oidc_v1_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,7 @@ var test_parameters = {};
// tenant specific endpoint configurations
var config_template, hybrid_config, hybrid_config_alternative, code_config,
implicit_config, code_config_query, hybrid_config_noIssuer, hybrid_config_with_scope,
hybrid_config_passReqToCallback,
hybrid_config_clientAssertion, code_config_clientAssertion = {};
hybrid_config_passReqToCallback = {};

// common endpoint configurations
var config_template_common_endpoint, hybrid_config_common_endpoint,
Expand All @@ -63,10 +62,7 @@ hybrid_config_common_endpoint_with_scope = {};
// invalid configurations
var hybrid_config_common_endpoint_wrong_issuer,
hybrid_config_common_endpoint_short_lifetime,
hybrid_config_common_endpoint_wrong_secret,
hybrid_config_clientAssertion_invalid_pemKey,
hybrid_config_clientAssertion_unregistered_pemKey,
hybrid_config_clientAssertion_wrong_thumbprint = {};
hybrid_config_common_endpoint_wrong_secret = {};

// drivers needed for the tests
var driver;
Expand Down Expand Up @@ -146,18 +142,6 @@ var apply_test_parameters = (done) => {
hybrid_config_passReqToCallback = JSON.parse(JSON.stringify(config_template));
hybrid_config_passReqToCallback.passReqToCallback = true;

// 6. Hybird flow using client assertion
hybrid_config_clientAssertion = JSON.parse(JSON.stringify(hybrid_config));
hybrid_config_clientAssertion.thumbprint = test_parameters.thumbprint;
hybrid_config_clientAssertion.privatePEMKey = test_parameters.privatePEMKey;
hybrid_config_clientAssertion.clientSecret = null;

// 7. Code flow using client assertion
code_config_clientAssertion = JSON.parse(JSON.stringify(code_config));
code_config_clientAssertion.thumbprint = test_parameters.thumbprint;
code_config_clientAssertion.privatePEMKey = test_parameters.privatePEMKey;
code_config_clientAssertion.clientSecret = null;

/****************************************************************************
* Tenant specific endpoint configurations
***************************************************************************/
Expand Down Expand Up @@ -203,22 +187,6 @@ var apply_test_parameters = (done) => {
// 2. common endpoint with wrong client secret
hybrid_config_common_endpoint_wrong_secret = JSON.parse(JSON.stringify(config_template_common_endpoint));
hybrid_config_common_endpoint_wrong_secret.clientSecret = 'wrong_secret';
// 3. Hybird flow using client assertion with invalid privatePEMKey
hybrid_config_clientAssertion_invalid_pemKey = JSON.parse(JSON.stringify(hybrid_config));
hybrid_config_clientAssertion_invalid_pemKey.thumbprint = test_parameters.thumbprint;
hybrid_config_clientAssertion_invalid_pemKey.privatePEMKey = 'invalid private pem key';
hybrid_config_clientAssertion_invalid_pemKey.clientSecret = null;
// 4. hybrid flow using client assertion with wrong thumbprint
hybrid_config_clientAssertion_wrong_thumbprint = JSON.parse(JSON.stringify(hybrid_config));
hybrid_config_clientAssertion_wrong_thumbprint.thumbprint = 'wrongThumbprint';
hybrid_config_clientAssertion_wrong_thumbprint.privatePEMKey = test_parameters.privatePEMKey;
hybrid_config_clientAssertion_wrong_thumbprint.clientSecret = null;
// 5. hybrid flow using client assertion with unregistered privatePEMKey
var unregistered_privatePEMKey = fs.readFileSync(__dirname + '/../resource/private.pem', 'utf8');
hybrid_config_clientAssertion_unregistered_pemKey = JSON.parse(JSON.stringify(hybrid_config));
hybrid_config_clientAssertion_unregistered_pemKey.thumbprint = test_parameters.thumbprint;
hybrid_config_clientAssertion_unregistered_pemKey.privatePEMKey = unregistered_privatePEMKey;
hybrid_config_clientAssertion_unregistered_pemKey.clientSecret = null;
done();
};

Expand All @@ -241,25 +209,14 @@ var checkResult = (test_app_config, arity, done) => {
passwordbox = driver.findElement(By.name('passwd'));
passwordbox.sendKeys(webdriver.Key.ENTER);
first_time = false;
driver.findElement(By.id('idSIButton9')).then((element)=>{element.click();}, () => {});
}
}).then(() => {
driver.findElement(By.id('idBtn_Back')).then((element)=>{element.click();}, () => {});
}).then(() => {
driver.wait(until.titleIs('result'), 10000);
driver.findElement(By.id('status')).getText().then((text) => {
expect(text).to.equal('succeeded');
});
driver.findElement(By.id('access_token')).getText().then((text) => {
if (arity >= 6)
expect(text).to.equal('exists');
else
expect(text).to.equal('none');
});
driver.findElement(By.id('refresh_token')).getText().then((text) => {
if (arity >= 6)
expect(text).to.equal('exists');
else
expect(text).to.equal('none');
server.shutdown(done);
});
});
Expand Down Expand Up @@ -380,20 +337,6 @@ describe('oidc v1 positive other test', function() {
checkResult(implicit_config, 2, done);
});

/****************************************************************************
* Test client assertion
***************************************************************************/

// hybrid flow using client assertion
it('should succeed', function(done) {
checkResult(hybrid_config_clientAssertion, 8, done);
});

// code flow using client assertion
it('should succeed', function(done) {
checkResult(code_config_clientAssertion, 8, done);
});

/****************************************************************************
* Test various response type for common endpoint
***************************************************************************/
Expand Down Expand Up @@ -474,21 +417,6 @@ describe('oidc v1 negative test', function() {
checkInvalidResult(hybrid_config_common_endpoint_wrong_secret, done);
});

// invalid privatePEMKey
it('should fail with invalid privatePEMKey', function(done) {
checkInvalidResult(hybrid_config_clientAssertion_invalid_pemKey, done);
});

// wrong thumbprint
it('should fail with wrong thumbprint', function(done) {
checkInvalidResult(hybrid_config_clientAssertion_wrong_thumbprint, done);
});

// unregistered privatePEMKey
it('should fail with unregistered privatePEMKey', function(done) {
checkInvalidResult(hybrid_config_clientAssertion_unregistered_pemKey, done);
});

it('close service', function(done) {
expect('1').to.equal('1');
driver.quit();
Expand Down
Loading

0 comments on commit 7f0197c

Please # to comment.