├── .github └── workflows │ └── semgrep.yml ├── .gitignore ├── .jshintrc ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── commitlint.config.js ├── lib ├── claims │ └── PassportProfileMapper.js ├── encoders.js ├── federationServerService.js ├── index.js ├── interpolate.js ├── metadata.js ├── sendError.js ├── templates.js ├── utils.js └── wsfed.js ├── opslevel.yml ├── package.json ├── templates ├── federationServerService.ejs ├── federationServerServiceResponse.ejs ├── federationServerServiceWsdl.ejs ├── form.ejs ├── form_el.ejs ├── metadata.ejs └── soapFault.ejs └── test ├── custom_form.html ├── encoders.tests.js ├── federationServerService.tests.js ├── fixture ├── dsaparam.pem ├── server.js ├── wsfed.test-cert.key ├── wsfed.test-cert.pb7 ├── wsfed.test-cert.pem ├── wsfed.test-cert.pub ├── wsfed.test-dsa-key.key ├── wsfed.test-dsa-key.pb7 ├── wsfed.test-dsa-key.pem ├── wsfed.test-dsa-key.pub ├── wsfed.test-insercure-key.key ├── wsfed.test-insercure-key.pb7 ├── wsfed.test-insercure-key.pem └── wsfed.test-insercure-key.pub ├── interpolate.tests.js ├── jwt.tests.js ├── metadata.tests.js ├── wsfed-encryption.tests.js ├── wsfed-sha1.tests.js ├── wsfed.custom_form.tests.js ├── wsfed.tests.js └── xmlhelper.js /.github/workflows/semgrep.yml: -------------------------------------------------------------------------------- 1 | name: Semgrep 2 | on: 3 | pull_request: {} 4 | push: 5 | branches: ["master"] 6 | jobs: 7 | semgrep: 8 | name: Scan 9 | runs-on: ubuntu-latest 10 | if: (github.actor != 'dependabot[bot]' && github.actor != 'snyk-bot') 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: returntocorp/semgrep-action@v1 14 | with: 15 | publishToken: ${{ secrets.SEMGREP_APP_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | npm-debug.log 3 | package-lock.json 4 | .idea -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "camelcase": false, 3 | "curly": false, 4 | 5 | "node": true, 6 | "esnext": true, 7 | "bitwise": true, 8 | "eqeqeq": true, 9 | "immed": true, 10 | "indent": 2, 11 | "latedef": false, 12 | "newcap": true, 13 | "noarg": true, 14 | "regexp": true, 15 | "undef": true, 16 | "strict": false, 17 | "smarttabs": true, 18 | "expr": true, 19 | 20 | "evil": true, 21 | "browser": true, 22 | "regexdash": true, 23 | "wsh": true, 24 | "trailing": true, 25 | "sub": true, 26 | "unused": true, 27 | "laxcomma": true, 28 | "nonbsp": true, 29 | 30 | "globals": { 31 | "after": false, 32 | "before": false, 33 | "afterEach": false, 34 | "beforeEach": false, 35 | "describe": false, 36 | "it": false, 37 | "escape": false 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 10 4 | - 12 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ### [7.0.1](https://github.com/auth0/node-wsfed/compare/v7.0.0...v7.0.1) (2025-05-08) 6 | 7 | 8 | ### Bug Fixes 9 | 10 | * update ejs to 3.1.10 ([#32](https://github.com/auth0/node-wsfed/issues/32)) ([bf73fff](https://github.com/auth0/node-wsfed/commit/bf73fff7fa06903c7fc35cc8fdcb72cc249334c0)) 11 | * update saml to 3.0.1 ([#33](https://github.com/auth0/node-wsfed/issues/33)) ([eb0dede](https://github.com/auth0/node-wsfed/commit/eb0dede47d5d52bfd83d809d8f11db111e6dffd0)) 12 | 13 | ## [7.0.0] 14 | 15 | ### ⚠ BREAKING CHANGES 16 | 17 | - Stops support for node versions < 12 18 | - When using signed JWT assertions (https://github.com/auth0/node-wsfed#jwt), new restrictions apply. See ([jsonwebtokenv9.0.0]https://github.com/auth0/node-jsonwebtoken/wiki/Migration-Notes:-v8-to-v9). In particular: 19 | - RSA key size must be 2048 bits or greater. (unless the not recommended and insecure option jwtAllowInsecureKeySizes is used) 20 | - Asymmetric keys cannot be used to sign HMAC tokens. 21 | - Key types must be valid for the signing algorithm (unless the not recommended and insecure option jwtAllowInvalidAsymmetricKeyTypes is used) 22 | 23 | 24 | ### Security 25 | 26 | * upgrades jsonwebtoken to version 9.0.0, fixing JWT signing vulnerabilities ([GHSA-8cf7-32gw-wr33]https://github.com/auth0/node-jsonwebtoken/security/advisories/GHSA-8cf7-32gw-wr33). 27 | 28 | ## [6.1.0](https://github.com/auth0/node-wsfed/compare/v6.0.0...v6.1.0) (2021-02-12) 29 | 30 | 31 | ### Features 32 | 33 | * adding support for name identifier format option ([2228a7a](https://github.com/auth0/node-wsfed/commit/2228a7afd44ffbbb139e32954265d0c30dc87a36)) 34 | 35 | 36 | ### Bug Fixes 37 | 38 | * add back support for custom profile mapper for nameIdentifierFormat ([c0d932b](https://github.com/auth0/node-wsfed/commit/c0d932bb2a3ce0d38dbf33f1a619398265ff81d0)) 39 | * make xtend a production dependency ([26f3dc4](https://github.com/auth0/node-wsfed/commit/26f3dc411bb07fa492041e3fea6aeda5e96977f2)) 40 | 41 | ## [6.0.0](https://github.com/auth0/node-wsfed/compare/v5.0.0...v6.0.0) (2020-11-04) 42 | 43 | 44 | ### ⚠ BREAKING CHANGES 45 | 46 | * stop supporting node v4, v6 and v8 47 | * update saml dependency to fix vulnerabilities reported by npm 48 | 49 | ### Features 50 | 51 | * update saml dependency to fix vulnerabilities reported by npm ([178c9af](https://github.com/auth0/node-wsfed/commit/178c9afa04921e4c43ca63bd40df6967516e7618)) 52 | 53 | 54 | ### Bug Fixes 55 | 56 | * remove unused `debug` dev depenency and fix the deprecated usage of express res.send ([0dfb671](https://github.com/auth0/node-wsfed/commit/0dfb6719da5d71fc42b4fa7789d3c569005b9d7c)) 57 | 58 | 59 | ### build 60 | 61 | * remove node v4, v6 and v8 in travis configuration ([5ffa4c8](https://github.com/auth0/node-wsfed/commit/5ffa4c899a5c8cec3c2e696b0b16c245a0fa95b7)) 62 | 63 | ## [5.0.0](https://github.com/auth0/node-wsfed/compare/v4.0.0...v5.0.0) (2020-10-28) 64 | 65 | 66 | ### ⚠ BREAKING CHANGES 67 | 68 | * an error will be returned in case no `nameIdentifier` is returned from the profile mapper 69 | 70 | * fix!(nameIdentifier): handle the case of not found nameIdentifier ([615cffd](https://github.com/auth0/node-wsfed/commit/615cffd8544d6f88fc50546abf318c18225014f8)) 71 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Auth0, Inc. (http://auth0.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | WS Federation middleware for node.js. 2 | 3 | [![Build Status](https://travis-ci.org/auth0/node-wsfed.png)](https://travis-ci.org/auth0/node-wsfed) 4 | 5 | ## Installation 6 | 7 | npm install wsfed 8 | 9 | ## Introduction 10 | 11 | This Express middleware is meant to generate a valid WSFederation endpoint that talks saml. 12 | 13 | The idea is that you will use another mechanism to validate the user first. 14 | 15 | The endpoint supports metadata as well in the url ```/FederationMetadata/2007-06/FederationMetadata.xml```. 16 | 17 | ## Usage 18 | 19 | Options 20 | 21 | | Name | Description | Default | 22 | | --------------------|:-------------------------------------------------| ---------------------------------------------| 23 | | getPostURL| the function receives 4 parameters from the request (wtrealm, wreply, request, callback). It must return, via the calback, the URL to post the result response to | REQUIRED 24 | | getUserFromRequest| the function receives one parameter, the request. It must return the user being authenticated, as an object. This object will be passed to the profile mapper to determine attributes of the response. | `function(req){ return req.user; }` 25 | | profileMapper| a ProfileMapper implementation to convert a user profile to claims (PassportProfile) | PassportProfileMapper 26 | | cert| The public key / certificate of the issuer, in PEM format. | REQUIRED 27 | | key| The private key of the issuer, used to sign the response, in PEM format. | REQUIRED 28 | | issuer| the name of the issuer of the response. | 29 | | audience| the name of the audience of the response. | 30 | | lifetime| The lifetime of the response, in seconds| 8 hours 31 | | signatureAlgorithm| signature algorithm, options: rsa-sha1, rsa-sha256 | rsa-sha256 32 | | digestAlgorithm| digest algorithm, options: sha1, sha256 | sha256 33 | | jwt| if true, uses a JWT token for the signed assertion. | false 34 | | extendJWT| An object that, is passed, contains claims to be added to JWT signed assertion. | {} 35 | | jwtAlgorithm| If using JWT signed assertion, indicates the algorithm to be applied | RS256 36 | | jwtAllowInsecureKeySizes| Insecure and not recommended, for backward compatibility ONLY. If true, allows insecure key sizes to be used when signing with JWT| false 37 | | jwtAllowInvalidAsymmetricKeyTypes| Insecure and not recommended, for backward compatibility ONLY. If true, allows a mismatch between JWT algorithm and the actual key type provided to sign. | false 38 | 39 | Add the middleware as follows: 40 | 41 | ~~~javascript 42 | app.get('/wsfed', wsfed.auth({ 43 | issuer: 'the-issuer', 44 | cert: fs.readFileSync(path.join(__dirname, 'some-cert.pem')), 45 | key: fs.readFileSync(path.join(__dirname, 'some-cert.key')), 46 | getPostURL: function (wtrealm, wreply, req, callback) { 47 | return cb( null, 'http://someurl.com') 48 | } 49 | })); 50 | ~~~~ 51 | 52 | ## WsFederation Metadata 53 | 54 | wsfed can generate the metadata document for wsfederation as well. Usage as follows: 55 | 56 | ~~~javascript 57 | app.get('/wsfed/FederationMetadata/2007-06/FederationMetadata.xml', wsfed.metadata({ 58 | issuer: 'the-issuer', 59 | cert: fs.readFileSync(path.join(__dirname, 'some-cert.pem')), 60 | })); 61 | ~~~ 62 | 63 | It also accept two optionals parameters: 64 | 65 | - profileMapper: a class implementing the profile mapper. This is used to render the claims type information (using the metadata property). See [PassportProfileMapper](https://github.com/auth0/node-wsfed/blob/master/lib/claims/PassportProfileMapper.js) for more information. 66 | - endpointPath: this is the full path in your server to the auth route. By default the metadata handler uses the metadata request route without ```/FederationMetadata/2007..blabla.``` 67 | 68 | ## WsFederation Metadata endpoints ADFS1-like 69 | 70 | ADFS v1 uses another set of endpoints for the metadata and the thumbprint. If you have to connect an ADFS v1 client you have to do something like this: 71 | 72 | ~~~javascript 73 | app.get('/wsfed/adfs/fs/federationserverservice.asmx', 74 | wsfed.federationServerService.wsdl); 75 | 76 | app.post('/wsfed/adfs/fs/federationserverservice.asmx', 77 | wsfed.federationServerService.thumbprint({ 78 | pkcs7: yourPkcs7, 79 | cert: yourCert 80 | })); 81 | ~~~ 82 | 83 | notice that you need a ```pkcs7``` with the full chain of all certificates. You can generate this with openssl as follows: 84 | 85 | ~~~bash 86 | openssl crl2pkcs7 -nocrl \ 87 | -certfile your.crt \ 88 | -certfile another-cert-in-the-chain.crt \ 89 | -out contoso1.p7b 90 | ~~~ 91 | 92 | ## JWT 93 | 94 | By default the signed assertion is a SAML token, you can use JWT tokens as follows: 95 | 96 | ~~~javascript 97 | app.get('/wsfed', wsfed.auth({ 98 | jwt: true, 99 | issuer: 'the-issuer', 100 | key: fs.readFileSync(path.join(__dirname, 'some-cert.key')), 101 | getPostUrl: function (wtrealm, wreply, req, callback) { 102 | return cb( null, 'http://someurl.com') 103 | } 104 | })); 105 | ~~~~ 106 | 107 | Use the option `extendJWT` to add claims to the resulted token. `jwtAlgorithm` option allows to customize the algorithm used to sign tokens. 108 | 109 | Since version 7.0.0, restrictions apply on the signature of JWT tokens: 110 | - RSA key size must be 2048 bits or greater. (unless the not recommended and insecure option `jwtAllowInsecureKeySizes` is used) 111 | - Asymmetric keys cannot be used to sign HMAC tokens. 112 | - Key types must be valid for the signing algorithm (unless the not recommended and insecure option `jwtAllowInvalidAsymmetricKeyTypes` is used) 113 | 114 | ## Issue Reporting 115 | 116 | If you have found a bug or if you have a feature request, please report them at this repository issues section. Please do not report security vulnerabilities on the public GitHub issue tracker. The [Responsible Disclosure Program](https://auth0.com/whitehat) details the procedure for disclosing security issues. 117 | 118 | ## Author 119 | 120 | [Auth0](auth0.com) 121 | 122 | ## License 123 | 124 | This project is licensed under the MIT license. See the [LICENSE](LICENSE) file for more info. 125 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { extends: ['@commitlint/config-conventional'] }; 2 | -------------------------------------------------------------------------------- /lib/claims/PassportProfileMapper.js: -------------------------------------------------------------------------------- 1 | //shorthands claims namespaces 2 | var fm = { 3 | 'nameIdentifier': 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier', 4 | 'email': 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress', 5 | 'name': 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name', 6 | 'givenname': 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname', 7 | 'surname': 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname', 8 | 'upn': 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn', 9 | 'groups': 'http://schemas.xmlsoap.org/claims/Group' 10 | }; 11 | 12 | /** 13 | * 14 | * Passport User Profile Mapper 15 | * 16 | * A class to map passport.js user profile to a wsfed claims based identity. 17 | * 18 | * Passport Profile: 19 | * http://passportjs.org/guide/profile/ 20 | * 21 | * Claim Types: 22 | * http://msdn.microsoft.com/en-us/library/microsoft.identitymodel.claims.claimtypes_members.aspx 23 | * 24 | * @param {Object} pu Passport.js user profile 25 | */ 26 | function PassportProfileMapper (pu) { 27 | if(!(this instanceof PassportProfileMapper)) { 28 | return new PassportProfileMapper(pu); 29 | } 30 | this._pu = pu; 31 | } 32 | 33 | /** 34 | * map passport.js user profile to a wsfed claims based identity. 35 | * 36 | * @return {Object} WsFederation claim identity 37 | */ 38 | PassportProfileMapper.prototype.getClaims = function () { 39 | var claims = {}; 40 | 41 | claims[fm.nameIdentifier] = this._pu.id; 42 | 43 | if(Array.isArray(this._pu.emails) && this._pu.emails[0]) { 44 | claims[fm.email] = this._pu.emails[0].value; 45 | } 46 | 47 | claims[fm.name] = this._pu.displayName; 48 | 49 | if (this._pu.name) { 50 | claims[fm.givenname] = this._pu.name.givenName; 51 | claims[fm.surname] = this._pu.name.familyName; 52 | } 53 | 54 | var dontRemapAttributes = ['emails', 'displayName', 'name', 'id', '_json']; 55 | 56 | Object.keys(this._pu).filter(function (k) { 57 | return !~dontRemapAttributes.indexOf(k); 58 | }).forEach(function (k) { 59 | claims['http://schemas.passportjs.com/' + k] = this._pu[k]; 60 | }.bind(this)); 61 | return claims; 62 | }; 63 | 64 | /** 65 | * returns the nameidentifier for the saml token. 66 | * 67 | * @return {Object} object containing a nameIdentifier property and optional nameIdentifierFormat. 68 | */ 69 | PassportProfileMapper.prototype.getNameIdentifier = function () { 70 | var claims = this.getClaims(); 71 | 72 | return { 73 | nameIdentifier: claims[fm.nameIdentifier] || 74 | claims[fm.name] || 75 | claims[fm.emailaddress] 76 | }; 77 | 78 | }; 79 | 80 | /** 81 | * claims metadata used in the metadata endpoint. 82 | * 83 | * @param {Object} pu Passport.js profile 84 | * @return {[type]} WsFederation claim identity 85 | */ 86 | PassportProfileMapper.prototype.metadata = [ { 87 | id: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress", 88 | optional: true, 89 | displayName: 'E-Mail Address', 90 | description: 'The e-mail address of the user' 91 | }, { 92 | id: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname", 93 | optional: true, 94 | displayName: 'Given Name', 95 | description: 'The given name of the user' 96 | }, { 97 | id: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", 98 | optional: true, 99 | displayName: 'Name', 100 | description: 'The unique name of the user' 101 | }, { 102 | id: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname", 103 | optional: true, 104 | displayName: 'Surname', 105 | description: 'The surname of the user' 106 | }, { 107 | id: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", 108 | optional: true, 109 | displayName: 'Name ID', 110 | description: 'The SAML name identifier of the user' 111 | }]; 112 | 113 | module.exports = PassportProfileMapper; -------------------------------------------------------------------------------- /lib/encoders.js: -------------------------------------------------------------------------------- 1 | var thumbprint = require('@auth0/thumbprint'); 2 | 3 | var removeHeaders = module.exports.removeHeaders = function (cert) { 4 | var pem = /-----BEGIN (\w*)-----([^-]*)-----END (\w*)-----/g.exec(cert.toString()); 5 | if (pem && pem.length > 0) { 6 | return pem[2].replace(/[\n|\r\n]/g, ''); 7 | } 8 | return null; 9 | }; 10 | 11 | module.exports.thumbprint = function (pem) { 12 | var cert = removeHeaders(pem); 13 | return thumbprint.calculate(cert).toUpperCase(); 14 | }; 15 | 16 | module.exports.toCertifiedStore = function (pem) { 17 | var cert = removeHeaders(pem); 18 | var certBuffer = new Buffer(cert, 'base64'); 19 | 20 | var header = new Buffer(8); 21 | header.writeUInt32LE(0x00000000, 0); 22 | header.writeUInt32LE(0x54524543, 4); 23 | 24 | 25 | var start = new Buffer(12); 26 | start.writeUInt32LE(0x00000020, 0); 27 | start.writeUInt32LE(0x00000001, 4); 28 | start.writeUInt32LE(certBuffer.length, 8); 29 | 30 | var ending = new Buffer(12); 31 | ending.writeUInt32LE(0x00000000, 0); 32 | ending.writeUInt32LE(0x00000000, 4); 33 | 34 | return Buffer.concat([header, start, certBuffer, ending]).toString('base64'); 35 | }; -------------------------------------------------------------------------------- /lib/federationServerService.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils'); 2 | var templates = require('./templates'); 3 | var URL_PATH = '/wsfed/adfs/fs/federationserverservice.asmx'; 4 | var encoders = require('./encoders'); 5 | 6 | function getLocation (req) { 7 | return utils.getBaseUrl(req) + req.originalUrl; 8 | } 9 | 10 | function getEndpointAddress (req, endpointPath) { 11 | endpointPath = endpointPath || 12 | (req.originalUrl.substr(0, req.originalUrl.length - URL_PATH.length)); 13 | return utils.getBaseUrl(req) + endpointPath; 14 | } 15 | 16 | module.exports.wsdl = function (req, res) { 17 | res.set('Content-Type', 'text/xml; charset=UTF-8'); 18 | if(req.query.wsdl){ 19 | return res.send(templates.federationServerServiceWsdl()); 20 | } 21 | res.send(templates.federationServerService({ 22 | location: getLocation(req) 23 | })); 24 | }; 25 | 26 | module.exports.thumbprint = function (options) { 27 | return function (req, res) { 28 | res.set('Content-Type', 'text/xml; charset=UTF-8'); 29 | res.send(templates.federationServerServiceResponse({ 30 | location: getEndpointAddress(req, options.endpointPath), 31 | cert: encoders.removeHeaders(options.pkcs7.toString()), 32 | thumbprint: encoders.thumbprint(options.cert) 33 | })); 34 | }; 35 | }; 36 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | exports.auth = require('./wsfed'); 2 | exports.metadata = require('./metadata'); 3 | exports.federationServerService = {}; 4 | exports.federationServerService.wsdl = require('./federationServerService').wsdl; 5 | exports.federationServerService.thumbprint = require('./federationServerService').thumbprint; 6 | exports.sendError = require('./sendError'); -------------------------------------------------------------------------------- /lib/interpolate.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils'); 2 | 3 | function getProp(obj, path) { 4 | return path.split('.').reduce(function (prev, curr) { 5 | return prev[curr]; 6 | }, obj); 7 | } 8 | 9 | function escape (html){ 10 | return utils.escape(html).replace(/'/g, ''') 11 | } 12 | 13 | module.exports = function (tmpl) { 14 | return function (model) { 15 | return tmpl.replace(/\@\@([^\@]*)\@\@/g, 16 | function (a, b) { 17 | var r = getProp(model, b); 18 | var value = typeof r === 'string' || typeof r === 'number' ? r : a; 19 | return escape(value); 20 | } 21 | ); 22 | }; 23 | }; -------------------------------------------------------------------------------- /lib/metadata.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils'); 2 | var templates = require('./templates'); 3 | var PassportProfileMapper = require('./claims/PassportProfileMapper'); 4 | var URL_PATH = '/FederationMetadata/2007-06/FederationMetadata.xml'; 5 | var encoders = require('./encoders'); 6 | 7 | function getEndpointAddress (req, endpointPath) { 8 | endpointPath = endpointPath || 9 | (req.originalUrl.substr(0, req.originalUrl.length - URL_PATH.length)); 10 | 11 | return utils.getBaseUrl(req) + endpointPath; 12 | } 13 | 14 | /** 15 | * WSFederation metadata endpoint 16 | * 17 | * This endpoint returns a wsfederation metadata document. 18 | * 19 | * You should expose this endpoint in an address like: 20 | * 21 | * 'https://your-wsfederation-server.com/FederationMetadata/2007-06/FederationMetadata.xml 22 | * 23 | * options: 24 | * - issuer string 25 | * - cert the public certificate 26 | * - profileMapper a function that given a user returns a claim based identity, also contains the metadata. By default maps from Passport.js user schema (PassportProfile). 27 | * - endpointPath optional, defaults to the root of the fed metadata document. 28 | * - mexEndpoint optional, url of the wsfederation MEX endpoint metadata document. 29 | * 30 | * @param {[type]} options [description] 31 | * @return {[type]} [description] 32 | */ 33 | function metadataMiddleware (options) { 34 | //claimTypes, issuer, pem, endpointPath 35 | options = options || {}; 36 | 37 | if(!options.issuer) { 38 | throw new Error('options.issuer is required'); 39 | } 40 | 41 | if(!options.cert) { 42 | throw new Error('options.cert is required'); 43 | } 44 | 45 | var claimTypes = (options.profileMapper || PassportProfileMapper).prototype.metadata; 46 | var issuer = options.issuer; 47 | var pem = encoders.removeHeaders(options.cert); 48 | 49 | return function (req, res) { 50 | var endpoint = getEndpointAddress(req, options.endpointPath); 51 | var mexEndpoint = options.mexEndpoint ? getEndpointAddress(req, options.mexEndpoint) : ''; 52 | 53 | res.set('Content-Type', 'application/xml'); 54 | 55 | res.send(templates.metadata({ 56 | claimTypes: claimTypes, 57 | pem: pem, 58 | issuer: issuer, 59 | endpoint: endpoint, 60 | mexEndpoint: mexEndpoint 61 | }).replace(/\n/g, '')); 62 | }; 63 | } 64 | 65 | module.exports = metadataMiddleware; 66 | -------------------------------------------------------------------------------- /lib/sendError.js: -------------------------------------------------------------------------------- 1 | var templates = require('./templates'); 2 | var interpolate = require('./interpolate'); 3 | 4 | module.exports = function (options) { 5 | // http://docs.oasis-open.org/wsfed/federation/v1.2/os/ws-federation-1.2-spec-os.pdf 6 | function renderResponse(res, postUrl) { 7 | options.fault = options.fault || {}; 8 | 9 | var fault = templates['soapFault']({ 10 | code: options.fault.code, 11 | description: options.fault.description 12 | }); 13 | 14 | var form = options.formTemplate ? 15 | interpolate(options.formTemplate) : templates[(!options.plain_form ? 'form' : 'form_el')]; 16 | 17 | res.set('Content-Type', 'text/html'); 18 | res.send(form({ 19 | callback: postUrl, 20 | wctx: options.wctx, 21 | wresult: fault 22 | })); 23 | } 24 | 25 | return function (req, res, next) { 26 | options.getPostURL(req, function (err, postUrl) { 27 | if (err) return next(err); 28 | if (!postUrl) return next(new Error('postUrl is required')); 29 | renderResponse(res, postUrl); 30 | }); 31 | }; 32 | }; -------------------------------------------------------------------------------- /lib/templates.js: -------------------------------------------------------------------------------- 1 | var ejs = require('ejs'); 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | 5 | var templates = fs.readdirSync(path.join(__dirname, '../templates')); 6 | 7 | templates.forEach(function (tmplFile) { 8 | var content = fs.readFileSync(path.join(__dirname, '../templates', tmplFile)); 9 | var template = ejs.compile(content.toString()); 10 | exports[tmplFile.slice(0, -4)] = template; 11 | }); -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | 2 | exports.escape = function(html) { 3 | return String(html) 4 | .replace(/&/g, '&') 5 | .replace(//g, '>') 7 | .replace(/"/g, '"'); 8 | }; 9 | 10 | exports.getBaseUrl = function(req) { 11 | var protocol = req.headers['x-iisnode-https'] && req.headers['x-iisnode-https'] === 'on' ? 12 | 'https' : 13 | (req.headers['x-forwarded-proto'] || req.protocol); 14 | var host = req.headers['x-forwarded-host'] || req.headers['host']; 15 | return protocol + '://' + host; 16 | }; 17 | -------------------------------------------------------------------------------- /lib/wsfed.js: -------------------------------------------------------------------------------- 1 | var templates = require('./templates'); 2 | var PassportProfileMapper = require('./claims/PassportProfileMapper'); 3 | var utils = require('./utils'); 4 | var saml11 = require('saml').Saml11; 5 | var jwt = require('jsonwebtoken'); 6 | var interpolate = require('./interpolate'); 7 | var xtend = require('xtend'); 8 | 9 | function asResource(res) { 10 | if(res.substr(0, 6) !== 'http:/' && 11 | res.substr(0, 6) !== 'https:' && 12 | res.substr(0, 4) !== 'urn:') { 13 | return 'urn:' + res; 14 | } 15 | return res; 16 | } 17 | 18 | /** 19 | * WSFederation middleware. 20 | * 21 | * This middleware creates a WSFed endpoint based on the user logged in identity. 22 | * 23 | * @param {object} options 24 | * @param {function} options.getPostURL REQUIRED the function receives 4 parameters from the request (wtrealm, wreply, request, callback). It must return, via the callback, 25 | * the URL to post the result response to. 26 | * @param {function} options.getUserFromRequest the function receives one parameter, the request. It must return the user being authenticated, as an object. 27 | * This object will be passed to the profile mapper to determine attributes of the response. 28 | * @param {PassportProfileMapper} options.profileMapper a ProfileMapper implementation to convert a user profile to claims (PassportProfile). 29 | * @param {Buffer} options.cert The public key / certificate of the issuer, in PEM format. 30 | * @param {Buffer} options.key The private key of the issuer, used to sign the response, in PEM format. 31 | * @param {string} options.issuer the name of the issuer of the response. 32 | * @param {string} options.audience the name of the audience of the response. 33 | * @param {number} options.lifetime The lifetime of the response, in seconds. Default 8 hours. 34 | * @param {string} options.signatureAlgorithm signature algorithm, options: rsa-sha1, rsa-sha256. default rsa-sha256. 35 | * @param {string} options.digestAlgorithm digest algorithm, options: sha1, sha256. default sha256. 36 | * @param {boolean} options.jwt if true, uses a JWT token for the signed assertion. default false. 37 | * @param {object} options.extendJWT An object that, is passed, contains claims to be added to JWT signed assertion. 38 | * @param {boolean} options.jwtAlgorithm If using JWT signed assertion, indicates the algorithm to be applied. Default RS256. 39 | * @param {boolean} options.jwtAllowInsecureKeySizes Insecure and not recommended, for backward compatibility ONLY. If true, allows insecure key sizes to be used when signing with JWT. Default false. 40 | * @param {boolean} options.jwtAllowInvalidAsymmetricKeyTypes Insecure and not recommended, for backward compatibility ONLY. 41 | * If true, allows a mismatch between JWT algorithm and the actual key type provided to sign. 42 | * 43 | * @return {function} An Express middleware that acts as a WsFed endpoint. 44 | */ 45 | module.exports = function(options) { 46 | options = options || {}; 47 | options.profileMapper = options.profileMapper || PassportProfileMapper; 48 | options.getUserFromRequest = options.getUserFromRequest || function(req){ return req.user; }; 49 | 50 | if(typeof options.getPostURL !== 'function') { 51 | throw new Error('getPostURL is required'); 52 | } 53 | 54 | function renderResponse(res, postUrl, wctx, assertion) { 55 | res.set('Content-Type', 'text/html'); 56 | var model = { 57 | callback: postUrl, 58 | wctx: wctx, 59 | wresult: assertion 60 | }; 61 | var form; 62 | 63 | if (options.formTemplate) { 64 | form = interpolate(options.formTemplate); 65 | } else { 66 | form = templates[(!options.plain_form ? 'form' : 'form_el')]; 67 | } 68 | 69 | res.send(form(model)); 70 | } 71 | 72 | var responseHandler = options.responseHandler || renderResponse; 73 | 74 | function execute (postUrl, req, res, next) { 75 | var audience = options.audience || 76 | req.query.wtrealm || 77 | req.query.wreply; 78 | 79 | if(!audience){ 80 | return next(new Error('audience is required')); 81 | } 82 | 83 | audience = asResource(audience); 84 | 85 | var user = options.getUserFromRequest(req); 86 | if(!user) return res.send(401); 87 | 88 | var ctx = options.wctx || req.query.wctx; 89 | if (!options.jwt) { 90 | var profileMap = options.profileMapper(user); 91 | var claims = profileMap.getClaims(options); 92 | var ni = profileMap.getNameIdentifier(options); 93 | if (!ni || !ni.nameIdentifier) { 94 | return next(new Error('No attribute was found to generate the nameIdentifier')); 95 | } 96 | 97 | saml11.create({ 98 | signatureAlgorithm: options.signatureAlgorithm, 99 | digestAlgorithm: options.digestAlgorithm, 100 | cert: options.cert, 101 | key: options.key, 102 | issuer: asResource(options.issuer), 103 | lifetimeInSeconds: options.lifetime || options.lifetimeInSeconds || (60 * 60 * 8), 104 | audiences: audience, 105 | attributes: claims, 106 | nameIdentifier: ni.nameIdentifier, 107 | nameIdentifierFormat: ni.nameIdentifierFormat || options.nameIdentifierFormat, 108 | encryptionPublicKey: options.encryptionPublicKey, 109 | encryptionCert: options.encryptionCert 110 | }, function(err, assertion) { 111 | if (err) return next(err); 112 | var escapedWctx = utils.escape(ctx); 113 | assertion = '' + assertion + ''; 114 | 115 | return responseHandler(res, postUrl, ctx, assertion); 116 | }); 117 | 118 | } else { 119 | if (options.extendJWT && typeof options.extendJWT === 'object') { 120 | user = xtend(user, options.extendJWT); 121 | } 122 | 123 | jwt.sign(user, options.key.toString(), { 124 | expiresIn: options.lifetime || options.lifetimeInSeconds || (60 * 60 * 8), 125 | audience: audience, 126 | issuer: asResource(options.issuer), 127 | algorithm: options.jwtAlgorithm || 'RS256', 128 | allowInsecureKeySizes: options.jwtAllowInsecureKeySizes || false, 129 | allowInvalidAsymmetricKeyTypes: options.jwtAllowInvalidAsymmetricKeyTypes || false 130 | }, (error, signed) => { 131 | if (error) { 132 | return next(error); 133 | } 134 | 135 | return responseHandler(res, postUrl, ctx, signed); 136 | }); 137 | } 138 | } 139 | 140 | return function (req, res, next) { 141 | options.getPostURL(req.query.wtrealm, req.query.wreply, req, function (err, postUrl) { 142 | if (err) return next(err); 143 | if (!postUrl) return res.send(400, 'postUrl is required'); 144 | execute(postUrl, req, res, next); 145 | }); 146 | }; 147 | }; 148 | -------------------------------------------------------------------------------- /opslevel.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 1 3 | repository: 4 | owner: iam_protocols 5 | tier: 6 | tags: 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wsfed", 3 | "version": "7.0.1", 4 | "description": "WSFed server middleware", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "test": "mocha", 8 | "release": "standard-version" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/auth0/node-wsfed.git" 13 | }, 14 | "keywords": [ 15 | "wsfed", 16 | "saml", 17 | "auth" 18 | ], 19 | "author": "Auth0", 20 | "license": "MIT", 21 | "dependencies": { 22 | "@auth0/thumbprint": "0.0.6", 23 | "ejs": "^3.1.10", 24 | "jsonwebtoken": "^9.0.0", 25 | "saml": "^3.0.1", 26 | "xtend": "~2.0.3" 27 | }, 28 | "devDependencies": { 29 | "@commitlint/cli": "^11.0.0", 30 | "@commitlint/config-conventional": "^11.0.0", 31 | "chai": "~1.5.0", 32 | "cheerio": "0.22.0", 33 | "express": "~4.17.1", 34 | "husky": "^4.3.0", 35 | "mocha": "~8.2.0", 36 | "request": "~2.88.2", 37 | "standard-version": "^9.0.0", 38 | "xml-crypto": "~0.10.1", 39 | "xml-encryption": "1.2.1", 40 | "xmldom": "~0.1.17", 41 | "xpath": "0.0.5" 42 | }, 43 | "husky": { 44 | "hooks": { 45 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /templates/federationServerService.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /templates/federationServerServiceResponse.ejs: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | true 9 | 10 | 1 11 | 26886a27-50ad-9695-3511-8d24a1a3a23b 12 | 1 13 | 14 | 15 | 16 | 17 | 18 | <%= thumbprint %> 19 | 20 | 21 | None 22 | 23 | 24 | <%= cert %> 25 | 26 | <%= location %> 27 | <%= location %> 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /templates/federationServerServiceWsdl.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /templates/form.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | Working... 4 | 5 | 6 |
7 | 8 | 11 | 12 | 17 |
18 | 21 | 22 | -------------------------------------------------------------------------------- /templates/form_el.ejs: -------------------------------------------------------------------------------- 1 |
2 | 3 | 6 | 7 | 12 |
-------------------------------------------------------------------------------- /templates/metadata.ejs: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | <%= pem %> 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | <% claimTypes.forEach(function(ct) {%> 20 | 24 | <%= ct.displayName %> 25 | <%= ct.description %> 26 | 27 | <% }); %> 28 | 29 | 30 | <% if (mexEndpoint) { %> 31 | 32 | 33 |
https://test-adfs.auth0.com/adfs/services/trust/2005/certificatemixed
34 | 35 | 36 | 37 | 38 |
<%= mexEndpoint %>
39 |
40 |
41 |
42 |
43 |
44 |
45 | <% } %> 46 | 47 | 48 |
<%= endpoint %>
49 |
50 |
51 |
-------------------------------------------------------------------------------- /templates/soapFault.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | env:Sender 6 | 7 | <%= code %> 8 | 9 | 10 | 11 | <%= description %> 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /test/custom_form.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Working... 4 | 5 | 6 |
7 | 8 | 11 | 12 | 17 |
18 | 21 | 22 | -------------------------------------------------------------------------------- /test/encoders.tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const encoders = require('../lib/encoders'); 5 | const fixtures = require('./fixture/server'); 6 | 7 | describe('encoders', function () { 8 | describe('thumbprint', function () { 9 | it('should return the thumbprint in all caps', function () { 10 | const certThumbprint = encoders.thumbprint(fixtures.credentials.cert); 11 | assert.equal(certThumbprint, '499FDF1C2218A99C8595AAC2FD95CE36F0A6D59D'); 12 | }); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /test/federationServerService.tests.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var server = require('./fixture/server'); 3 | var request = require('request'); 4 | var xmldom = require('xmldom'); 5 | 6 | describe('wsfed federationserverservice', function () { 7 | before(function (done) { 8 | server.start(done); 9 | }); 10 | 11 | after(function (done) { 12 | server.close(done); 13 | }); 14 | 15 | var doc; 16 | 17 | before(function (done) { 18 | request.get({ 19 | jar: request.jar(), 20 | uri: 'http://localhost:5050/wsfed/adfs/fs/federationserverservice.asmx' 21 | }, function (err, response, b){ 22 | if(err) return done(err); 23 | doc = new xmldom.DOMParser().parseFromString(b).documentElement; 24 | done(); 25 | }); 26 | }); 27 | 28 | it('should have the location field', function () { 29 | var location = doc.getElementsByTagName('soap:address')[0] 30 | .getAttribute('location'); 31 | expect(location) 32 | .to.equal('http://localhost:5050/wsfed/adfs/fs/federationserverservice.asmx'); 33 | }); 34 | 35 | it('should have the wsdl url', function () { 36 | var location = doc.getElementsByTagName('wsdl:import')[0] 37 | .getAttribute('location'); 38 | expect(location) 39 | .to.equal('http://localhost:5050/wsfed/adfs/fs/federationserverservice.asmx?wsdl=wsdl0'); 40 | }); 41 | 42 | describe('when loading wsdl', function () { 43 | var doc; 44 | 45 | before(function (done) { 46 | request.get({ 47 | jar: request.jar(), 48 | uri: 'http://localhost:5050/wsfed/adfs/fs/federationserverservice.asmx?wsdl=wsdl0' 49 | }, function (err, response, b){ 50 | if(err) return done(err); 51 | doc = new xmldom.DOMParser().parseFromString(b).documentElement; 52 | done(); 53 | }); 54 | }); 55 | 56 | it('should have have portType', function(){ 57 | var portType = doc.getElementsByTagName('wsdl:portType')[0] 58 | .getAttribute('name'); 59 | 60 | expect(portType) 61 | .to.equal('ITrustInformationContract'); 62 | }); 63 | }); 64 | 65 | describe('when posting to the thumbprint endpoint', function () { 66 | var doc; 67 | 68 | before(function (done) { 69 | request.post({ 70 | jar: request.jar(), 71 | uri: 'http://localhost:5050/wsfed/adfs/fs/federationserverservice.asmx' 72 | }, function (err, response, b){ 73 | if(err) return done(err); 74 | //not sure how to test this yet... 75 | doc = new xmldom.DOMParser().parseFromString(b).documentElement; 76 | done(); 77 | }); 78 | }); 79 | 80 | it('should have have portType', function(){ 81 | // var portType = doc.getElementsByTagName('wsdl:portType')[0] 82 | // .getAttribute('name'); 83 | 84 | // expect(portType) 85 | // .to.equal('ITrustInformationContract'); 86 | }); 87 | }); 88 | }); -------------------------------------------------------------------------------- /test/fixture/dsaparam.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN DSA PARAMETERS----- 2 | MIICLAKCAQEAsGFwTeXBTz60jrVTMQDt8p7vUclkmmjHCU2ttvg5emUNxNVto+2g 3 | YlhrI+EeV6pMdO+RC6GEq5aohbqIdwI3qdo7j3QRBp/WiWxaCXVnT//w25TgoXrR 4 | TF2ghj0YhNPgtlELbLkrCRwrfHxUvhKc0OWvE5KXxVAvgRbL1czb9CvWgCU0fb5u 5 | MHjFzplaCUoEuBU9WDPjsboaW2u2R+XfnDAkDkIZNxSGJEa/WKUc77iNhYdmNMBM 6 | cCF59SoHL58g3uRUX0bbEscGskknp3TBLDAizx9ecEqZitz7kFPFdjm4Eb4mQReY 7 | j04FcSUkfLM6ZlaH1Lti5NovJdYP8txn5QIhANd6pY8xdBsMjFUrEoMqByTqrPKz 8 | 6qbMo9q7o/SY2Xl9AoIBAF4t5BbXg4huyLTE9kuG2cpP68YWJd6NOeqkrCNOUsea 9 | TTol+2j8QeQCrHRnGsgo5C/9PqN75Yzyq3p0UIh3LAQRY2PSnwGStxUI9EFsuVhb 10 | 5y7ZoOwB3p8Yl6N9WX/+e15gU5QBoEeTg5eEOYg9ZS+G25OWPseI56Ivfd01qQNT 11 | eM23UeTTOIWY/HCyXFPUNnSxgJrhxyjeXtYMHwSXUKjq1ifpSoqMzbdfVqR5UoS5 12 | Uj0o7+3Qb0KQFKBwKeiyHUwM0HD31a+I3IUSbBAUBC1mIYbokYNSctU65lCzxf+g 13 | E4QFTpW6zWuqVLfx9Pdqe1EKiJGByHIn5k/O0Bq03ZE= 14 | -----END DSA PARAMETERS----- 15 | -------------------------------------------------------------------------------- /test/fixture/server.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var http = require('http'); 3 | var wsfed = require('../../lib'); 4 | var xtend = require('xtend'); 5 | var fs = require('fs'); 6 | var path = require('path'); 7 | 8 | var fakeUser = { 9 | id: '12334444444', 10 | displayName: 'José Romaniello', 11 | name: { 12 | familyName: 'Romaniello', 13 | givenName: 'José' 14 | }, 15 | emails: [ 16 | { 17 | type: 'work', 18 | value: 'jfr@jfr.com' 19 | } 20 | ] 21 | }; 22 | 23 | var credentials = { 24 | cert: fs.readFileSync(path.join(__dirname, 'wsfed.test-cert.pem')), 25 | key: fs.readFileSync(path.join(__dirname, 'wsfed.test-cert.key')), 26 | pkcs7: fs.readFileSync(path.join(__dirname, 'wsfed.test-cert.pb7')) 27 | }; 28 | 29 | module.exports.options = {}; 30 | 31 | module.exports.start = function(options, callback){ 32 | module.exports.options = options; 33 | if (typeof options === 'function') { 34 | callback = options; 35 | module.exports.options = {}; 36 | } 37 | 38 | if (!options.credentials) { 39 | options.credentials = credentials; 40 | } 41 | 42 | var app = express(); 43 | 44 | app.use(function(req,res,next){ 45 | req.user = fakeUser; 46 | next(); 47 | }); 48 | 49 | app.get('/wsfed/FederationMetadata/2007-06/FederationMetadata.xml', 50 | wsfed.metadata({ 51 | cert: options.credentials.cert, 52 | issuer: 'fixture-test' 53 | })); 54 | 55 | app.get('/wsfed/adfs/fs/federationserverservice.asmx', 56 | wsfed.federationServerService.wsdl); 57 | 58 | app.post('/wsfed/adfs/fs/federationserverservice.asmx', 59 | wsfed.federationServerService.thumbprint({ 60 | pkcs7: options.credentials.pkcs7, 61 | cert: options.credentials.cert 62 | })); 63 | 64 | function getPostURL (wtrealm, wreply, req, callback) { 65 | if(!wreply || wreply == 'http://office.google.com'){ 66 | return callback(null, 'http://office.google.com'); 67 | } 68 | return callback(); 69 | } 70 | 71 | //configure wsfed middleware 72 | app.get('/wsfed', function(req, res, next) { 73 | wsfed.auth(xtend({}, { 74 | issuer: 'fixture-test', 75 | getPostURL: getPostURL, 76 | cert: options.credentials.cert, 77 | key: options.credentials.key, 78 | }, module.exports.options))(req, res, function(err){ 79 | if (err) { 80 | return res.status(400).send(err.message); 81 | } 82 | next(); 83 | }) 84 | }); 85 | 86 | var server = http.createServer(app).listen(5050, callback); 87 | module.exports.close = server.close.bind(server); 88 | }; 89 | 90 | module.exports.fakeUser = fakeUser; 91 | module.exports.credentials = credentials; 92 | -------------------------------------------------------------------------------- /test/fixture/wsfed.test-cert.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEAvtH4wKLYlIXZlfYQFJtXZVC3fD8XMarzwvb/fHUyJ6NvNStN 3 | +H7GHp3/QhZbSaRyqK5hu5xXtFLgnI0QG8oE1NlXbczjH45LeHWhPIdc2uHSpzXi 4 | c78kOugMY1vng4J10PF6+T2FNaiv0iXeIQq9xbwwPYpflViQyJnzGCIZ7VGan6Gb 5 | RKzyTKcB58yx24pJq+CviLXEY52TIW1l5imcjGvLtlCp1za9qBZa4XGoVqHi1kRX 6 | kdDSHty6lZWj3KxoRvTbiaBCH+75U7rifS6fR9lqjWE57bCGoz7+BBu9YmPKtI1K 7 | kyHFqWpxaJc/AKf9xgg+UumeqVcirUmAsHJrMwIDAQABAoIBAQCYKw05YSNhXVPk 8 | eHLeW/pXuwR3OkCexPrakOmwMC0s2vIF7mChN0d6hvhVlUp68X7V8SnS2JxAGo8v 9 | iHY+Et3DdwZ3cxnzwh+BEhzgDfoIOmkoGppZPyX/K6klWtbGUrTtSISOWXbvEXQU 10 | G0qGAvDOzIGTsdMDX7slnU70Ac23JybPY5qBSiE+ky8U4dm2fUHMroWub4QP5vA/ 11 | nqyWqX2FB/MEAbcujaknDQrFCtbmtUYlBbJCKGd9V3cGEqp6H7oH+ah2ofMc91gJ 12 | mCHk3YyWZB/bcVXH3CA+s1ywvCOVDBZ3Nw7Pt9zIcv6Rl9UKIy+Nx0QjXxR90Hla 13 | Tr0GHIShAoGBAPsD7uXm+0ksnGyKRYgvlVad8Z8FUFT6bf4B+vboDbx40FO8O/5V 14 | PraBPC5z8YRSBOQ/WfccPQzakkA28F2pXlRpXu5JcErVWnyyUiKpX5sw6iPenQR2 15 | JO9hY/GFbKiwUhVHpvWMcXFqFLSQu2A86jPnFFEfG48ZT4IhTzINKJVZAoGBAMKc 16 | B3YGfVfY9qiRFXzYRdSRLg5c8p/HzuWwXc9vfJ4kQTDkPXe/+nqD67rzeT54uVec 17 | jKoIrsCu4BfEaoyvOT+1KmUfdEpBgYZuuEC4CZf7dgKbXOpPVvZDMyJ/e7HyqTpw 18 | mvIYJLPm2fNAcAsnbrNX5mhLwwzEIltbplUUeRdrAoGBAKhZgPYsLkhrZRXevreR 19 | wkTvdUfD1pbHxtFfHqROCjhnhsFCM7JmFcNtdaFqHYczQxiZ7IqxI7jlNsVek2Md 20 | 3qgaa5LBKlDmOuP67N9WXUrGSaJ5ATIm0qrB1Lf9VlzktIiVH8L7yHHaRby8fQ8U 21 | i7b3ukaV6HPW895A3M6iyJ8xAoGAInp4S+3MaTL0SFsj/nFmtcle6oaHKc3BlyoP 22 | BMBQyMfNkPbu+PdXTjtvGTknouzKkX4X4cwWAec5ppxS8EffEa1sLGxNMxa19vZI 23 | yJaShI21k7Ko3I5f7tNrDNKfPKCsYMEwgnHKluDwfktNTnyW/Uk2dgXuMaXSHHN5 24 | XZt59K8CgYArGVOWK7LUmf3dkTIs3tXBm4/IMtUZmWmcP9C8Xe/Dg/IdQhK5CIx4 25 | VXl8rgZNeX/5/4nJ8Q3LrdLau1Iz620trNRGU6sGMs3x4WQbSq93RRbFzfG1oK74 26 | IOo5yIBxImQOSk5jz31gF9RJb15SDBIxonuWv8qAERyUfvrmEwR0kg== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /test/fixture/wsfed.test-cert.pb7: -------------------------------------------------------------------------------- 1 | -----BEGIN PKCS7----- 2 | MIID5gYJKoZIhvcNAQcCoIID1zCCA9MCAQExADALBgkqhkiG9w0BBwGgggO5MIID 3 | tTCCAp2gAwIBAgIJAMKR/NsyfcazMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYT 4 | AkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRn 5 | aXRzIFB0eSBMdGQwHhcNMTIxMTEyMjM0MzQxWhcNMTYxMjIxMjM0MzQxWjBFMQsw 6 | CQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJu 7 | ZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC 8 | AQEAvtH4wKLYlIXZlfYQFJtXZVC3fD8XMarzwvb/fHUyJ6NvNStN+H7GHp3/QhZb 9 | SaRyqK5hu5xXtFLgnI0QG8oE1NlXbczjH45LeHWhPIdc2uHSpzXic78kOugMY1vn 10 | g4J10PF6+T2FNaiv0iXeIQq9xbwwPYpflViQyJnzGCIZ7VGan6GbRKzyTKcB58yx 11 | 24pJq+CviLXEY52TIW1l5imcjGvLtlCp1za9qBZa4XGoVqHi1kRXkdDSHty6lZWj 12 | 3KxoRvTbiaBCH+75U7rifS6fR9lqjWE57bCGoz7+BBu9YmPKtI1KkyHFqWpxaJc/ 13 | AKf9xgg+UumeqVcirUmAsHJrMwIDAQABo4GnMIGkMB0GA1UdDgQWBBTs83nkLtoX 14 | FlmBUts3EIxcVvkvcjB1BgNVHSMEbjBsgBTs83nkLtoXFlmBUts3EIxcVvkvcqFJ 15 | pEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoT 16 | GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAMKR/NsyfcazMAwGA1UdEwQFMAMB 17 | Af8wDQYJKoZIhvcNAQEFBQADggEBABw7w/5k4d5dVDgd/OOOmXdaaCIKvt7d3ntl 18 | v1SSvAoKT8d8lt97Dm5RrmefBI13I2yivZg5bfTge4+vAV6VdLFdWeFp1b/FOZkY 19 | Uv6A8o5HW0OWQYVX26zIqBcG2Qrm3reiSl5BLvpj1WSpCsYvs5kaO4vFpMak/ICg 20 | dZD+rxwxf8Vb/6fntKywWSLgwKH3mJ+Z0kRlpq1g1oieiOm1/gpZ35s0YuorXZba 21 | 9ptfLCYSggg/qc3d3d0tbHplKYkwFm7f5ORGHDSD5SJm+gI7RPE+4bO8q79RPAfb 22 | G1UGuJ0b/oigagciHhJp851SQRYf3JuNSc17BnK2L5IEtzjqr+ShADEA 23 | -----END PKCS7----- 24 | -------------------------------------------------------------------------------- /test/fixture/wsfed.test-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDtTCCAp2gAwIBAgIJAMKR/NsyfcazMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV 3 | BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX 4 | aWRnaXRzIFB0eSBMdGQwHhcNMTIxMTEyMjM0MzQxWhcNMTYxMjIxMjM0MzQxWjBF 5 | MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50 6 | ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB 7 | CgKCAQEAvtH4wKLYlIXZlfYQFJtXZVC3fD8XMarzwvb/fHUyJ6NvNStN+H7GHp3/ 8 | QhZbSaRyqK5hu5xXtFLgnI0QG8oE1NlXbczjH45LeHWhPIdc2uHSpzXic78kOugM 9 | Y1vng4J10PF6+T2FNaiv0iXeIQq9xbwwPYpflViQyJnzGCIZ7VGan6GbRKzyTKcB 10 | 58yx24pJq+CviLXEY52TIW1l5imcjGvLtlCp1za9qBZa4XGoVqHi1kRXkdDSHty6 11 | lZWj3KxoRvTbiaBCH+75U7rifS6fR9lqjWE57bCGoz7+BBu9YmPKtI1KkyHFqWpx 12 | aJc/AKf9xgg+UumeqVcirUmAsHJrMwIDAQABo4GnMIGkMB0GA1UdDgQWBBTs83nk 13 | LtoXFlmBUts3EIxcVvkvcjB1BgNVHSMEbjBsgBTs83nkLtoXFlmBUts3EIxcVvkv 14 | cqFJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNV 15 | BAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAMKR/NsyfcazMAwGA1UdEwQF 16 | MAMBAf8wDQYJKoZIhvcNAQEFBQADggEBABw7w/5k4d5dVDgd/OOOmXdaaCIKvt7d 17 | 3ntlv1SSvAoKT8d8lt97Dm5RrmefBI13I2yivZg5bfTge4+vAV6VdLFdWeFp1b/F 18 | OZkYUv6A8o5HW0OWQYVX26zIqBcG2Qrm3reiSl5BLvpj1WSpCsYvs5kaO4vFpMak 19 | /ICgdZD+rxwxf8Vb/6fntKywWSLgwKH3mJ+Z0kRlpq1g1oieiOm1/gpZ35s0Yuor 20 | XZba9ptfLCYSggg/qc3d3d0tbHplKYkwFm7f5ORGHDSD5SJm+gI7RPE+4bO8q79R 21 | PAfbG1UGuJ0b/oigagciHhJp851SQRYf3JuNSc17BnK2L5IEtzjqr+Q= 22 | -----END CERTIFICATE----- 23 | -------------------------------------------------------------------------------- /test/fixture/wsfed.test-cert.pub: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvtH4wKLYlIXZlfYQFJtX 3 | ZVC3fD8XMarzwvb/fHUyJ6NvNStN+H7GHp3/QhZbSaRyqK5hu5xXtFLgnI0QG8oE 4 | 1NlXbczjH45LeHWhPIdc2uHSpzXic78kOugMY1vng4J10PF6+T2FNaiv0iXeIQq9 5 | xbwwPYpflViQyJnzGCIZ7VGan6GbRKzyTKcB58yx24pJq+CviLXEY52TIW1l5imc 6 | jGvLtlCp1za9qBZa4XGoVqHi1kRXkdDSHty6lZWj3KxoRvTbiaBCH+75U7rifS6f 7 | R9lqjWE57bCGoz7+BBu9YmPKtI1KkyHFqWpxaJc/AKf9xgg+UumeqVcirUmAsHJr 8 | MwIDAQAB 9 | -----END PUBLIC KEY----- -------------------------------------------------------------------------------- /test/fixture/wsfed.test-dsa-key.key: -------------------------------------------------------------------------------- 1 | -----BEGIN DSA PRIVATE KEY----- 2 | MIIDVgIBAAKCAQEAsGFwTeXBTz60jrVTMQDt8p7vUclkmmjHCU2ttvg5emUNxNVt 3 | o+2gYlhrI+EeV6pMdO+RC6GEq5aohbqIdwI3qdo7j3QRBp/WiWxaCXVnT//w25Tg 4 | oXrRTF2ghj0YhNPgtlELbLkrCRwrfHxUvhKc0OWvE5KXxVAvgRbL1czb9CvWgCU0 5 | fb5uMHjFzplaCUoEuBU9WDPjsboaW2u2R+XfnDAkDkIZNxSGJEa/WKUc77iNhYdm 6 | NMBMcCF59SoHL58g3uRUX0bbEscGskknp3TBLDAizx9ecEqZitz7kFPFdjm4Eb4m 7 | QReYj04FcSUkfLM6ZlaH1Lti5NovJdYP8txn5QIhANd6pY8xdBsMjFUrEoMqByTq 8 | rPKz6qbMo9q7o/SY2Xl9AoIBAF4t5BbXg4huyLTE9kuG2cpP68YWJd6NOeqkrCNO 9 | UseaTTol+2j8QeQCrHRnGsgo5C/9PqN75Yzyq3p0UIh3LAQRY2PSnwGStxUI9EFs 10 | uVhb5y7ZoOwB3p8Yl6N9WX/+e15gU5QBoEeTg5eEOYg9ZS+G25OWPseI56Ivfd01 11 | qQNTeM23UeTTOIWY/HCyXFPUNnSxgJrhxyjeXtYMHwSXUKjq1ifpSoqMzbdfVqR5 12 | UoS5Uj0o7+3Qb0KQFKBwKeiyHUwM0HD31a+I3IUSbBAUBC1mIYbokYNSctU65lCz 13 | xf+gE4QFTpW6zWuqVLfx9Pdqe1EKiJGByHIn5k/O0Bq03ZECggEAHeZfC95qrzkg 14 | eK5z/VqbM4+pEty6+AZ2MKWK7UiqgVVI4BgH7I28DNRhDTIx0njjb8280yaQNhlu 15 | eztIrWInqmuZ7Ypcg8qIV6dioVvNKzEeS/X4VdOdOi2YS1YMRBNqQ1k+LV9qpqGF 16 | aY4C9gEC7aS7RtB+s6BTdTW0pa/Y+jS1pANmz+OzBYMGXQtD8R7AFa+yZWOUgYzR 17 | kMh73uyFW3+85kJzGO7XjBVfaEirdDHKi/s9RrIjEr+WFshr5frre0p6xbX+67Nx 18 | CIkMFp0Ebd0WNA92qxspE8va77PW3l6p82tYST9H846G0T+E6RSRM61+A7A8Jlnx 19 | BT+1QZzBlgIhAMLnMXvqqZkga0Trce29Bjf4dzTybXpQ/fCawAMTS+DI 20 | -----END DSA PRIVATE KEY----- 21 | -------------------------------------------------------------------------------- /test/fixture/wsfed.test-dsa-key.pb7: -------------------------------------------------------------------------------- 1 | -----BEGIN PKCS7----- 2 | MIIFHgYJKoZIhvcNAQcCoIIFDzCCBQsCAQExADALBgkqhkiG9w0BBwGgggTxMIIE 3 | 7TCCBJICCQDcrO4UA65FWTALBglghkgBZQMEAwIwgYUxCzAJBgNVBAYTAlVTMREw 4 | DwYDVQQIDAhpbnNlY3VyZTERMA8GA1UEBwwIaW5zZWN1cmUxETAPBgNVBAoMCGlu 5 | c2VjdXJlMREwDwYDVQQLDAhpbnNlY3VyZTERMA8GA1UEAwwIaW5zZWN1cmUxFzAV 6 | BgkqhkiG9w0BCQEWCGluc2VjdXJlMB4XDTIzMDEwNDE2NDMzNVoXDTIzMDIwMzE2 7 | NDMzNVowgYUxCzAJBgNVBAYTAlVTMREwDwYDVQQIDAhpbnNlY3VyZTERMA8GA1UE 8 | BwwIaW5zZWN1cmUxETAPBgNVBAoMCGluc2VjdXJlMREwDwYDVQQLDAhpbnNlY3Vy 9 | ZTERMA8GA1UEAwwIaW5zZWN1cmUxFzAVBgkqhkiG9w0BCQEWCGluc2VjdXJlMIID 10 | RjCCAjkGByqGSM44BAEwggIsAoIBAQCwYXBN5cFPPrSOtVMxAO3ynu9RyWSaaMcJ 11 | Ta22+Dl6ZQ3E1W2j7aBiWGsj4R5Xqkx075ELoYSrlqiFuoh3Ajep2juPdBEGn9aJ 12 | bFoJdWdP//DblOChetFMXaCGPRiE0+C2UQtsuSsJHCt8fFS+EpzQ5a8TkpfFUC+B 13 | FsvVzNv0K9aAJTR9vm4weMXOmVoJSgS4FT1YM+Oxuhpba7ZH5d+cMCQOQhk3FIYk 14 | Rr9YpRzvuI2Fh2Y0wExwIXn1KgcvnyDe5FRfRtsSxwaySSendMEsMCLPH15wSpmK 15 | 3PuQU8V2ObgRviZBF5iPTgVxJSR8szpmVofUu2Lk2i8l1g/y3GflAiEA13qljzF0 16 | GwyMVSsSgyoHJOqs8rPqpsyj2ruj9JjZeX0CggEAXi3kFteDiG7ItMT2S4bZyk/r 17 | xhYl3o056qSsI05Sx5pNOiX7aPxB5AKsdGcayCjkL/0+o3vljPKrenRQiHcsBBFj 18 | Y9KfAZK3FQj0QWy5WFvnLtmg7AHenxiXo31Zf/57XmBTlAGgR5ODl4Q5iD1lL4bb 19 | k5Y+x4jnoi993TWpA1N4zbdR5NM4hZj8cLJcU9Q2dLGAmuHHKN5e1gwfBJdQqOrW 20 | J+lKiozNt19WpHlShLlSPSjv7dBvQpAUoHAp6LIdTAzQcPfVr4jchRJsEBQELWYh 21 | huiRg1Jy1TrmULPF/6AThAVOlbrNa6pUt/H092p7UQqIkYHIcifmT87QGrTdkQOC 22 | AQUAAoIBAB3mXwveaq85IHiuc/1amzOPqRLcuvgGdjCliu1IqoFVSOAYB+yNvAzU 23 | YQ0yMdJ442/NvNMmkDYZbns7SK1iJ6prme2KXIPKiFenYqFbzSsxHkv1+FXTnTot 24 | mEtWDEQTakNZPi1faqahhWmOAvYBAu2ku0bQfrOgU3U1tKWv2Po0taQDZs/jswWD 25 | Bl0LQ/EewBWvsmVjlIGM0ZDIe97shVt/vOZCcxju14wVX2hIq3Qxyov7PUayIxK/ 26 | lhbIa+X663tKesW1/uuzcQiJDBadBG3dFjQPdqsbKRPL2u+z1t5eqfNrWEk/R/OO 27 | htE/hOkUkTOtfgOwPCZZ8QU/tUGcwZYwCwYJYIZIAWUDBAMCA0gAMEUCIAbfAqv6 28 | RkuMaY7MkBrpIzqSBfuBheTgwRxP0LRH7FFbAiEAril8ypDpN//mfyXmV1Ab5v1b 29 | g1L9TqgDJse/6YkonjKhADEA 30 | -----END PKCS7----- 31 | -------------------------------------------------------------------------------- /test/fixture/wsfed.test-dsa-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIE7TCCBJICCQDcrO4UA65FWTALBglghkgBZQMEAwIwgYUxCzAJBgNVBAYTAlVT 3 | MREwDwYDVQQIDAhpbnNlY3VyZTERMA8GA1UEBwwIaW5zZWN1cmUxETAPBgNVBAoM 4 | CGluc2VjdXJlMREwDwYDVQQLDAhpbnNlY3VyZTERMA8GA1UEAwwIaW5zZWN1cmUx 5 | FzAVBgkqhkiG9w0BCQEWCGluc2VjdXJlMB4XDTIzMDEwNDE2NDMzNVoXDTIzMDIw 6 | MzE2NDMzNVowgYUxCzAJBgNVBAYTAlVTMREwDwYDVQQIDAhpbnNlY3VyZTERMA8G 7 | A1UEBwwIaW5zZWN1cmUxETAPBgNVBAoMCGluc2VjdXJlMREwDwYDVQQLDAhpbnNl 8 | Y3VyZTERMA8GA1UEAwwIaW5zZWN1cmUxFzAVBgkqhkiG9w0BCQEWCGluc2VjdXJl 9 | MIIDRjCCAjkGByqGSM44BAEwggIsAoIBAQCwYXBN5cFPPrSOtVMxAO3ynu9RyWSa 10 | aMcJTa22+Dl6ZQ3E1W2j7aBiWGsj4R5Xqkx075ELoYSrlqiFuoh3Ajep2juPdBEG 11 | n9aJbFoJdWdP//DblOChetFMXaCGPRiE0+C2UQtsuSsJHCt8fFS+EpzQ5a8TkpfF 12 | UC+BFsvVzNv0K9aAJTR9vm4weMXOmVoJSgS4FT1YM+Oxuhpba7ZH5d+cMCQOQhk3 13 | FIYkRr9YpRzvuI2Fh2Y0wExwIXn1KgcvnyDe5FRfRtsSxwaySSendMEsMCLPH15w 14 | SpmK3PuQU8V2ObgRviZBF5iPTgVxJSR8szpmVofUu2Lk2i8l1g/y3GflAiEA13ql 15 | jzF0GwyMVSsSgyoHJOqs8rPqpsyj2ruj9JjZeX0CggEAXi3kFteDiG7ItMT2S4bZ 16 | yk/rxhYl3o056qSsI05Sx5pNOiX7aPxB5AKsdGcayCjkL/0+o3vljPKrenRQiHcs 17 | BBFjY9KfAZK3FQj0QWy5WFvnLtmg7AHenxiXo31Zf/57XmBTlAGgR5ODl4Q5iD1l 18 | L4bbk5Y+x4jnoi993TWpA1N4zbdR5NM4hZj8cLJcU9Q2dLGAmuHHKN5e1gwfBJdQ 19 | qOrWJ+lKiozNt19WpHlShLlSPSjv7dBvQpAUoHAp6LIdTAzQcPfVr4jchRJsEBQE 20 | LWYhhuiRg1Jy1TrmULPF/6AThAVOlbrNa6pUt/H092p7UQqIkYHIcifmT87QGrTd 21 | kQOCAQUAAoIBAB3mXwveaq85IHiuc/1amzOPqRLcuvgGdjCliu1IqoFVSOAYB+yN 22 | vAzUYQ0yMdJ442/NvNMmkDYZbns7SK1iJ6prme2KXIPKiFenYqFbzSsxHkv1+FXT 23 | nTotmEtWDEQTakNZPi1faqahhWmOAvYBAu2ku0bQfrOgU3U1tKWv2Po0taQDZs/j 24 | swWDBl0LQ/EewBWvsmVjlIGM0ZDIe97shVt/vOZCcxju14wVX2hIq3Qxyov7PUay 25 | IxK/lhbIa+X663tKesW1/uuzcQiJDBadBG3dFjQPdqsbKRPL2u+z1t5eqfNrWEk/ 26 | R/OOhtE/hOkUkTOtfgOwPCZZ8QU/tUGcwZYwCwYJYIZIAWUDBAMCA0gAMEUCIAbf 27 | Aqv6RkuMaY7MkBrpIzqSBfuBheTgwRxP0LRH7FFbAiEAril8ypDpN//mfyXmV1Ab 28 | 5v1bg1L9TqgDJse/6YkonjI= 29 | -----END CERTIFICATE----- 30 | -------------------------------------------------------------------------------- /test/fixture/wsfed.test-dsa-key.pub: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIDRjCCAjkGByqGSM44BAEwggIsAoIBAQCwYXBN5cFPPrSOtVMxAO3ynu9RyWSa 3 | aMcJTa22+Dl6ZQ3E1W2j7aBiWGsj4R5Xqkx075ELoYSrlqiFuoh3Ajep2juPdBEG 4 | n9aJbFoJdWdP//DblOChetFMXaCGPRiE0+C2UQtsuSsJHCt8fFS+EpzQ5a8TkpfF 5 | UC+BFsvVzNv0K9aAJTR9vm4weMXOmVoJSgS4FT1YM+Oxuhpba7ZH5d+cMCQOQhk3 6 | FIYkRr9YpRzvuI2Fh2Y0wExwIXn1KgcvnyDe5FRfRtsSxwaySSendMEsMCLPH15w 7 | SpmK3PuQU8V2ObgRviZBF5iPTgVxJSR8szpmVofUu2Lk2i8l1g/y3GflAiEA13ql 8 | jzF0GwyMVSsSgyoHJOqs8rPqpsyj2ruj9JjZeX0CggEAXi3kFteDiG7ItMT2S4bZ 9 | yk/rxhYl3o056qSsI05Sx5pNOiX7aPxB5AKsdGcayCjkL/0+o3vljPKrenRQiHcs 10 | BBFjY9KfAZK3FQj0QWy5WFvnLtmg7AHenxiXo31Zf/57XmBTlAGgR5ODl4Q5iD1l 11 | L4bbk5Y+x4jnoi993TWpA1N4zbdR5NM4hZj8cLJcU9Q2dLGAmuHHKN5e1gwfBJdQ 12 | qOrWJ+lKiozNt19WpHlShLlSPSjv7dBvQpAUoHAp6LIdTAzQcPfVr4jchRJsEBQE 13 | LWYhhuiRg1Jy1TrmULPF/6AThAVOlbrNa6pUt/H092p7UQqIkYHIcifmT87QGrTd 14 | kQOCAQUAAoIBAB3mXwveaq85IHiuc/1amzOPqRLcuvgGdjCliu1IqoFVSOAYB+yN 15 | vAzUYQ0yMdJ442/NvNMmkDYZbns7SK1iJ6prme2KXIPKiFenYqFbzSsxHkv1+FXT 16 | nTotmEtWDEQTakNZPi1faqahhWmOAvYBAu2ku0bQfrOgU3U1tKWv2Po0taQDZs/j 17 | swWDBl0LQ/EewBWvsmVjlIGM0ZDIe97shVt/vOZCcxju14wVX2hIq3Qxyov7PUay 18 | IxK/lhbIa+X663tKesW1/uuzcQiJDBadBG3dFjQPdqsbKRPL2u+z1t5eqfNrWEk/ 19 | R/OOhtE/hOkUkTOtfgOwPCZZ8QU/tUGcwZY= 20 | -----END PUBLIC KEY----- 21 | -------------------------------------------------------------------------------- /test/fixture/wsfed.test-insercure-key.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXgIBAAKBgQC1s8FGid2ncS6x25oD5OIDQmHxgZJMnlhsDGigJY4J/fWgajTD 3 | b0kWW8X8RaERWaD8Qc7IDiuSpdCJJ2ipGfERF3dI5grvF2oOPmAaT3tQlnBDlt09 4 | agBEXLOSnf4sjSL1716neMCAqeyOv2Wz4x5+PHXOTP77v/CKV8o+PxAPOwIDAQAB 5 | AoGAK5pyZCoLbfYkPXw0boDLl/qjJGByqj5Je8xSgzPf7RfrjM5P0SJwiJQHY+2V 6 | pGM81aw5Ihr8a9msvG0fmYznI0pYma4pXto4GrfwWK6bA3+K8FttXv8vsP/YIkVU 7 | VIs7he40e21fdbXKSGIBgkYHuPIWP39TQ+Vya8m7Q2VBtMECQQDZ1RuhsU6iKUCN 8 | ZkuopWDUIuquFfkC3EjqddqB85cXa82Rv1alnw8YvoVyxTMiO2PUBcrLBYt8/rSD 9 | F5/iEvYhAkEA1YoFLcWlSGzJdVlx82UU0HkSKQfysiMd0O0w+wEH5kI3O1GDnRcj 10 | cjg9WdBOXXZ3RotNTTjaXU83gtYNnPVh2wJBAJjqRultBJxFhTE93GLZJW4FzOTE 11 | O/fMF8uvS+Lp0uj4Hmjv/q1pSTm/lQKKeGjRdDTElCWNEOyACMv166DSWsECQQCV 12 | 9BRq7i6BS5O1SpiQuV0CgOLV2XT+z1Yyzz3kJfcsJTev+jAYcpU7sm6y0WsWiwh7 13 | i3s/TTta5zQuNq7cmBydAkEAiruiB9yxKinKNusduk2Kifp3oHkKRNO6y1Irt+Hs 14 | mOvlpuv0CnVRMnBCHBUBWe3UVX+agwDfMOHYIWijT/tXpw== 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /test/fixture/wsfed.test-insercure-key.pb7: -------------------------------------------------------------------------------- 1 | -----BEGIN PKCS7----- 2 | MIICtAYJKoZIhvcNAQcCoIICpTCCAqECAQExADALBgkqhkiG9w0BBwGgggKHMIIC 3 | gzCCAewCCQCGW66rIHLP7DANBgkqhkiG9w0BAQsFADCBhTELMAkGA1UEBhMCVVMx 4 | ETAPBgNVBAgMCGluc2VjdXJlMREwDwYDVQQHDAhpbnNlY3VyZTERMA8GA1UECgwI 5 | aW5zZWN1cmUxETAPBgNVBAsMCGluc2VjdXJlMREwDwYDVQQDDAhpbnNlY3VyZTEX 6 | MBUGCSqGSIb3DQEJARYIaW5zZWN1cmUwHhcNMjMwMTA0MTMwMjM5WhcNMjMwMjAz 7 | MTMwMjM5WjCBhTELMAkGA1UEBhMCVVMxETAPBgNVBAgMCGluc2VjdXJlMREwDwYD 8 | VQQHDAhpbnNlY3VyZTERMA8GA1UECgwIaW5zZWN1cmUxETAPBgNVBAsMCGluc2Vj 9 | dXJlMREwDwYDVQQDDAhpbnNlY3VyZTEXMBUGCSqGSIb3DQEJARYIaW5zZWN1cmUw 10 | gZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALWzwUaJ3adxLrHbmgPk4gNCYfGB 11 | kkyeWGwMaKAljgn99aBqNMNvSRZbxfxFoRFZoPxBzsgOK5Kl0IknaKkZ8REXd0jm 12 | Cu8Xag4+YBpPe1CWcEOW3T1qAERcs5Kd/iyNIvXvXqd4wICp7I6/ZbPjHn48dc5M 13 | /vu/8IpXyj4/EA87AgMBAAEwDQYJKoZIhvcNAQELBQADgYEANUT/BWNGX05yOnak 14 | NoP4DaFH25lcC+ykxca36za9xSwtvXmlVTH+xa8AJF2w87Hx0K5WryrhxxpYebN5 15 | 6NEWEzFaGN4sH0h9QpFhD2fjBd18DQ4muvGVDRbeJeTt0jJ02DAI1cPuxcrgQWqC 16 | obAqjqAePjrFpbNr1WLcl2ygC6yhADEA 17 | -----END PKCS7----- 18 | -------------------------------------------------------------------------------- /test/fixture/wsfed.test-insercure-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICgzCCAewCCQCGW66rIHLP7DANBgkqhkiG9w0BAQsFADCBhTELMAkGA1UEBhMC 3 | VVMxETAPBgNVBAgMCGluc2VjdXJlMREwDwYDVQQHDAhpbnNlY3VyZTERMA8GA1UE 4 | CgwIaW5zZWN1cmUxETAPBgNVBAsMCGluc2VjdXJlMREwDwYDVQQDDAhpbnNlY3Vy 5 | ZTEXMBUGCSqGSIb3DQEJARYIaW5zZWN1cmUwHhcNMjMwMTA0MTMwMjM5WhcNMjMw 6 | MjAzMTMwMjM5WjCBhTELMAkGA1UEBhMCVVMxETAPBgNVBAgMCGluc2VjdXJlMREw 7 | DwYDVQQHDAhpbnNlY3VyZTERMA8GA1UECgwIaW5zZWN1cmUxETAPBgNVBAsMCGlu 8 | c2VjdXJlMREwDwYDVQQDDAhpbnNlY3VyZTEXMBUGCSqGSIb3DQEJARYIaW5zZWN1 9 | cmUwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALWzwUaJ3adxLrHbmgPk4gNC 10 | YfGBkkyeWGwMaKAljgn99aBqNMNvSRZbxfxFoRFZoPxBzsgOK5Kl0IknaKkZ8REX 11 | d0jmCu8Xag4+YBpPe1CWcEOW3T1qAERcs5Kd/iyNIvXvXqd4wICp7I6/ZbPjHn48 12 | dc5M/vu/8IpXyj4/EA87AgMBAAEwDQYJKoZIhvcNAQELBQADgYEANUT/BWNGX05y 13 | OnakNoP4DaFH25lcC+ykxca36za9xSwtvXmlVTH+xa8AJF2w87Hx0K5WryrhxxpY 14 | ebN56NEWEzFaGN4sH0h9QpFhD2fjBd18DQ4muvGVDRbeJeTt0jJ02DAI1cPuxcrg 15 | QWqCobAqjqAePjrFpbNr1WLcl2ygC6w= 16 | -----END CERTIFICATE----- 17 | -------------------------------------------------------------------------------- /test/fixture/wsfed.test-insercure-key.pub: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC1s8FGid2ncS6x25oD5OIDQmHx 3 | gZJMnlhsDGigJY4J/fWgajTDb0kWW8X8RaERWaD8Qc7IDiuSpdCJJ2ipGfERF3dI 4 | 5grvF2oOPmAaT3tQlnBDlt09agBEXLOSnf4sjSL1716neMCAqeyOv2Wz4x5+PHXO 5 | TP77v/CKV8o+PxAPOwIDAQAB 6 | -----END PUBLIC KEY----- 7 | -------------------------------------------------------------------------------- /test/interpolate.tests.js: -------------------------------------------------------------------------------- 1 | var interpolate = require('../lib/interpolate'); 2 | var expect = require('chai').expect; 3 | 4 | describe('interpolation template', function () { 5 | it('should work', function () { 6 | var r = interpolate('aaa@@test@@')({ 7 | test:'bbb' 8 | }); 9 | expect(r).to.equal('aaabbb'); 10 | }); 11 | }); -------------------------------------------------------------------------------- /test/jwt.tests.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var expect = require('chai').expect; 4 | var server = require('./fixture/server'); 5 | var request = require('request'); 6 | var cheerio = require('cheerio'); 7 | var jwt = require('jsonwebtoken'); 8 | 9 | // RSA 10 | var credentials = { 11 | cert: fs.readFileSync(path.join(__dirname, '/fixture/wsfed.test-cert.pem')), 12 | key: fs.readFileSync(path.join(__dirname, '/fixture/wsfed.test-cert.key')), 13 | pkcs7: fs.readFileSync(path.join(__dirname, '/fixture/wsfed.test-cert.pb7')) 14 | }; 15 | 16 | // 1024 bits RSA key 17 | var insecure_credentials = { 18 | cert: fs.readFileSync(path.join(__dirname, '/fixture/wsfed.test-insercure-key.pem')), 19 | key: fs.readFileSync(path.join(__dirname, '/fixture/wsfed.test-insercure-key.key')), 20 | pkcs7: fs.readFileSync(path.join(__dirname, '/fixture/wsfed.test-insercure-key.pb7')), 21 | }; 22 | 23 | // DSA 24 | var dsa_credentials = { 25 | cert: fs.readFileSync(path.join(__dirname, '/fixture/wsfed.test-dsa-key.pem')), 26 | key: fs.readFileSync(path.join(__dirname, '/fixture/wsfed.test-dsa-key.key')), 27 | pkcs7: fs.readFileSync(path.join(__dirname, '/fixture/wsfed.test-dsa-key.pb7')), 28 | }; 29 | 30 | describe('wsfed+jwt', function () { 31 | 32 | describe('authorizing', function () { 33 | var body, $, signedAssertion, profile; 34 | 35 | after(function (done) { 36 | server.close(done); 37 | }); 38 | 39 | before(function (done) { 40 | server.start({ 41 | jwt: true 42 | }, function(err) { 43 | request.get({ 44 | jar: request.jar(), 45 | uri: 'http://localhost:5050/wsfed?wa=wsignin1.0&wctx=123&wtrealm=urn:the-super-client-id' 46 | }, function (err, response, b){ 47 | if(err) return done(err); 48 | body = b; 49 | $ = cheerio.load(body); 50 | var signedAssertion = $('input[name="wresult"]').attr('value'); 51 | jwt.verify(signedAssertion, credentials.cert.toString(), function (err, decoded) { 52 | if (err) return done(err); 53 | profile = decoded; 54 | done(); 55 | }); 56 | }); 57 | }); 58 | }); 59 | 60 | it('should have the attributes', function(){ 61 | expect(profile).to.have.property('displayName'); 62 | expect(profile.id).to.equal('12334444444'); 63 | }); 64 | 65 | it('should have jwt attributes', function(){ 66 | expect(profile).to.have.property('aud'); 67 | expect(profile).to.have.property('iss'); 68 | expect(profile).to.have.property('iat'); 69 | }); 70 | 71 | }); 72 | 73 | describe('when using a key and an algorithm that do not match', function () { 74 | 75 | afterEach(function (done) { 76 | server.close(done); 77 | }); 78 | 79 | describe('when not passing jwtAllowInvalidAsymmetricKeyTypes', function () { 80 | 81 | beforeEach(function (done) { 82 | 83 | server.start({ 84 | jwt: true, 85 | // using RSA256 with a DSA key 86 | jwtAlgorithm: 'RS256', 87 | credentials: dsa_credentials 88 | }, function(err) { 89 | if (err) { 90 | done(err); 91 | } 92 | 93 | done(); 94 | }); 95 | }); 96 | 97 | it('should error because of the mismatch between the key and the algorithm', function (done) { 98 | 99 | request.get({ 100 | jar: request.jar(), 101 | uri: 'http://localhost:5050/wsfed?wa=wsignin1.0&wctx=123&wtrealm=urn:the-super-client-id' 102 | }, function (err, response, body) { 103 | if (err) return done(err); 104 | expect(response.statusCode).to.eql(400); 105 | expect(body).to.eql('Unknown key type "dsa".'); 106 | done(); 107 | }); 108 | 109 | }); 110 | }); 111 | 112 | describe('when passing jwtAllowInvalidAsymmetricKeyTypes = true', function () { 113 | 114 | beforeEach(function (done) { 115 | 116 | server.start({ 117 | jwt: true, 118 | // using RSA256 with a DSA key 119 | jwtAlgorithm: 'RS256', 120 | jwtAllowInvalidAsymmetricKeyTypes: true, 121 | credentials: dsa_credentials 122 | }, function(err) { 123 | if (err) { 124 | done(err); 125 | } 126 | 127 | done(); 128 | }); 129 | }); 130 | 131 | it('should allow the mismatch between the key and the algorithm', function (done) { 132 | 133 | request.get({ 134 | jar: request.jar(), 135 | uri: 'http://localhost:5050/wsfed?wa=wsignin1.0&wctx=123&wtrealm=urn:the-super-client-id' 136 | }, function (err, response, body) { 137 | if (err) return done(err); 138 | console.log(body); 139 | expect(response.statusCode).to.eql(200); 140 | done(); 141 | }); 142 | 143 | }); 144 | }); 145 | }); 146 | 147 | describe('when using insecure key sizes', function () { 148 | 149 | afterEach(function (done) { 150 | server.close(done); 151 | }); 152 | 153 | describe('when not passing jwtAllowInsecureKeySizes', function () { 154 | 155 | beforeEach(function (done) { 156 | 157 | server.start({ 158 | jwt: true, 159 | credentials: insecure_credentials 160 | }, function(err) { 161 | if (err) { 162 | done(err); 163 | } 164 | 165 | done(); 166 | }); 167 | }); 168 | 169 | it('should error because of the size of key', function (done) { 170 | 171 | request.get({ 172 | jar: request.jar(), 173 | uri: 'http://localhost:5050/wsfed?wa=wsignin1.0&wctx=123&wtrealm=urn:the-super-client-id' 174 | }, function (err, response, body) { 175 | if (err) return done(err); 176 | expect(response.statusCode).to.eql(400); 177 | expect(body).to.eql('secretOrPrivateKey has a minimum key size of 2048 bits for RS256'); 178 | done(); 179 | }); 180 | 181 | }); 182 | }); 183 | 184 | describe('when passing jwtAllowInsecureKeySizes = true', function () { 185 | 186 | beforeEach(function (done) { 187 | 188 | server.start({ 189 | jwt: true, 190 | jwtAllowInsecureKeySizes: true, 191 | credentials: insecure_credentials 192 | }, function(err) { 193 | if (err) { 194 | done(err); 195 | } 196 | 197 | done(); 198 | }); 199 | }); 200 | 201 | it('should allow the insecure key', function (done) { 202 | 203 | request.get({ 204 | jar: request.jar(), 205 | uri: 'http://localhost:5050/wsfed?wa=wsignin1.0&wctx=123&wtrealm=urn:the-super-client-id' 206 | }, function (err, response, body) { 207 | if (err) return done(err); 208 | expect(response.statusCode).to.eql(200); 209 | done(); 210 | }); 211 | 212 | }); 213 | }); 214 | }); 215 | 216 | describe('authorizing with extra claims', function () { 217 | var body, $, signedAssertion, profile; 218 | 219 | after(function (done) { 220 | server.close(done); 221 | }); 222 | 223 | before(function (done) { 224 | server.start({ 225 | jwt: true, 226 | extendJWT: { extra: 'claim' } 227 | }, function(err) { 228 | request.get({ 229 | jar: request.jar(), 230 | uri: 'http://localhost:5050/wsfed?wa=wsignin1.0&wctx=123&wtrealm=urn:the-super-client-id' 231 | }, function (err, response, b){ 232 | if(err) return done(err); 233 | body = b; 234 | $ = cheerio.load(body); 235 | var signedAssertion = $('input[name="wresult"]').attr('value'); 236 | jwt.verify(signedAssertion, credentials.cert.toString(), function (err, decoded) { 237 | if (err) return done(err); 238 | profile = decoded; 239 | done(); 240 | }); 241 | }); 242 | }); 243 | }); 244 | 245 | it('should have the attributes', function(){ 246 | expect(profile).to.have.property('displayName'); 247 | expect(profile.id).to.equal('12334444444'); 248 | expect(profile).to.have.property('extra'); 249 | expect(profile.extra).to.equal('claim'); 250 | }); 251 | 252 | it('should have jwt attributes', function(){ 253 | expect(profile).to.have.property('aud'); 254 | expect(profile).to.have.property('iss'); 255 | expect(profile).to.have.property('iat'); 256 | }); 257 | 258 | }); 259 | }); 260 | -------------------------------------------------------------------------------- /test/metadata.tests.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var server = require('./fixture/server'); 3 | var request = require('request'); 4 | var xmldom = require('xmldom'); 5 | 6 | function certToPem (cert) { 7 | var pem = /-----BEGIN CERTIFICATE-----([^-]*)-----END CERTIFICATE-----/g.exec(cert.toString()); 8 | if (pem.length > 0) { 9 | return pem[1].replace(/[\n|\r\n]/g, ''); 10 | } 11 | return null; 12 | } 13 | 14 | describe('wsfed metadata', function () { 15 | before(function (done) { 16 | server.start(done); 17 | }); 18 | 19 | after(function (done) { 20 | server.close(done); 21 | }); 22 | 23 | describe('request to metadata', function (){ 24 | var doc, content; 25 | before(function (done) { 26 | request.get({ 27 | jar: request.jar(), 28 | uri: 'http://localhost:5050/wsfed/FederationMetadata/2007-06/FederationMetadata.xml' 29 | }, function (err, response, b){ 30 | if(err) return done(err); 31 | content = b; 32 | doc = new xmldom.DOMParser().parseFromString(b).documentElement; 33 | done(); 34 | }); 35 | }); 36 | 37 | it('sholud have the endpoint url', function(){ 38 | expect(doc.getElementsByTagName('EndpointReference')[0].firstChild.textContent) 39 | .to.equal('http://localhost:5050/wsfed'); 40 | }); 41 | 42 | it('sholud have the claim types', function(){ 43 | expect(doc.getElementsByTagName('auth:ClaimType')) 44 | .to.not.be.empty; 45 | }); 46 | 47 | it('sholud have the issuer', function(){ 48 | expect(doc.getAttribute('entityID')) 49 | .to.equal('fixture-test'); 50 | }); 51 | 52 | it('sholud have the pem', function(){ 53 | expect(doc.getElementsByTagName('X509Certificate')[0].textContent) 54 | .to.equal(certToPem(server.credentials.cert)); 55 | }); 56 | 57 | it('should not contain line breaks', function(){ 58 | expect(content) 59 | .to.not.contain('\n'); 60 | }); 61 | 62 | }); 63 | 64 | describe('request to metadata with proxy', function (){ 65 | var doc, content; 66 | before(function (done) { 67 | request.get({ 68 | jar: request.jar(), 69 | uri: 'http://localhost:5050/wsfed/FederationMetadata/2007-06/FederationMetadata.xml', 70 | headers: { 71 | 'X-Forwarded-Host': 'myserver.com' 72 | } 73 | }, function (err, response, b){ 74 | if(err) return done(err); 75 | content = b; 76 | doc = new xmldom.DOMParser().parseFromString(b).documentElement; 77 | done(); 78 | }); 79 | }); 80 | 81 | it('should have the custom endpoint url', function(){ 82 | expect(doc.getElementsByTagName('EndpointReference')[0].firstChild.textContent) 83 | .to.equal('http://myserver.com/wsfed'); 84 | }); 85 | }); 86 | }); 87 | -------------------------------------------------------------------------------- /test/wsfed-encryption.tests.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var expect = require('chai').expect; 4 | var server = require('./fixture/server'); 5 | var request = require('request'); 6 | var cheerio = require('cheerio'); 7 | var xmlenc = require('xml-encryption'); 8 | var xmlhelper = require('./xmlhelper'); 9 | 10 | var credentials = { 11 | cert: fs.readFileSync(path.join(__dirname, '/fixture/wsfed.test-cert.pem')), 12 | key: fs.readFileSync(path.join(__dirname, '/fixture/wsfed.test-cert.key')), 13 | pkcs7: fs.readFileSync(path.join(__dirname, '/fixture/wsfed.test-cert.pb7')), 14 | pub: fs.readFileSync(path.join(__dirname, '/fixture/wsfed.test-cert.pub')) 15 | }; 16 | 17 | 18 | describe('when dwdw encrypting the assertion', function () { 19 | before(function (done) { 20 | server.start({ 21 | encryptionPublicKey: credentials.pub, 22 | encryptionCert: credentials.cert 23 | }, done); 24 | }); 25 | 26 | after(function (done) { 27 | server.close(done); 28 | }); 29 | 30 | var body, $, encryptedAssertion; 31 | 32 | describe('when encrypting the assertion', function () { 33 | before(function (done) { 34 | request.get({ 35 | jar: request.jar(), 36 | uri: 'http://localhost:5050/wsfed?wa=wsignin1.0&wctx=123&wtrealm=urn:the-super-client-id' 37 | }, function (err, response, b){ 38 | if(err) return done(err); 39 | body = b; 40 | $ = cheerio.load(body); 41 | var wresult = $('input[name="wresult"]').attr('value'); 42 | encryptedAssertion = /(.*)<\/t:RequestedSecurityToken>/.exec(wresult)[1]; 43 | done(); 44 | }); 45 | }); 46 | 47 | it('should contain a form in the result', function(){ 48 | expect(body).to.match(/
(.*)<\/t:RequestedSecurityToken>/.exec(wresult)[1]; 32 | attributes = xmlhelper.getAttributes(signedAssertion); 33 | done(); 34 | }); 35 | }); 36 | 37 | it('should use sha1 as signature algorithm', function(){ 38 | var algorithm = xmlhelper.getSignatureMethodAlgorithm(signedAssertion); 39 | expect(algorithm).to.equal('http://www.w3.org/2000/09/xmldsig#rsa-sha1'); 40 | }); 41 | 42 | it('should use sha1 as digest algorithm', function(){ 43 | var algorithm = xmlhelper.getDigestMethodAlgorithm(signedAssertion); 44 | expect(algorithm).to.equal('http://www.w3.org/2000/09/xmldsig#sha1'); 45 | }); 46 | 47 | }); 48 | }); -------------------------------------------------------------------------------- /test/wsfed.custom_form.tests.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var server = require('./fixture/server'); 3 | var request = require('request'); 4 | var cheerio = require('cheerio'); 5 | var xmlhelper = require('./xmlhelper'); 6 | var fs = require('fs'); 7 | 8 | describe('wsfed', function () { 9 | before(function (done) { 10 | server.start({ 11 | formTemplate: fs.readFileSync(__dirname + '/custom_form.html').toString() 12 | }, done); 13 | }); 14 | 15 | after(function (done) { 16 | server.close(done); 17 | }); 18 | 19 | describe('authorizing', function () { 20 | var body, $, signedAssertion, attributes; 21 | 22 | before(function (done) { 23 | request.get({ 24 | jar: request.jar(), 25 | uri: 'http://localhost:5050/wsfed?wa=wsignin1.0&wctx=123&wtrealm=urn:the-super-client-id' 26 | }, function (err, response, b){ 27 | if(err) return done(err); 28 | body = b; 29 | $ = cheerio.load(body); 30 | var wresult = $('input[name="wresult"]').attr('value'); 31 | signedAssertion = /(.*)<\/t:RequestedSecurityToken>/.exec(wresult)[1]; 32 | attributes = xmlhelper.getAttributes(signedAssertion); 33 | done(); 34 | }); 35 | }); 36 | 37 | it('should contain a form in the result', function(){ 38 | expect(body).to.match(/(.*)<\/t:RequestedSecurityToken>/.exec(wresult)[1]; 109 | 110 | expect(xmlhelper.getAudiences(signedAssertion)[0].textContent) 111 | .to.equal('urn:auth0:superclient'); 112 | 113 | done(); 114 | }); 115 | }); 116 | }); 117 | 118 | describe('when the wctx has ampersand(&)', function (){ 119 | it('should return escaped Context value', function (done) { 120 | var wctx = encodeURIComponent('rm=0&id=passive&ru=%2f'); 121 | 122 | request.get({ 123 | jar: request.jar(), 124 | uri: 'http://localhost:5050/wsfed?wa=wsignin1.0&wctx=' + wctx + '&wtrealm=urn:auth0:superclient' 125 | }, function (err, response, b){ 126 | if(err) return done(err); 127 | var body = b; 128 | var $ = cheerio.load(body); 129 | var wresult = $('input[name="wresult"]').attr('value'); 130 | 131 | expect(wresult.indexOf(' Context="rm=0&id=passive&ru=%2f" ')) 132 | .to.be.above(-1); 133 | 134 | done(); 135 | }); 136 | }); 137 | }); 138 | 139 | describe('when attribute has ampersand(&)', function (){ 140 | it('should return escaped value', function (done) { 141 | server.fakeUser.attribute_with_ampersand = 'http://foo?foo&foo'; 142 | request.get({ 143 | jar: request.jar(), 144 | uri: 'http://localhost:5050/wsfed?wa=wsignin1.0&wtrealm=urn:auth0:superclient' 145 | }, function (err, response, b){ 146 | if(err) return done(err); 147 | var body = b; 148 | var $ = cheerio.load(body); 149 | var wresult = $('input[name="wresult"]').attr('value'); 150 | 151 | expect(wresult.indexOf('http://foo?foo&foo')) 152 | .to.be.above(-1); 153 | 154 | delete server.fakeUser.attribute_with_ampersand; 155 | done(); 156 | }); 157 | }); 158 | }); 159 | 160 | describe('when using an invalid callback url', function () { 161 | it('should return error', function(done){ 162 | request.get({ 163 | jar: request.jar(), 164 | uri: 'http://localhost:5050/wsfed?wa=wsignin1.0&wctx=123&wtrealm=urn:auth0:superclient&wreply=http://google.comcomcom' 165 | }, function (err, response){ 166 | if(err) return done(err); 167 | expect(response.statusCode) 168 | .to.equal(400); 169 | done(); 170 | }); 171 | }); 172 | }); 173 | }); 174 | 175 | -------------------------------------------------------------------------------- /test/wsfed.tests.js: -------------------------------------------------------------------------------- 1 | const expect = require('chai').expect; 2 | const server = require('./fixture/server'); 3 | const request = require('request'); 4 | const cheerio = require('cheerio'); 5 | const xmlhelper = require('./xmlhelper'); 6 | 7 | describe('wsfed', function () { 8 | before(function (done) { 9 | server.start(done); 10 | }); 11 | 12 | after(function (done) { 13 | server.close(done); 14 | }); 15 | 16 | describe('authorizing', function () { 17 | let body, $, signedAssertion, attributes; 18 | 19 | before(function (done) { 20 | request.get({ 21 | jar: request.jar(), 22 | uri: 'http://localhost:5050/wsfed?wa=wsignin1.0&wctx=123&wtrealm=urn:the-super-client-id' 23 | }, function (err, response, b){ 24 | if(err) return done(err); 25 | body = b; 26 | $ = cheerio.load(body); 27 | let wresult = $('input[name="wresult"]').attr('value'); 28 | signedAssertion = /(.*)<\/t:RequestedSecurityToken>/.exec(wresult)[1]; 29 | attributes = xmlhelper.getAttributes(signedAssertion); 30 | done(); 31 | }); 32 | }); 33 | 34 | it('should contain a form in the result', function(){ 35 | expect(body).to.match(/(.*)<\/t:RequestedSecurityToken>/.exec(wresult)[1]; 116 | attributes = xmlhelper.getAttributes(signedAssertion); 117 | done(); 118 | }); 119 | }); 120 | 121 | it('should set name identifier format to the passed auth option', function (){ 122 | const nameIdentifier = xmlhelper.getNameIdentifier(signedAssertion); 123 | const formatAttributeValue = nameIdentifier.getAttribute('Format'); 124 | expect(formatAttributeValue).to.equal(fakeNameIdentifierFomat); 125 | }); 126 | }); 127 | 128 | describe('when the audience has colon(:)', function (){ 129 | it('should work', function (done) { 130 | request.get({ 131 | jar: request.jar(), 132 | uri: 'http://localhost:5050/wsfed?wa=wsignin1.0&wctx=123&wtrealm=urn:auth0:superclient' 133 | }, function (err, response, b){ 134 | if(err) return done(err); 135 | const body = b; 136 | const $ = cheerio.load(body); 137 | const wresult = $('input[name="wresult"]').attr('value'); 138 | const signedAssertion = /(.*)<\/t:RequestedSecurityToken>/.exec(wresult)[1]; 139 | 140 | expect(xmlhelper.getAudiences(signedAssertion)[0].textContent) 141 | .to.equal('urn:auth0:superclient'); 142 | 143 | done(); 144 | }); 145 | }); 146 | }); 147 | 148 | describe('when the wctx has ampersand(&)', function (){ 149 | it('should return escaped Context value', function (done) { 150 | const wctx = encodeURIComponent('rm=0&id=passive&ru=%2f'); 151 | 152 | request.get({ 153 | jar: request.jar(), 154 | uri: 'http://localhost:5050/wsfed?wa=wsignin1.0&wctx=' + wctx + '&wtrealm=urn:auth0:superclient' 155 | }, function (err, response, b){ 156 | if(err) return done(err); 157 | const body = b; 158 | const $ = cheerio.load(body); 159 | const wresult = $('input[name="wresult"]').attr('value'); 160 | 161 | expect(wresult.indexOf(' Context="rm=0&id=passive&ru=%2f" ')) 162 | .to.be.above(-1); 163 | 164 | done(); 165 | }); 166 | }); 167 | }); 168 | 169 | describe('when attribute has ampersand(&)', function (){ 170 | it('should return escaped value', function (done) { 171 | server.fakeUser.attribute_with_ampersand = 'http://foo?foo&foo'; 172 | request.get({ 173 | jar: request.jar(), 174 | uri: 'http://localhost:5050/wsfed?wa=wsignin1.0&wtrealm=urn:auth0:superclient' 175 | }, function (err, response, b){ 176 | if(err) return done(err); 177 | const body = b; 178 | const $ = cheerio.load(body); 179 | const wresult = $('input[name="wresult"]').attr('value'); 180 | 181 | expect(wresult.indexOf('http://foo?foo&foo')) 182 | .to.be.above(-1); 183 | 184 | delete server.fakeUser.attribute_with_ampersand; 185 | done(); 186 | }); 187 | }); 188 | }); 189 | 190 | describe('when using an invalid callback url', function () { 191 | it('should return error', function(done){ 192 | request.get({ 193 | jar: request.jar(), 194 | uri: 'http://localhost:5050/wsfed?wa=wsignin1.0&wctx=123&wtrealm=urn:auth0:superclient&wreply=http://google.comcomcom' 195 | }, function (err, response){ 196 | if(err) return done(err); 197 | expect(response.statusCode) 198 | .to.equal(400); 199 | done(); 200 | }); 201 | }); 202 | }); 203 | 204 | describe('using custom profile mapper', function() { 205 | describe('when NameIdentifier and NameIdentifierFormat have been configured in the profile mapper', function() { 206 | const fakeNameIdentifier = 'fakeNameIdentifier'; 207 | const fakeNameIdentifierFormat = 'fakeNameIdentifierFormat'; 208 | let body, $, signedAssertion, attributes; 209 | 210 | function ProfileMapper(user) { 211 | this.user = user; 212 | } 213 | ProfileMapper.prototype.getClaims = function () { 214 | return this.user; 215 | } 216 | ProfileMapper.prototype.getNameIdentifier = function () { 217 | return { 218 | nameIdentifier: fakeNameIdentifier, 219 | nameIdentifierFormat: fakeNameIdentifierFormat, 220 | }; 221 | } 222 | 223 | before(function () { 224 | server.options = { 225 | profileMapper: function createProfileMapper(user) { 226 | return new ProfileMapper(user); 227 | } 228 | }; 229 | }); 230 | 231 | function createRequest(done) { 232 | request.get({ 233 | jar: request.jar(), 234 | uri: 'http://localhost:5050/wsfed?wa=wsignin1.0&wctx=123&wtrealm=urn:the-super-client-id' 235 | }, function (err, response, b) { 236 | if (err) return done(err); 237 | body = b; 238 | $ = cheerio.load(body); 239 | let wresult = $('input[name="wresult"]').attr('value'); 240 | signedAssertion = /(.*)<\/t:RequestedSecurityToken>/.exec(wresult)[1]; 241 | attributes = xmlhelper.getAttributes(signedAssertion); 242 | done(); 243 | }); 244 | } 245 | 246 | describe('when a name identifier format is passed as an auth option', function() { 247 | 248 | const fakeOptionNameIdentifierFormat = 'urn:oasis:names:tc:SAML:1.1:nameid-format:swfedfakeformat'; 249 | 250 | before(function(done) { 251 | server.options.nameIdentifierFormat = fakeOptionNameIdentifierFormat; 252 | createRequest(done); 253 | }); 254 | 255 | it('should set name identifier', function() { 256 | const nameIdentifierValue = xmlhelper.getNameIdentifier(signedAssertion).textContent; 257 | expect(nameIdentifierValue).to.equal(fakeNameIdentifier); 258 | }); 259 | 260 | it('should set name identifier format', function() { 261 | const nameIdentifier = xmlhelper.getNameIdentifier(signedAssertion); 262 | const formatAttributeValue = nameIdentifier.getAttribute('Format'); 263 | expect(formatAttributeValue).to.equal(fakeNameIdentifierFormat); 264 | }); 265 | 266 | }); 267 | 268 | describe('when a name identifier format is NOT passed as an auth option', function() { 269 | 270 | before(function(done) { 271 | createRequest(done); 272 | }); 273 | 274 | it('should set name identifier', function() { 275 | const nameIdentifierValue = xmlhelper.getNameIdentifier(signedAssertion).textContent; 276 | expect(nameIdentifierValue).to.equal(fakeNameIdentifier); 277 | }); 278 | 279 | it('should set name identifier format', function() { 280 | const nameIdentifier = xmlhelper.getNameIdentifier(signedAssertion); 281 | const formatAttributeValue = nameIdentifier.getAttribute('Format'); 282 | expect(formatAttributeValue).to.equal(fakeNameIdentifierFormat); 283 | }); 284 | 285 | }); 286 | }); 287 | 288 | describe('when NameIdentifier is not found', function(){ 289 | function ProfileMapper(user) { 290 | this.user = user; 291 | } 292 | ProfileMapper.prototype.getClaims = function () { 293 | return this.user; 294 | } 295 | ProfileMapper.prototype.getNameIdentifier = function () { 296 | return null; 297 | } 298 | 299 | before(function () { 300 | server.options = { 301 | profileMapper: function createProfileMapper(user) { 302 | return new ProfileMapper(user) 303 | } 304 | }; 305 | }); 306 | 307 | it('should return an error', function(done){ 308 | request.get({ 309 | jar: request.jar(), 310 | uri: 'http://localhost:5050/wsfed?wa=wsignin1.0&wctx=123&wtrealm=urn:the-super-client-id' 311 | }, function (err, response){ 312 | if(err) return done(err); 313 | expect(response.statusCode).to.equal(400); 314 | expect(response.body).to.equal('No attribute was found to generate the nameIdentifier'); 315 | done(); 316 | }); 317 | }); 318 | }); 319 | }); 320 | }); 321 | 322 | -------------------------------------------------------------------------------- /test/xmlhelper.js: -------------------------------------------------------------------------------- 1 | var xmlCrypto = require('xml-crypto'), 2 | xmldom = require('xmldom'); 3 | 4 | exports.verifySignature = function(assertion, cert) { 5 | var doc = new xmldom.DOMParser().parseFromString(assertion); 6 | var signature = xmlCrypto.xpath(doc, "/*/*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']")[0]; 7 | var sig = new xmlCrypto.SignedXml(null, { idAttribute: 'AssertionID' }); 8 | sig.keyInfoProvider = { 9 | getKeyInfo: function (key) { 10 | return ""; 11 | }, 12 | getKey: function (keyInfo) { 13 | return cert; 14 | } 15 | }; 16 | sig.loadSignature(signature.toString()); 17 | return sig.checkSignature(assertion); 18 | }; 19 | 20 | exports.getIssuer = function(assertion) { 21 | var doc = new xmldom.DOMParser().parseFromString(assertion); 22 | return doc.documentElement.getAttribute('Issuer'); 23 | }; 24 | 25 | exports.getSignatureMethodAlgorithm = function(assertion) { 26 | var doc = new xmldom.DOMParser().parseFromString(assertion); 27 | return doc.documentElement 28 | .getElementsByTagName('SignatureMethod')[0] 29 | .getAttribute('Algorithm'); 30 | }; 31 | 32 | exports.getDigestMethodAlgorithm = function(assertion) { 33 | var doc = new xmldom.DOMParser().parseFromString(assertion); 34 | return doc.documentElement 35 | .getElementsByTagName('DigestMethod')[0] 36 | .getAttribute('Algorithm'); 37 | }; 38 | 39 | exports.getIssueInstant = function(assertion) { 40 | var doc = new xmldom.DOMParser().parseFromString(assertion); 41 | return doc.documentElement.getAttribute('IssueInstant'); 42 | }; 43 | 44 | exports.getConditions = function(assertion) { 45 | var doc = new xmldom.DOMParser().parseFromString(assertion); 46 | return doc.documentElement.getElementsByTagName('saml:Conditions'); 47 | }; 48 | 49 | exports.getAudiences = function(assertion) { 50 | var doc = new xmldom.DOMParser().parseFromString(assertion); 51 | return doc.documentElement 52 | .getElementsByTagName('saml:Conditions')[0] 53 | .getElementsByTagName('saml:AudienceRestrictionCondition')[0] 54 | .getElementsByTagName('saml:Audience'); 55 | }; 56 | 57 | exports.getAttributes = function(assertion) { 58 | var doc = new xmldom.DOMParser().parseFromString(assertion); 59 | return doc.documentElement 60 | .getElementsByTagName('saml:Attribute'); 61 | }; 62 | 63 | exports.getNameIdentifier = function(assertion) { 64 | var doc = new xmldom.DOMParser().parseFromString(assertion); 65 | return doc.documentElement 66 | .getElementsByTagName('saml:NameIdentifier')[0]; 67 | }; --------------------------------------------------------------------------------