diff --git a/.prettierignore b/.prettierignore index 01c96ead..f1565a00 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,3 +2,5 @@ node_modules lib package-lock.json +.eslintcache +.prettierignore diff --git a/README.md b/README.md index 92dfdc0d..70d9cfa9 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,8 @@ passport.use(new SamlStrategy( { path: '/login/callback', entryPoint: 'https://openidp.feide.no/simplesaml/saml2/idp/SSOService.php', - issuer: 'passport-saml' + issuer: 'passport-saml', + cert: 'fake cert', // cert must be provided }, function(profile, done) { findByEmail(profile.email, function(err, user) { @@ -53,7 +54,7 @@ const { MultiSamlStrategy } = require('passport-saml'); passport.use(new MultiSamlStrategy( { - passReqToCallback: true, //makes req available in callback + passReqToCallback: true, // makes req available in callback getSamlOptions: function(request, done) { findProvider(request, function(err, provider) { if (err) { @@ -124,18 +125,18 @@ type Profile = { - **Additional SAML behaviors** - `additionalParams`: dictionary of additional query params to add to all requests; if an object with this key is passed to `authenticate`, the dictionary of additional query params will be appended to those present on the returned URL, overriding any specified by initialization options' additional parameters (`additionalParams`, `additionalAuthorizeParams`, and `additionalLogoutParams`) - `additionalAuthorizeParams`: dictionary of additional query params to add to 'authorize' requests -- `identifierFormat`: if truthy, name identifier format to request from identity provider (default: `urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress`) +- `identifierFormat`: optional name identifier format to request from identity provider (default: `urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress`) - `acceptedClockSkewMs`: Time in milliseconds of skew that is acceptable between client and server when checking `OnBefore` and `NotOnOrAfter` assertion condition validity timestamps. Setting to `-1` will disable checking these conditions entirely. Default is `0`. - `attributeConsumingServiceIndex`: optional `AttributeConsumingServiceIndex` attribute to add to AuthnRequest to instruct the IDP which attribute set to attach to the response ([link](http://blog.aniljohn.com/2014/01/data-minimization-front-channel-saml-attribute-requests.html)) - `disableRequestedAuthnContext`: if truthy, do not request a specific authentication context. This is [known to help when authenticating against Active Directory](https://github.com/node-saml/passport-saml/issues/226) (AD FS) servers. - `authnContext`: if truthy, name identifier format to request auth context (default: `urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport`); array of values is also supported -- `RACComparison`: Requested Authentication Context comparison type. Possible values are 'exact','minimum','maximum','better'. Default is 'exact'. +- `racComparison`: Requested Authentication Context comparison type. Possible values are 'exact','minimum','maximum','better'. Default is 'exact'. - `forceAuthn`: if set to true, the initial SAML request from the service provider specifies that the IdP should force re-authentication of the user, even if they possess a valid session. - `providerName`: optional human-readable name of the requester for use by the presenter's user agent or the identity provider - `skipRequestCompression`: if set to true, the SAML request from the service provider won't be compressed. - `authnRequestBinding`: if set to `HTTP-POST`, will request authentication from IDP via HTTP POST binding, otherwise defaults to HTTP Redirect -- `disableRequestACSUrl`: if truthy, SAML AuthnRequest from the service provider will not include the optional AssertionConsumerServiceURL. Default is falsy so it is automatically included. +- `disableRequestAcsUrl`: if truthy, SAML AuthnRequest from the service provider will not include the optional AssertionConsumerServiceURL. Default is falsy so it is automatically included. - `scoping`: An optional configuration which implements the functionality [explained in the SAML spec paragraph "3.4.1.2 Element "](https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf). The config object is structured as following: ```javascript diff --git a/docs/adfs/README.md b/docs/adfs/README.md index f125e887..2fc0dbdb 100644 --- a/docs/adfs/README.md +++ b/docs/adfs/README.md @@ -55,7 +55,7 @@ passport.use( identifierFormat: null, // this is configured under the Advanced tab in AD FS relying party signatureAlgorithm: "sha256", - RACComparison: "exact", // default to exact RequestedAuthnContext Comparison Type + racComparison: "exact", // default to exact RequestedAuthnContext Comparison Type }, function (profile, done) { return done(null, { diff --git a/package.json b/package.json index 3f57b57c..0cab1ad4 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "prettier-format": "prettier --config .prettierrc.json --write .", "prettier-watch": "onchange -k -p 100 \".\" -- prettier --config .prettierrc.json --write {{file}}", "test": "npm run prettier-check && npm run lint && npm run tsc && mocha", + "test-watch": "mocha --watch", "tsc": "tsc", "tsc-watch": "tsc --watch", "watch": "concurrently --kill-others \"npm:*-watch\"" diff --git a/src/passport-saml/inmemory-cache-provider.ts b/src/passport-saml/inmemory-cache-provider.ts index d9724cd8..d568283c 100644 --- a/src/passport-saml/inmemory-cache-provider.ts +++ b/src/passport-saml/inmemory-cache-provider.ts @@ -29,15 +29,10 @@ export class CacheProvider { constructor(options: Partial) { this.cacheKeys = {}; - if (!options) { - options = {}; - } - - if (!options.keyExpirationPeriodMs) { - options.keyExpirationPeriodMs = 28800000; // 8 hours - } - - this.options = options as CacheProviderOptions; + this.options = { + ...options, + keyExpirationPeriodMs: options?.keyExpirationPeriodMs ?? 28800000, // 8 hours, + }; // Expire old cache keys const expirationTimer = setInterval(() => { diff --git a/src/passport-saml/multiSamlStrategy.ts b/src/passport-saml/multiSamlStrategy.ts index a4151368..ef07ab6f 100644 --- a/src/passport-saml/multiSamlStrategy.ts +++ b/src/passport-saml/multiSamlStrategy.ts @@ -8,35 +8,35 @@ import { AuthorizeOptions, MultiSamlConfig, RequestWithUser, + SamlConfig, VerifyWithoutRequest, VerifyWithRequest, } from "./types"; class MultiSamlStrategy extends SamlStrategy { - _options: MultiSamlConfig; + static readonly newSamlProviderOnConstruct = false; + + _options: SamlConfig & MultiSamlConfig; constructor(options: MultiSamlConfig, verify: VerifyWithRequest); constructor(options: MultiSamlConfig, verify: VerifyWithoutRequest); constructor(options: MultiSamlConfig, verify: never) { - if (!options || typeof options.getSamlOptions != "function") { + if (!options || typeof options.getSamlOptions !== "function") { throw new Error("Please provide a getSamlOptions function"); } - if (!options.requestIdExpirationPeriodMs) { - options.requestIdExpirationPeriodMs = 28800000; // 8 hours - } - - if (!options.cacheProvider) { - options.cacheProvider = new InMemoryCacheProvider({ - keyExpirationPeriodMs: options.requestIdExpirationPeriodMs, - }); - } + // Force the type on this since we've disabled `newOnConstruct` + // so the `SAML` constructor will not be called at this time + // and there are defaults for all `strategy`-required options. + const samlConfig = { + ...options, + } as SamlConfig & MultiSamlConfig; - super(options, verify); - this._options = options; + super(samlConfig, verify); + this._options = samlConfig; } - authenticate(req: RequestWithUser, options: AuthenticateOptions) { + authenticate(req: RequestWithUser, options: AuthenticateOptions): void { this._options.getSamlOptions(req, (err, samlOptions) => { if (err) { return this.error(err); @@ -90,6 +90,11 @@ class MultiSamlStrategy extends SamlStrategy { ); }); } + + // This is reduntant, but helps with testing + error(err: Error): void { + super.error(err); + } } export = MultiSamlStrategy; diff --git a/src/passport-saml/saml-post-signing.ts b/src/passport-saml/saml-post-signing.ts index 53ac67bf..ba4641fe 100644 --- a/src/passport-saml/saml-post-signing.ts +++ b/src/passport-saml/saml-post-signing.ts @@ -1,6 +1,6 @@ import { SignedXml } from "xml-crypto"; import * as algorithms from "./algorithms"; -import { SamlOptions, SamlSigningOptions } from "./types"; +import { SamlSigningOptions } from "./types"; const authnRequestXPath = '/*[local-name(.)="AuthnRequest" and namespace-uri(.)="urn:oasis:names:tc:SAML:2.0:protocol"]'; @@ -11,7 +11,11 @@ const defaultTransforms = [ "http://www.w3.org/2001/10/xml-exc-c14n#", ]; -export function signSamlPost(samlMessage: string, xpath: string, options: SamlSigningOptions) { +export function signSamlPost( + samlMessage: string, + xpath: string, + options: SamlSigningOptions +): string { if (!samlMessage) throw new Error("samlMessage is required"); if (!xpath) throw new Error("xpath is required"); if (!options) { diff --git a/src/passport-saml/saml.ts b/src/passport-saml/saml.ts index e2b91ec2..579aa7c3 100644 --- a/src/passport-saml/saml.ts +++ b/src/passport-saml/saml.ts @@ -27,13 +27,13 @@ import { SamlOptions, SamlIDPListConfig, SamlIDPEntryConfig, - SamlScopingConfig, ServiceMetadataXML, XMLInput, XMLObject, XMLOutput, - XMLValue, + SamlConfig, } from "./types"; +import { assertRequired } from "./utility"; const inflateRawAsync = util.promisify(zlib.inflateRaw); const deflateRawAsync = util.promisify(zlib.deflateRaw); @@ -62,7 +62,7 @@ async function processValidlySignedPostRequestAsync( } else { throw new Error("Missing SAML issuer"); } - const nameID = await self.getNameIDAsync(self, dom); + const nameID = await self.getNameIdAsync(self, dom); if (nameID) { profile.nameID = nameID.value!; if (nameID.format) { @@ -108,81 +108,62 @@ async function promiseWithNameID(nameid: Node): Promise { class SAML { options: SamlOptions; - cacheProvider: InMemoryCacheProvider; + // This is only for testing + cacheProvider!: InMemoryCacheProvider; - constructor(options: Partial) { - this.options = this.initialize(options); + constructor(ctorOptions: SamlConfig) { + this.options = this.initialize(ctorOptions); this.cacheProvider = this.options.cacheProvider; } - initialize(options: Partial): SamlOptions { - if (!options) { - options = {}; + + initialize(ctorOptions: SamlConfig): SamlOptions { + if (!ctorOptions) { + throw new TypeError("SamlOptions required on construction"); } - if (options.privateCert) { + if (ctorOptions.privateCert) { console.warn("options.privateCert has been deprecated; use options.privateKey instead."); - if (!options.privateKey) { - options.privateKey = options.privateCert; + if (!ctorOptions.privateKey) { + ctorOptions.privateKey = ctorOptions.privateCert; } } - if (Object.prototype.hasOwnProperty.call(options, "cert") && !options.cert) { - throw new Error("Invalid property: cert must not be empty"); - } - - if (!options.path) { - options.path = "/saml/consume"; - } - - if (!options.host) { - options.host = "localhost"; - } - - if (!options.issuer) { - options.issuer = "onelogin_saml"; - } - - if (options.identifierFormat === undefined) { - options.identifierFormat = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"; - } - - if (options.authnContext === undefined) { - options.authnContext = "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"; - } - - if (!Array.isArray(options.authnContext)) { - options.authnContext = [options.authnContext]; - } - - if (!options.acceptedClockSkewMs) { - // default to no skew - options.acceptedClockSkewMs = 0; - } - - if (!options.validateInResponseTo) { - options.validateInResponseTo = false; - } - - if (!options.requestIdExpirationPeriodMs) { - options.requestIdExpirationPeriodMs = 28800000; // 8 hours - } - - if (!options.cacheProvider) { - options.cacheProvider = new InMemoryCacheProvider({ - keyExpirationPeriodMs: options.requestIdExpirationPeriodMs, - }); - } - - if (!options.logoutUrl) { - // Default to Entry Point - options.logoutUrl = options.entryPoint || ""; - } - - // sha1, sha256, or sha512 - if (!options.signatureAlgorithm) { - options.signatureAlgorithm = "sha1"; - } + const options = { + ...ctorOptions, + passive: ctorOptions.passive ?? false, + disableRequestedAuthnContext: ctorOptions.disableRequestedAuthnContext ?? false, + additionalParams: ctorOptions.additionalParams ?? {}, + additionalAuthorizeParams: ctorOptions.additionalAuthorizeParams ?? {}, + additionalLogoutParams: ctorOptions.additionalLogoutParams ?? {}, + forceAuthn: ctorOptions.forceAuthn ?? false, + skipRequestCompression: ctorOptions.skipRequestCompression ?? false, + disableRequestAcsUrl: ctorOptions.disableRequestAcsUrl ?? false, + acceptedClockSkewMs: ctorOptions.acceptedClockSkewMs ?? 0, + path: ctorOptions.path ?? "/saml/consume", + host: ctorOptions.host ?? "localhost", + issuer: ctorOptions.issuer ?? "onelogin_saml", + identifierFormat: + ctorOptions.identifierFormat === undefined + ? "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" + : ctorOptions.identifierFormat, + authnContext: ctorOptions.authnContext ?? [ + "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport", + ], + validateInResponseTo: ctorOptions.validateInResponseTo ?? false, + cert: assertRequired(ctorOptions.cert, "cert is required"), + requestIdExpirationPeriodMs: ctorOptions.requestIdExpirationPeriodMs ?? 28800000, // 8 hours + cacheProvider: + ctorOptions.cacheProvider ?? + new InMemoryCacheProvider({ + keyExpirationPeriodMs: ctorOptions.requestIdExpirationPeriodMs, + }), + logoutUrl: ctorOptions.logoutUrl ?? ctorOptions.entryPoint ?? "", // Default to Entry Point + signatureAlgorithm: ctorOptions.signatureAlgorithm ?? "sha1", // sha1, sha256, or sha512 + authnRequestBinding: ctorOptions.authnRequestBinding ?? "HTTP-Redirect", + + racComparison: ctorOptions.racComparison ?? "exact", + }; /** * List of possible values: @@ -191,16 +172,11 @@ class SAML { * - maximum: Assertion context must be no stronger than a context in the list * - better: Assertion context must be stronger than all contexts in the list */ - if ( - !options.RACComparison || - ["exact", "minimum", "maximum", "better"].indexOf(options.RACComparison) === -1 - ) { - options.RACComparison = "exact"; + if (!["exact", "minimum", "maximum", "better"].includes(options.racComparison)) { + throw new TypeError("racComparison must be one of ['exact', 'minimum', 'maximum', 'better']"); } - options.authnRequestBinding = options.authnRequestBinding || "HTTP-Redirect"; - - return options as SamlOptions; + return options; } getProtocol(req: Request | { headers?: undefined; protocol?: undefined }) { @@ -230,7 +206,9 @@ class SAML { return new Date().toISOString(); } - signRequest(samlMessage: querystring.ParsedUrlQueryInput) { + signRequest(samlMessage: querystring.ParsedUrlQueryInput): void { + this.options.privateKey = assertRequired(this.options.privateKey, "privateKey is required"); + const samlMessageToSign: querystring.ParsedUrlQueryInput = {}; samlMessage.SigAlg = algorithms.getSigningAlgorithm(this.options.signatureAlgorithm); const signer = algorithms.getSigner(this.options.signatureAlgorithm); @@ -255,9 +233,10 @@ class SAML { isPassive: boolean, isHttpPostBinding: boolean ): Promise { + this.options.entryPoint = assertRequired(this.options.entryPoint, "entryPoint is required"); + const id = "_" + this.generateUniqueID(); const instant = this.generateInstant(); - const forceAuthn = this.options.forceAuthn || false; if (this.options.validateInResponseTo) { await this.cacheProvider.saveAsync(id, instant); @@ -279,15 +258,15 @@ class SAML { if (isPassive) request["samlp:AuthnRequest"]["@IsPassive"] = true; - if (forceAuthn) { + if (this.options.forceAuthn) { request["samlp:AuthnRequest"]["@ForceAuthn"] = true; } - if (!this.options.disableRequestACSUrl) { + if (!this.options.disableRequestAcsUrl) { request["samlp:AuthnRequest"]["@AssertionConsumerServiceURL"] = this.getCallbackUrl(req); } - if (this.options.identifierFormat) { + if (this.options.identifierFormat != null) { request["samlp:AuthnRequest"]["samlp:NameIDPolicy"] = { "@xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", "@Format": this.options.identifierFormat, @@ -306,7 +285,7 @@ class SAML { request["samlp:AuthnRequest"]["samlp:RequestedAuthnContext"] = { "@xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", - "@Comparison": this.options.RACComparison, + "@Comparison": this.options.racComparison, "saml:AuthnContextClassRef": authnContextClassRefs, }; } @@ -317,11 +296,11 @@ class SAML { ] = this.options.attributeConsumingServiceIndex; } - if (this.options.providerName) { + if (this.options.providerName != null) { request["samlp:AuthnRequest"]["@ProviderName"] = this.options.providerName; } - if (this.options.scoping) { + if (this.options.scoping != null) { const scoping: XMLInput = { "@xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", }; @@ -376,7 +355,7 @@ class SAML { } let stringRequest = xmlbuilder.create((request as unknown) as Record).end(); - if (isHttpPostBinding && this.options.privateKey) { + if (isHttpPostBinding && this.options.privateKey != null) { stringRequest = signAuthnRequestPost(stringRequest, this.options); } return stringRequest; @@ -457,6 +436,8 @@ class SAML { operation: string, additionalParameters: querystring.ParsedUrlQuery ): Promise { + this.options.entryPoint = assertRequired(this.options.entryPoint, "entryPoint is required"); + let buffer: Buffer; if (this.options.skipRequestCompression) { buffer = Buffer.from((request || response)!, "utf8"); @@ -485,7 +466,7 @@ class SAML { Object.keys(additionalParameters).forEach((k) => { samlMessage[k] = additionalParameters[k]; }); - if (this.options.privateKey) { + if (this.options.privateKey != null) { if (!this.options.entryPoint) { throw new Error('"entryPoint" config parameter is required for signed messages'); } @@ -508,7 +489,7 @@ class SAML { req: Request, operation: string, overrideParams?: querystring.ParsedUrlQuery - ) { + ): querystring.ParsedUrlQuery { const additionalParams: querystring.ParsedUrlQuery = {}; const RelayState = (req.query && req.query.RelayState) || (req.body && req.body.RelayState); @@ -516,24 +497,24 @@ class SAML { additionalParams.RelayState = RelayState; } - const optionsAdditionalParams = this.options.additionalParams || {}; + const optionsAdditionalParams = this.options.additionalParams; Object.keys(optionsAdditionalParams).forEach(function (k) { additionalParams[k] = optionsAdditionalParams[k]; }); let optionsAdditionalParamsForThisOperation: Record = {}; if (operation == "authorize") { - optionsAdditionalParamsForThisOperation = this.options.additionalAuthorizeParams || {}; + optionsAdditionalParamsForThisOperation = this.options.additionalAuthorizeParams; } if (operation == "logout") { - optionsAdditionalParamsForThisOperation = this.options.additionalLogoutParams || {}; + optionsAdditionalParamsForThisOperation = this.options.additionalLogoutParams; } Object.keys(optionsAdditionalParamsForThisOperation).forEach(function (k) { additionalParams[k] = optionsAdditionalParamsForThisOperation[k]; }); - overrideParams = overrideParams || {}; + overrideParams = overrideParams ?? {}; Object.keys(overrideParams).forEach(function (k) { additionalParams[k] = overrideParams![k]; }); @@ -553,7 +534,9 @@ class SAML { ); } - async getAuthorizeFormAsync(req: Request) { + async getAuthorizeFormAsync(req: Request): Promise { + this.options.entryPoint = assertRequired(this.options.entryPoint, "entryPoint is required"); + // The quoteattr() function is used in a context, where the result will not be evaluated by javascript // but must be interpreted by an XML or HTML parser, and it must absolutely avoid breaking the syntax // of an element attribute. @@ -645,13 +628,13 @@ class SAML { req: RequestWithUser, options: AuthenticateOptions & AuthorizeOptions, callback: (err: Error | null, url?: string | null) => void - ) { + ): void { util.callbackify(() => this.getLogoutResponseUrlAsync(req, options))(callback); } async getLogoutResponseUrlAsync( req: RequestWithUser, options: AuthenticateOptions & AuthorizeOptions - ) { + ): Promise { const response = this.generateLogoutResponse(req, req.samlLogoutRequest); const operation = "logout"; const overrideParams = options ? options.additionalParams || {} : {}; @@ -672,25 +655,30 @@ class SAML { return cert; } - async certsToCheck(): Promise { - if (!this.options.cert) { - return undefined; - } + async certsToCheck(): Promise { + let checkedCerts: string[]; + if (typeof this.options.cert === "function") { - return util + checkedCerts = await util .promisify(this.options.cert as CertCallback)() .then((certs) => { + certs = assertRequired(certs, "callback didn't return cert"); if (!Array.isArray(certs)) { - certs = [certs as string]; + certs = [certs]; } - return certs as string[]; + return certs; }); + } else if (Array.isArray(this.options.cert)) { + checkedCerts = this.options.cert; + } else { + checkedCerts = [this.options.cert]; } - let certs = this.options.cert; - if (!Array.isArray(certs)) { - certs = [certs]; - } - return certs; + + checkedCerts.forEach((cert) => { + assertRequired(cert, "unknown cert found"); + }); + + return checkedCerts; } // This function checks that the |currentNode| in the |fullXml| document contains exactly 1 valid @@ -698,7 +686,7 @@ class SAML { // // See https://github.com/bergie/passport-saml/issues/19 for references to some of the attack // vectors against SAML signature verification. - validateSignature(fullXml: string, currentNode: HTMLElement, certs: string[]) { + validateSignature(fullXml: string, currentNode: HTMLElement, certs: string[]): boolean { const xpathSigQuery = ".//*[" + "local-name(.)='Signature' and " + @@ -710,7 +698,7 @@ class SAML { const signatures = xmlCrypto.xpath(currentNode, xpathSigQuery); // This function is expecting to validate exactly one signature, so if we find more or fewer // than that, reject. - if (signatures.length != 1) { + if (signatures.length !== 1) { return false; } @@ -726,12 +714,12 @@ class SAML { cert: string, fullXml: string, currentNode: HTMLElement - ) { + ): boolean { const sig = new xmlCrypto.SignedXml(); sig.keyInfoProvider = { file: "", - getKeyInfo: (key) => "", - getKey: (keyInfo) => Buffer.from(this.certToPEM(cert)), + getKeyInfo: () => "", + getKey: () => Buffer.from(this.certToPEM(cert)), }; signature = this.normalizeNewlines(signature.toString()); sig.loadSignature(signature); @@ -781,7 +769,7 @@ class SAML { const certs = await this.certsToCheck(); // Check if this document has a valid top-level signature let validSignature = false; - if (this.options.cert && this.validateSignature(xml, doc.documentElement, certs!)) { + if (this.validateSignature(xml, doc.documentElement, certs)) { validSignature = true; } @@ -801,14 +789,10 @@ class SAML { } if (assertions.length == 1) { - if ( - this.options.cert && - !validSignature && - !this.validateSignature(xml, assertions[0], certs!) - ) { + if (!validSignature && !this.validateSignature(xml, assertions[0], certs)) { throw new Error("Invalid signature"); } - return this.processValidlySignedAssertionAsync( + return await this.processValidlySignedAssertionAsync( assertions[0].toString(), xml, inResponseTo! @@ -816,8 +800,10 @@ class SAML { } if (encryptedAssertions.length == 1) { - if (!this.options.decryptionPvk) - throw new Error("No decryption key for encrypted SAML response"); + this.options.decryptionPvk = assertRequired( + this.options.decryptionPvk, + "No decryption key for encrypted SAML response" + ); const encryptedAssertionXml = encryptedAssertions[0].toString(); @@ -834,11 +820,11 @@ class SAML { if (decryptedAssertions.length != 1) throw new Error("Invalid EncryptedAssertion content"); if ( - this.options.cert && !validSignature && - !this.validateSignature(decryptedXml, decryptedAssertions[0], certs!) - ) + !this.validateSignature(decryptedXml, decryptedAssertions[0], certs) + ) { throw new Error("Invalid signature from encrypted assertion"); + } return await this.processValidlySignedAssertionAsync( decryptedAssertions[0].toString(), @@ -873,7 +859,7 @@ class SAML { nestedStatusCode && nestedStatusCode[0].$.Value === "urn:oasis:names:tc:SAML:2.0:status:NoPassive" ) { - if (this.options.cert && !validSignature) { + if (!validSignature) { throw new Error("Invalid signature: NoPassive"); } return { profile: null, loggedOut: false }; @@ -906,7 +892,7 @@ class SAML { } throw new Error("Missing SAML assertion"); } else { - if (this.options.cert && !validSignature) { + if (!validSignature) { throw new Error("Invalid signature: No response found"); } const logoutResponse = xmljsDoc.LogoutResponse; @@ -918,14 +904,14 @@ class SAML { } } catch (err) { debug("validatePostResponse resulted in an error: %s", err); - if (this.options.validateInResponseTo) { + if (this.options.validateInResponseTo != null) { await this.cacheProvider.removeAsync(inResponseTo!); } throw err; } } - async validateInResponseTo(inResponseTo: string | null) { + async validateInResponseTo(inResponseTo: string | null): Promise { if (this.options.validateInResponseTo) { if (inResponseTo) { const result = await this.cacheProvider.getAsync(inResponseTo); @@ -975,7 +961,7 @@ class SAML { return exists[0]; }; - if (container.Signature && this.options.cert) { + if (container.Signature) { let urlString = getParam("SAMLRequest") || getParam("SAMLResponse"); if (getParam("RelayState")) { @@ -985,7 +971,7 @@ class SAML { urlString += "&" + getParam("SigAlg"); const certs = await this.certsToCheck(); - const hasValidQuerySignature = certs!.some((cert) => { + const hasValidQuerySignature = certs.some((cert) => { return this.validateSignatureForRedirect( urlString, container.Signature as string, @@ -994,7 +980,7 @@ class SAML { ); }); if (!hasValidQuerySignature) { - throw new Error("Invalid signature"); + throw new Error("Invalid query signature"); } } else { return true; @@ -1057,7 +1043,7 @@ class SAML { } verifyIssuer(samlMessage: XMLOutput) { - if (this.options.idpIssuer) { + if (this.options.idpIssuer != null) { const issuer = samlMessage.Issuer; if (issuer) { if (issuer[0]._ !== this.options.idpIssuer) @@ -1193,7 +1179,7 @@ class SAML { if (conErr) throw conErr; } - if (this.options.audience) { + if (this.options.audience != null) { const audienceErr = this.checkAudienceValidityError( this.options.audience, conditions.AudienceRestriction @@ -1305,13 +1291,13 @@ class SAML { const parser = new xml2js.Parser(parserConfig); const doc = await parser.parseStringPromise(xml); const certs = await this.certsToCheck(); - if (this.options.cert && !this.validateSignature(xml, dom.documentElement, certs!)) { + if (!this.validateSignature(xml, dom.documentElement, certs)) { throw new Error("Invalid signature on documentElement"); } return await processValidlySignedPostRequestAsync(this, doc, dom); } - async getNameIDAsync(self: SAML, doc: Node): Promise { + async getNameIdAsync(self: SAML, doc: Node): Promise { const nameIds = xmlCrypto.xpath( doc, "/*[local-name()='LogoutRequest']/*[local-name()='NameID']" @@ -1328,9 +1314,10 @@ class SAML { return promiseWithNameID(nameIds[0]); } if (encryptedIds.length === 1) { - if (!self.options.decryptionPvk) { - throw new Error("No decryption key for encrypted SAML response"); - } + self.options.decryptionPvk = assertRequired( + self.options.decryptionPvk, + "No decryption key found getting name ID for encrypted SAML response" + ); const encryptedDatas = xmlCrypto.xpath(encryptedIds[0], "./*[local-name()='EncryptedData']"); @@ -1367,14 +1354,14 @@ class SAML { }, }; - if (this.options.decryptionPvk) { + if (this.options.decryptionPvk != null) { if (!decryptionCert) { throw new Error( "Missing decryptionCert while generating metadata for decrypting service provider" ); } } - if (this.options.privateKey) { + if (this.options.privateKey != null) { if (!signingCert) { throw new Error( "Missing signingCert while generating metadata for signing service provider messages" @@ -1382,9 +1369,9 @@ class SAML { } } - if (this.options.decryptionPvk || this.options.privateKey) { + if (this.options.decryptionPvk != null || this.options.privateKey != null) { metadata.EntityDescriptor.SPSSODescriptor.KeyDescriptor = []; - if (this.options.privateKey) { + if (this.options.privateKey != null) { signingCert = signingCert!.replace(/-+BEGIN CERTIFICATE-+\r?\n?/, ""); signingCert = signingCert.replace(/-+END CERTIFICATE-+\r?\n?/, ""); signingCert = signingCert.replace(/\r\n/g, "\n"); @@ -1401,7 +1388,7 @@ class SAML { }); } - if (this.options.decryptionPvk) { + if (this.options.decryptionPvk != null) { decryptionCert = decryptionCert!.replace(/-+BEGIN CERTIFICATE-+\r?\n?/, ""); decryptionCert = decryptionCert.replace(/-+END CERTIFICATE-+\r?\n?/, ""); decryptionCert = decryptionCert.replace(/\r\n/g, "\n"); @@ -1425,14 +1412,14 @@ class SAML { } } - if (this.options.logoutCallbackUrl) { + if (this.options.logoutCallbackUrl != null) { metadata.EntityDescriptor.SPSSODescriptor.SingleLogoutService = { "@Binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", "@Location": this.options.logoutCallbackUrl, }; } - if (this.options.identifierFormat) { + if (this.options.identifierFormat != null) { metadata.EntityDescriptor.SPSSODescriptor.NameIDFormat = this.options.identifierFormat; } @@ -1447,19 +1434,25 @@ class SAML { .end({ pretty: true, indent: " ", newline: "\n" }); } - keyToPEM(key: crypto.KeyLike) { - if (!key || typeof key !== "string") return key; + keyToPEM(key: string | Buffer): typeof key extends string | Buffer ? string | Buffer : Error { + key = assertRequired(key, "key is required"); - const lines = key.split(/\r?\n/); - if (lines.length !== 1) return key; + if (typeof key !== "string") return key; + if (key.split(/\r?\n/).length !== 1) return key; + + const matchedKey = key.match(/.{1,64}/g); + + if (matchedKey) { + const wrappedKey = [ + "-----BEGIN PRIVATE KEY-----", + ...matchedKey, + "-----END PRIVATE KEY-----", + "", + ].join("\n"); + return wrappedKey; + } - const wrappedKey = [ - "-----BEGIN PRIVATE KEY-----", - ...(key.match(/.{1,64}/g) ?? []), - "-----END PRIVATE KEY-----", - "", - ].join("\n"); - return wrappedKey; + throw new Error("Invalid key"); } normalizeNewlines(xml: string): string { diff --git a/src/passport-saml/strategy.ts b/src/passport-saml/strategy.ts index 8f4fc487..8feac264 100644 --- a/src/passport-saml/strategy.ts +++ b/src/passport-saml/strategy.ts @@ -6,24 +6,26 @@ import { AuthorizeOptions, RequestWithUser, SamlConfig, + StrategyOptions, VerifyWithoutRequest, VerifyWithRequest, } from "./types"; import { Profile } from "./types"; class Strategy extends PassportStrategy { + static readonly newSamlProviderOnConstruct = true; + name: string; _verify: VerifyWithRequest | VerifyWithoutRequest; - _saml: saml.SAML; + _saml: saml.SAML | undefined; _passReqToCallback?: boolean; constructor(options: SamlConfig, verify: VerifyWithRequest); constructor(options: SamlConfig, verify: VerifyWithoutRequest); constructor(options: SamlConfig, verify: never) { super(); - if (typeof options == "function") { - verify = options; - options = {}; + if (typeof options === "function") { + throw new Error("Mandatory SAML options missing"); } if (!verify) { @@ -39,11 +41,17 @@ class Strategy extends PassportStrategy { } this._verify = verify; - this._saml = new saml.SAML(options); + if ((this.constructor as typeof Strategy).newSamlProviderOnConstruct) { + this._saml = new saml.SAML(options); + } this._passReqToCallback = !!options.passReqToCallback; } authenticate(req: RequestWithUser, options: AuthenticateOptions): void { + if (this._saml == null) { + throw new Error("Can't get authenticate without a SAML provider defined."); + } + options.samlFallback = options.samlFallback || "login-request"; const validateCallback = ({ profile, @@ -55,6 +63,10 @@ class Strategy extends PassportStrategy { if (loggedOut) { req.logout(); if (profile) { + if (this._saml == null) { + throw new Error("Can't get logout response URL without a SAML provider defined."); + } + req.samlLogoutRequest = profile; return this._saml.getLogoutResponseUrl(req, options, redirectIfSuccess); } @@ -112,6 +124,10 @@ class Strategy extends PassportStrategy { const requestHandler = { "login-request": async () => { try { + if (this._saml == null) { + throw new Error("Can't process login request without a SAML provider defined."); + } + if (this._saml.options.authnRequestBinding === "HTTP-POST") { const data = await this._saml.getAuthorizeFormAsync(req); const res = req.res!; @@ -125,6 +141,10 @@ class Strategy extends PassportStrategy { } }, "logout-request": async () => { + if (this._saml == null) { + throw new Error("Can't process logout request without a SAML provider defined."); + } + try { this.redirect(await this._saml.getLogoutUrlAsync(req, options)); } catch (err) { @@ -142,6 +162,10 @@ class Strategy extends PassportStrategy { } logout(req: RequestWithUser, callback: (err: Error | null, url?: string | null) => void): void { + if (this._saml == null) { + throw new Error("Can't logout without a SAML provider defined."); + } + this._saml .getLogoutUrlAsync(req, {}) .then((url) => callback(null, url)) @@ -152,8 +176,17 @@ class Strategy extends PassportStrategy { decryptionCert: string | null, signingCert?: string | null ): string { + if (this._saml == null) { + throw new Error("Can't generate service provider metadata without a SAML provider defined."); + } + return this._saml.generateServiceProviderMetadata(decryptionCert, signingCert); } + + // This is reduntant, but helps with testing + error(err: Error): void { + super.error(err); + } } export = Strategy; diff --git a/src/passport-saml/types.ts b/src/passport-saml/types.ts index 5cba6b79..eacd63a1 100644 --- a/src/passport-saml/types.ts +++ b/src/passport-saml/types.ts @@ -5,7 +5,7 @@ import type { CacheProvider } from "./inmemory-cache-provider"; export type CertCallback = ( callback: (err: Error | null, cert?: string | string[]) => void ) => void; -export type RACComparision = "exact" | "minimum" | "maximum" | "better"; +export type RacComparision = "exact" | "minimum" | "maximum" | "better"; export type SignatureAlgorithm = "sha1" | "sha256" | "sha512"; export interface AuthenticateOptions extends passport.AuthenticateOptions { @@ -20,40 +20,50 @@ export interface AuthorizeOptions extends AuthenticateOptions { export interface SamlSigningOptions { /** @deprecated use privateKey field instead */ privateCert?: string | Buffer; - privateKey: string | Buffer; + privateKey?: string | Buffer; signatureAlgorithm?: SignatureAlgorithm; xmlSignatureTransforms?: string[]; digestAlgorithm?: string; } -export interface SamlOptions extends SamlSigningOptions { +/** + * These are SAML options that must be provided to construct a new SAML Strategy + */ +export interface MandatorySamlOptions { + cert: string | string[] | CertCallback; +} + +/** + * The options required to use a SAML strategy + * These may be provided by means of defaults specified in the constructor + */ +export interface SamlOptions extends SamlSigningOptions, MandatorySamlOptions { // Core - callbackUrl: string; + callbackUrl?: string; path: string; - protocol: string; + protocol?: string; host: string; - entryPoint: string; + entryPoint?: string; issuer: string; - cert: string | string[] | CertCallback; - decryptionPvk: string | Buffer; + decryptionPvk?: string | Buffer; // Additional SAML behaviors additionalParams: Record; additionalAuthorizeParams: Record; - identifierFormat: string | null; + identifierFormat?: string | null; acceptedClockSkewMs: number; - attributeConsumingServiceIndex: string | null; + attributeConsumingServiceIndex?: string; disableRequestedAuthnContext: boolean; - authnContext: string | string[]; + authnContext: string[]; forceAuthn: boolean; skipRequestCompression: boolean; authnRequestBinding?: string; - RACComparison: RACComparision; - providerName: string; + racComparison: RacComparision; + providerName?: string; passive: boolean; - idpIssuer: string; - audience: string; - scoping: SamlScopingConfig; + idpIssuer?: string; + audience?: string; + scoping?: SamlScopingConfig; // InResponseTo Validation validateInResponseTo: boolean; @@ -63,19 +73,21 @@ export interface SamlOptions extends SamlSigningOptions { // Logout logoutUrl: string; additionalLogoutParams: Record; - logoutCallbackUrl: string; + logoutCallbackUrl?: string; // extras - disableRequestACSUrl: boolean; + disableRequestAcsUrl: boolean; } - -export type SamlConfig = Partial & StrategyOptions; - -interface StrategyOptions { +export interface StrategyOptions { name?: string; passReqToCallback?: boolean; } +/** + * These options are availble for configuring a SAML strategy + */ +export type SamlConfig = Partial & StrategyOptions & MandatorySamlOptions; + export interface SamlScopingConfig { idpList?: SamlIDPListConfig[]; proxyCount?: number; @@ -166,6 +178,8 @@ export type VerifyWithoutRequest = ( export type SamlOptionsCallback = (err: Error | null, samlOptions?: SamlConfig) => void; -export interface MultiSamlConfig extends SamlConfig { +interface BaseMultiSamlConfig { getSamlOptions(req: express.Request, callback: SamlOptionsCallback): void; } + +export type MultiSamlConfig = Partial & StrategyOptions & BaseMultiSamlConfig; diff --git a/src/passport-saml/utility.ts b/src/passport-saml/utility.ts new file mode 100644 index 00000000..a378170f --- /dev/null +++ b/src/passport-saml/utility.ts @@ -0,0 +1,54 @@ +import { SignedXml } from "xml-crypto"; +import { SamlSigningOptions } from "./types"; +import * as algorithms from "./algorithms"; + +export function assertRequired(value: T | null | undefined, error?: string): T { + if (value === undefined || value === null || (typeof value === "string" && value.length === 0)) { + throw new TypeError(error ?? "value does not exist"); + } else { + return value; + } +} + +export function signXml(samlMessage: string, xpath: string, options: SamlSigningOptions): string { + const defaultTransforms = [ + "http://www.w3.org/2000/09/xmldsig#enveloped-signature", + "http://www.w3.org/2001/10/xml-exc-c14n#", + ]; + + if (!samlMessage) throw new Error("samlMessage is required"); + if (!xpath) throw new Error("xpath is required"); + if (!options) { + options = {} as SamlSigningOptions; + } + + if (options.privateCert) { + console.warn("options.privateCert has been deprecated; use options.privateKey instead."); + + if (!options.privateKey) { + options.privateKey = options.privateCert; + } + } + + if (!options.privateKey) throw new Error("options.privateKey is required"); + + const transforms = options.xmlSignatureTransforms || defaultTransforms; + const sig = new SignedXml(); + if (options.signatureAlgorithm) { + sig.signatureAlgorithm = algorithms.getSigningAlgorithm(options.signatureAlgorithm); + } + sig.addReference(xpath, transforms, algorithms.getDigestAlgorithm(options.digestAlgorithm)); + sig.signingKey = options.privateKey; + sig.computeSignature(samlMessage, { + location: { reference: xpath, action: "append" }, + }); + + return sig.getSignedXml(); +} + +export function signXmlResponse(samlMessage: string, options: SamlSigningOptions): string { + const responseXpath = + '//*[local-name(.)="Response" and namespace-uri(.)="urn:oasis:names:tc:SAML:2.0:protocol"]'; + + return signXml(samlMessage, responseXpath, options); +} diff --git a/test/capturedSamlRequests.spec.ts b/test/capturedSamlRequests.spec.ts new file mode 100644 index 00000000..6865c9f8 --- /dev/null +++ b/test/capturedSamlRequests.spec.ts @@ -0,0 +1,1118 @@ +"use strict"; +import * as express from "express"; +import * as bodyParser from "body-parser"; +import * as passport from "passport"; +import { Strategy as SamlStrategy } from "../src/passport-saml"; +import request = require("request"); +import * as zlib from "zlib"; +import * as querystring from "querystring"; +import { parseString } from "xml2js"; +import * as fs from "fs"; +import { AuthenticateOptions, Profile, VerifiedCallback } from "../src/passport-saml/types.js"; +import * as should from "should"; +import { Server } from "http"; +import { CapturedCheck, FAKE_CERT, SamlCheck } from "./types"; + +const capturedSamlRequestChecks: SamlCheck[] = [ + { + name: "Empty Config", + config: { cert: FAKE_CERT }, + result: { + "samlp:AuthnRequest": { + $: { + "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", + Version: "2.0", + ProtocolBinding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", + AssertionConsumerServiceURL: "http://localhost:3033/login", + Destination: "https://wwwexampleIdp.com/saml", + }, + "saml:Issuer": [ + { _: "onelogin_saml", $: { "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion" } }, + ], + "samlp:NameIDPolicy": [ + { + $: { + "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", + Format: "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", + AllowCreate: "true", + }, + }, + ], + "samlp:RequestedAuthnContext": [ + { + $: { "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", Comparison: "exact" }, + "saml:AuthnContextClassRef": [ + { + _: "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport", + $: { "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion" }, + }, + ], + }, + ], + }, + }, + }, + { + name: "Empty Config w/ HTTP-POST binding", + config: { authnRequestBinding: "HTTP-POST", cert: FAKE_CERT }, + result: { + "samlp:AuthnRequest": { + $: { + "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", + Version: "2.0", + ProtocolBinding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", + AssertionConsumerServiceURL: "http://localhost:3033/login", + Destination: "https://wwwexampleIdp.com/saml", + }, + "saml:Issuer": [ + { _: "onelogin_saml", $: { "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion" } }, + ], + "samlp:NameIDPolicy": [ + { + $: { + "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", + Format: "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", + AllowCreate: "true", + }, + }, + ], + "samlp:RequestedAuthnContext": [ + { + $: { "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", Comparison: "exact" }, + "saml:AuthnContextClassRef": [ + { + _: "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport", + $: { "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion" }, + }, + ], + }, + ], + }, + }, + }, + { + name: "Config #2", + config: { + issuer: "http://exampleSp.com/saml", + identifierFormat: "alternateIdentifier", + passive: true, + attributeConsumingServiceIndex: "123", + forceAuthn: false, + cert: FAKE_CERT, + }, + result: { + "samlp:AuthnRequest": { + $: { + "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", + Version: "2.0", + ProtocolBinding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", + AssertionConsumerServiceURL: "http://localhost:3033/login", + AttributeConsumingServiceIndex: "123", + Destination: "https://wwwexampleIdp.com/saml", + IsPassive: "true", + }, + "saml:Issuer": [ + { + _: "http://exampleSp.com/saml", + $: { "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion" }, + }, + ], + "samlp:NameIDPolicy": [ + { + $: { + "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", + Format: "alternateIdentifier", + AllowCreate: "true", + }, + }, + ], + "samlp:RequestedAuthnContext": [ + { + $: { "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", Comparison: "exact" }, + "saml:AuthnContextClassRef": [ + { + _: "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport", + $: { "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion" }, + }, + ], + }, + ], + }, + }, + }, + { + name: "Uncompressed config #2", + config: { + issuer: "http://exampleSp.com/saml", + identifierFormat: "alternateIdentifier", + passive: true, + attributeConsumingServiceIndex: "123", + skipRequestCompression: true, + cert: FAKE_CERT, + }, + result: { + "samlp:AuthnRequest": { + $: { + "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", + Version: "2.0", + ProtocolBinding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", + AssertionConsumerServiceURL: "http://localhost:3033/login", + AttributeConsumingServiceIndex: "123", + Destination: "https://wwwexampleIdp.com/saml", + IsPassive: "true", + }, + "saml:Issuer": [ + { + _: "http://exampleSp.com/saml", + $: { "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion" }, + }, + ], + "samlp:NameIDPolicy": [ + { + $: { + "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", + Format: "alternateIdentifier", + AllowCreate: "true", + }, + }, + ], + "samlp:RequestedAuthnContext": [ + { + $: { "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", Comparison: "exact" }, + "saml:AuthnContextClassRef": [ + { + _: "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport", + $: { "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion" }, + }, + ], + }, + ], + }, + }, + }, + { + name: "Config #3", + config: { + issuer: "http://exampleSp.com/saml", + identifierFormat: "alternateIdentifier", + passive: true, + attributeConsumingServiceIndex: "123", + skipRequestCompression: true, + disableRequestedAuthnContext: true, + forceAuthn: true, + cert: FAKE_CERT, + }, + result: { + "samlp:AuthnRequest": { + $: { + "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", + Version: "2.0", + ProtocolBinding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", + AssertionConsumerServiceURL: "http://localhost:3033/login", + AttributeConsumingServiceIndex: "123", + Destination: "https://wwwexampleIdp.com/saml", + IsPassive: "true", + ForceAuthn: "true", + }, + "saml:Issuer": [ + { + _: "http://exampleSp.com/saml", + $: { "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion" }, + }, + ], + "samlp:NameIDPolicy": [ + { + $: { + "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", + Format: "alternateIdentifier", + AllowCreate: "true", + }, + }, + ], + }, + }, + }, + { + name: "Config with AuthnContext", + config: { + issuer: "http://exampleSp.com/saml", + identifierFormat: "alternateIdentifier", + passive: true, + attributeConsumingServiceIndex: "123", + authnContext: ["myAuthnContext"], + cert: FAKE_CERT, + }, + result: { + "samlp:AuthnRequest": { + $: { + "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", + Version: "2.0", + ProtocolBinding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", + AssertionConsumerServiceURL: "http://localhost:3033/login", + AttributeConsumingServiceIndex: "123", + Destination: "https://wwwexampleIdp.com/saml", + IsPassive: "true", + }, + "saml:Issuer": [ + { + _: "http://exampleSp.com/saml", + $: { "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion" }, + }, + ], + "samlp:NameIDPolicy": [ + { + $: { + "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", + Format: "alternateIdentifier", + AllowCreate: "true", + }, + }, + ], + "samlp:RequestedAuthnContext": [ + { + $: { "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", Comparison: "exact" }, + "saml:AuthnContextClassRef": [ + { + _: "myAuthnContext", + $: { "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion" }, + }, + ], + }, + ], + }, + }, + }, + { + name: "Config with multiple AuthnContext", + config: { + issuer: "http://exampleSp.com/saml", + identifierFormat: "alternateIdentifier", + passive: true, + attributeConsumingServiceIndex: "123", + authnContext: ["myAuthnContext", "myAuthnContext2"], + cert: FAKE_CERT, + }, + result: { + "samlp:AuthnRequest": { + $: { + "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", + Version: "2.0", + ProtocolBinding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", + AssertionConsumerServiceURL: "http://localhost:3033/login", + AttributeConsumingServiceIndex: "123", + Destination: "https://wwwexampleIdp.com/saml", + IsPassive: "true", + }, + "saml:Issuer": [ + { + _: "http://exampleSp.com/saml", + $: { "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion" }, + }, + ], + "samlp:NameIDPolicy": [ + { + $: { + "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", + Format: "alternateIdentifier", + AllowCreate: "true", + }, + }, + ], + "samlp:RequestedAuthnContext": [ + { + $: { "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", Comparison: "exact" }, + "saml:AuthnContextClassRef": [ + { + _: "myAuthnContext", + $: { "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion" }, + }, + { + _: "myAuthnContext2", + $: { "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion" }, + }, + ], + }, + ], + }, + }, + }, + { + name: "Config with ProviderName", + config: { + issuer: "http://exampleSp.com/saml", + identifierFormat: "alternateIdentifier", + providerName: "myProviderName", + cert: FAKE_CERT, + }, + result: { + "samlp:AuthnRequest": { + $: { + "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", + Version: "2.0", + ProtocolBinding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", + ProviderName: "myProviderName", + AssertionConsumerServiceURL: "http://localhost:3033/login", + Destination: "https://wwwexampleIdp.com/saml", + }, + "saml:Issuer": [ + { + _: "http://exampleSp.com/saml", + $: { "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion" }, + }, + ], + "samlp:NameIDPolicy": [ + { + $: { + "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", + Format: "alternateIdentifier", + AllowCreate: "true", + }, + }, + ], + "samlp:RequestedAuthnContext": [ + { + $: { "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", Comparison: "exact" }, + "saml:AuthnContextClassRef": [ + { + _: "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport", + $: { "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion" }, + }, + ], + }, + ], + }, + }, + }, + { + name: "Remove NameIDPolicy, AuthnRequest, and AssertionConsumerServiceURL Config", + config: { + identifierFormat: null, + disableRequestedAuthnContext: true, + disableRequestAcsUrl: true, + cert: FAKE_CERT, + }, + result: { + "samlp:AuthnRequest": { + $: { + "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", + Version: "2.0", + ProtocolBinding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", + Destination: "https://wwwexampleIdp.com/saml", + }, + "saml:Issuer": [ + { _: "onelogin_saml", $: { "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion" } }, + ], + }, + }, + }, + { + name: "Config with full Scoping config", + config: { + issuer: "http://exampleSp.com/saml", + identifierFormat: "alternateIdentifier", + scoping: { + proxyCount: 2, + requesterId: "fooBarRequesterId", + idpList: [ + { + entries: [ + { + providerId: "myScopingProviderId", + name: "myScopingProviderName", + loc: "myScopingProviderLoc", + }, + ], + getComplete: "https://www.getcompleteidplist.com", + }, + ], + }, + cert: FAKE_CERT, + }, + result: { + "samlp:AuthnRequest": { + $: { + AssertionConsumerServiceURL: "http://localhost:3033/login", + Destination: "https://wwwexampleIdp.com/saml", + ProtocolBinding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", + Version: "2.0", + "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", + }, + "saml:Issuer": [ + { + $: { + "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion", + }, + _: "http://exampleSp.com/saml", + }, + ], + "samlp:NameIDPolicy": [ + { + $: { + AllowCreate: "true", + Format: "alternateIdentifier", + "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", + }, + }, + ], + "samlp:RequestedAuthnContext": [ + { + $: { + Comparison: "exact", + "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", + }, + "saml:AuthnContextClassRef": [ + { + $: { + "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion", + }, + _: "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport", + }, + ], + }, + ], + "samlp:Scoping": [ + { + $: { + "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", + ProxyCount: "2", + }, + "samlp:IDPList": [ + { + $: { + "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", + }, + "samlp:GetComplete": ["https://www.getcompleteidplist.com"], + "samlp:IDPEntry": [ + { + $: { + Loc: "myScopingProviderLoc", + Name: "myScopingProviderName", + ProviderID: "myScopingProviderId", + "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", + }, + }, + ], + }, + ], + "samlp:RequesterID": ["fooBarRequesterId"], + }, + ], + }, + }, + }, + { + name: "Config with Scoping config without proxyCount and requesterId", + config: { + issuer: "http://exampleSp.com/saml", + identifierFormat: "alternateIdentifier", + scoping: { + idpList: [ + { + entries: [ + { + providerId: "myScopingProviderId", + name: "myScopingProviderName", + loc: "myScopingProviderLoc", + }, + ], + getComplete: "https://www.getcompleteidplist.com", + }, + ], + }, + cert: FAKE_CERT, + }, + result: { + "samlp:AuthnRequest": { + $: { + AssertionConsumerServiceURL: "http://localhost:3033/login", + Destination: "https://wwwexampleIdp.com/saml", + ProtocolBinding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", + Version: "2.0", + "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", + }, + "saml:Issuer": [ + { + $: { + "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion", + }, + _: "http://exampleSp.com/saml", + }, + ], + "samlp:NameIDPolicy": [ + { + $: { + AllowCreate: "true", + Format: "alternateIdentifier", + "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", + }, + }, + ], + "samlp:RequestedAuthnContext": [ + { + $: { + Comparison: "exact", + "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", + }, + "saml:AuthnContextClassRef": [ + { + $: { + "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion", + }, + _: "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport", + }, + ], + }, + ], + "samlp:Scoping": [ + { + $: { + "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", + }, + "samlp:IDPList": [ + { + $: { + "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", + }, + "samlp:GetComplete": ["https://www.getcompleteidplist.com"], + "samlp:IDPEntry": [ + { + $: { + Loc: "myScopingProviderLoc", + Name: "myScopingProviderName", + ProviderID: "myScopingProviderId", + "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", + }, + }, + ], + }, + ], + }, + ], + }, + }, + }, + { + name: "Config with Scoping config without proxyCount, requesterId, getComplete", + config: { + issuer: "http://exampleSp.com/saml", + identifierFormat: "alternateIdentifier", + scoping: { + idpList: [ + { + entries: [ + { + providerId: "myScopingProviderId", + name: "myScopingProviderName", + loc: "myScopingProviderLoc", + }, + ], + }, + ], + }, + cert: FAKE_CERT, + }, + result: { + "samlp:AuthnRequest": { + $: { + AssertionConsumerServiceURL: "http://localhost:3033/login", + Destination: "https://wwwexampleIdp.com/saml", + ProtocolBinding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", + Version: "2.0", + "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", + }, + "saml:Issuer": [ + { + $: { + "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion", + }, + _: "http://exampleSp.com/saml", + }, + ], + "samlp:NameIDPolicy": [ + { + $: { + AllowCreate: "true", + Format: "alternateIdentifier", + "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", + }, + }, + ], + "samlp:RequestedAuthnContext": [ + { + $: { + Comparison: "exact", + "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", + }, + "saml:AuthnContextClassRef": [ + { + $: { + "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion", + }, + _: "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport", + }, + ], + }, + ], + "samlp:Scoping": [ + { + $: { + "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", + }, + "samlp:IDPList": [ + { + $: { + "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", + }, + "samlp:IDPEntry": [ + { + $: { + Loc: "myScopingProviderLoc", + Name: "myScopingProviderName", + ProviderID: "myScopingProviderId", + "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", + }, + }, + ], + }, + ], + }, + ], + }, + }, + }, + { + name: + "Config with Scoping config without proxyCount, requesterId, idpList getComplete, entry name, entry loc", + config: { + issuer: "http://exampleSp.com/saml", + identifierFormat: "alternateIdentifier", + scoping: { + idpList: [ + { + entries: [ + { + providerId: "myScopingProviderId", + }, + ], + }, + ], + }, + cert: FAKE_CERT, + }, + result: { + "samlp:AuthnRequest": { + $: { + AssertionConsumerServiceURL: "http://localhost:3033/login", + Destination: "https://wwwexampleIdp.com/saml", + ProtocolBinding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", + Version: "2.0", + "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", + }, + "saml:Issuer": [ + { + $: { + "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion", + }, + _: "http://exampleSp.com/saml", + }, + ], + "samlp:NameIDPolicy": [ + { + $: { + AllowCreate: "true", + Format: "alternateIdentifier", + "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", + }, + }, + ], + "samlp:RequestedAuthnContext": [ + { + $: { + Comparison: "exact", + "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", + }, + "saml:AuthnContextClassRef": [ + { + $: { + "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion", + }, + _: "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport", + }, + ], + }, + ], + "samlp:Scoping": [ + { + $: { + "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", + }, + "samlp:IDPList": [ + { + $: { + "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", + }, + "samlp:IDPEntry": [ + { + $: { + ProviderID: "myScopingProviderId", + "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", + }, + }, + ], + }, + ], + }, + ], + }, + }, + }, + { + name: "Config with Scoping and multiple IDPList entries", + config: { + issuer: "http://exampleSp.com/saml", + identifierFormat: "alternateIdentifier", + scoping: { + idpList: [ + { + entries: [ + { + providerId: "myScopingProviderId", + }, + { + providerId: "myOtherScopingProviderId", + }, + ], + }, + ], + }, + cert: FAKE_CERT, + }, + result: { + "samlp:AuthnRequest": { + $: { + AssertionConsumerServiceURL: "http://localhost:3033/login", + Destination: "https://wwwexampleIdp.com/saml", + ProtocolBinding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", + Version: "2.0", + "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", + }, + "saml:Issuer": [ + { + $: { + "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion", + }, + _: "http://exampleSp.com/saml", + }, + ], + "samlp:NameIDPolicy": [ + { + $: { + AllowCreate: "true", + Format: "alternateIdentifier", + "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", + }, + }, + ], + "samlp:RequestedAuthnContext": [ + { + $: { + Comparison: "exact", + "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", + }, + "saml:AuthnContextClassRef": [ + { + $: { + "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion", + }, + _: "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport", + }, + ], + }, + ], + "samlp:Scoping": [ + { + $: { + "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", + }, + "samlp:IDPList": [ + { + $: { + "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", + }, + "samlp:IDPEntry": [ + { + $: { + ProviderID: "myScopingProviderId", + "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", + }, + }, + { + $: { + ProviderID: "myOtherScopingProviderId", + "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", + }, + }, + ], + }, + ], + }, + ], + }, + }, + }, +]; + +export const logoutChecks: CapturedCheck[] = [ + { + name: "Logout", + config: { + skipRequestCompression: true, + entryPoint: "https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO", + cert: fs.readFileSync(__dirname + "/static/cert.pem", "ascii"), + identifierFormat: "urn:oasis:names:tc:SAML:2.0:nameid-format:transient", + }, + samlRequest: { + SAMLRequest: fs.readFileSync( + __dirname + "/static/logout_request_with_good_signature.xml", + "base64" + ), + }, + expectedStatusCode: 200, + mockDate: "2014-06-02T17:48:56.820Z", + result: { + "samlp:LogoutResponse": { + $: { + "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", + "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion", + Version: "2.0", + Destination: "https://wwwexampleIdp.com/saml", + InResponseTo: "pfxd4d369e8-9ea1-780c-aff8-a1d11a9862a1", + }, + "saml:Issuer": ["onelogin_saml"], + "samlp:Status": [ + { + "samlp:StatusCode": [ + { + $: { + Value: "urn:oasis:names:tc:SAML:2.0:status:Success", + }, + }, + ], + }, + ], + }, + }, + }, +]; + +describe("captured SAML requests /", function () { + let server: Server; + + function testForCheck(check: SamlCheck) { + return function (done: Mocha.Done) { + const app = express(); + try { + app.use(bodyParser.urlencoded({ extended: false })); + app.use(passport.initialize()); + const config = check.config; + config.callbackUrl = "http://localhost:3033/login"; + config.entryPoint = "https://wwwexampleIdp.com/saml"; + let profile: Profile; + const strategy = new SamlStrategy( + config, + function (_profile: Profile | null | undefined, done: VerifiedCallback) { + if (_profile) { + profile = _profile; + done(null, profile); + } + } + ); + passport.use(strategy); + + let userSerialized = false; + passport.serializeUser(function (user, done) { + userSerialized = true; + done(null, user); + }); + } catch (err) { + done(err); + } + + app.get( + "/login", + passport.authenticate("saml", { + samlFallback: "login-request", + session: false, + } as AuthenticateOptions), + function (req, res) { + res.status(200).send("200 OK"); + } + ); + + app.use(function ( + err: Error | null, + req: express.Request, + res: express.Response, + next: express.NextFunction + ) { + if (err) { + done(err); + res.status(500).send("500 Internal Server Error"); + } + }); + + server = app.listen(3033, function () { + const requestOpts = { + url: "http://localhost:3033/login", + method: "get", + followRedirect: false, + }; + + function helper(err: Error | null, samlRequest: Buffer) { + try { + should.not.exist(err); + parseString(samlRequest.toString(), function (err, doc) { + try { + should.not.exist(err); + delete doc["samlp:AuthnRequest"]["$"]["ID"]; + delete doc["samlp:AuthnRequest"]["$"]["IssueInstant"]; + doc.should.eql(check.result); + done(); + } catch (err2) { + done(err2); + } + }); + } catch (err3) { + done(err3); + } + } + + // TODO remove usage of request module + request(requestOpts, function (err: Error | null, response: any, body: any) { + try { + should.not.exist(err); + + let encodedSamlRequest; + if (check.config.authnRequestBinding === "HTTP-POST") { + response.statusCode.should.equal(200); + body.should.match(/[^]*/); + encodedSamlRequest = body.match(/ undefined; describe("MultiSamlStrategy()", function () { it("extends passport Strategy", function () { - function getSamlOptions() { - return {}; + function getSamlOptions(): SamlConfig { + return { cert: FAKE_CERT }; } - const strategy = new MultiSamlStrategy({ getSamlOptions: getSamlOptions }, noop); + const strategy = new MultiSamlStrategy({ getSamlOptions }, noop); strategy.should.be.an.instanceOf(SamlStrategy); }); @@ -26,7 +27,7 @@ describe("MultiSamlStrategy()", function () { function createStrategy() { return new MultiSamlStrategy({} as MultiSamlConfig, noop); } - should.throws(createStrategy); + assert.throws(createStrategy); }); }); @@ -43,7 +44,7 @@ describe("MultiSamlStrategy#authenticate", function () { const superAuthenticateStub = this.superAuthenticateStub; function getSamlOptions(req: express.Request, fn: SamlOptionsCallback) { try { - fn(null, {}); + fn(null, { cert: FAKE_CERT }); sinon.assert.calledOnce(superAuthenticateStub); done(); } catch (err2) { @@ -53,7 +54,7 @@ describe("MultiSamlStrategy#authenticate", function () { const strategy = new MultiSamlStrategy( { - getSamlOptions: getSamlOptions, + getSamlOptions, }, noop ); @@ -65,7 +66,7 @@ describe("MultiSamlStrategy#authenticate", function () { passReqToCallback: true, getSamlOptions: function (req: express.Request, fn: SamlOptionsCallback) { try { - fn(null, {}); + fn(null, { cert: FAKE_CERT }); strategy._passReqToCallback!.should.eql(true); done(); } catch (err2) { @@ -106,7 +107,7 @@ describe("MultiSamlStrategy#authenticate", function () { } const strategy = new MultiSamlStrategy( - { getSamlOptions: getSamlOptions, cacheProvider: "mock cache provider" as any }, + { getSamlOptions, cacheProvider: "mock cache provider" as any }, noop ); strategy.authenticate("random" as any, "random" as any); @@ -114,32 +115,40 @@ describe("MultiSamlStrategy#authenticate", function () { }); describe("MultiSamlStrategy#authorize", function () { + let getAuthorizeFormStub: sinon.SinonStub; + let getAuthorizeUrlStub: sinon.SinonStub; + let errorStub: sinon.SinonStub; + beforeEach(function () { - this.getAuthorizeFormStub = sinon.stub(SAML.prototype, "getAuthorizeFormAsync").resolves(); - this.getAuthorizeUrlStub = sinon.stub(SAML.prototype, "getAuthorizeUrlAsync").resolves(); + getAuthorizeFormStub = sinon.stub(SAML.prototype, "getAuthorizeFormAsync").resolves(); + getAuthorizeUrlStub = sinon.stub(SAML.prototype, "getAuthorizeUrlAsync").resolves(); + errorStub = sinon.stub(MultiSamlStrategy.prototype, "error"); }); afterEach(function () { - this.getAuthorizeFormStub.restore(); - this.getAuthorizeUrlStub.restore(); + getAuthorizeFormStub.restore(); + getAuthorizeUrlStub.restore(); + errorStub.restore(); }); it("calls getAuthorizeForm when authnRequestBinding is HTTP-POST", function () { function getSamlOptions(req: express.Request, fn: SamlOptionsCallback) { - fn(null, { authnRequestBinding: "HTTP-POST" }); + fn(null, { authnRequestBinding: "HTTP-POST", cert: FAKE_CERT }); } const strategy = new MultiSamlStrategy({ getSamlOptions }, noop); strategy.authenticate({} as RequestWithUser, {}); - sinon.assert.calledOnce(this.getAuthorizeFormStub); + sinon.assert.notCalled(errorStub); + sinon.assert.calledOnce(getAuthorizeFormStub); }); it("calls getAuthorizeUrl when authnRequestBinding is not HTTP-POST", function () { function getSamlOptions(req: express.Request, fn: SamlOptionsCallback) { - fn(null, {}); + fn(null, { cert: FAKE_CERT }); } const strategy = new MultiSamlStrategy({ getSamlOptions }, noop); strategy.authenticate({} as RequestWithUser, {}); - sinon.assert.calledOnce(this.getAuthorizeUrlStub); + sinon.assert.notCalled(errorStub); + sinon.assert.calledOnce(getAuthorizeUrlStub); }); }); @@ -156,7 +165,7 @@ describe("MultiSamlStrategy#logout", function () { const superLogoutMock = this.superLogoutMock; function getSamlOptions(req: express.Request, fn: SamlOptionsCallback) { try { - fn(null); + fn(null, { cert: FAKE_CERT }); sinon.assert.calledOnce(superLogoutMock); done(); } catch (err2) { @@ -164,7 +173,7 @@ describe("MultiSamlStrategy#logout", function () { } } - const strategy = new MultiSamlStrategy({ getSamlOptions: getSamlOptions }, noop); + const strategy = new MultiSamlStrategy({ getSamlOptions }, noop); strategy.logout("random" as any, "random" as any); }); @@ -173,7 +182,7 @@ describe("MultiSamlStrategy#logout", function () { passReqToCallback: true, getSamlOptions: function (req: express.Request, fn: SamlOptionsCallback) { try { - fn(null, {}); + fn(null, { cert: FAKE_CERT }); strategy._passReqToCallback!.should.eql(true); done(); } catch (err2) { @@ -212,7 +221,7 @@ describe("MultiSamlStrategy#logout", function () { } } - const strategy = new MultiSamlStrategy({ getSamlOptions: getSamlOptions }, noop); + const strategy = new MultiSamlStrategy({ getSamlOptions }, noop); strategy.logout("random" as any, sinon.spy()); }); }); @@ -232,7 +241,7 @@ describe("MultiSamlStrategy#generateServiceProviderMetadata", function () { const superGenerateServiceProviderMetadata = this.superGenerateServiceProviderMetadata; function getSamlOptions(req: express.Request, fn: SamlOptionsCallback) { try { - fn(null); + fn(null, { cert: FAKE_CERT }); sinon.assert.calledOnce(superGenerateServiceProviderMetadata); superGenerateServiceProviderMetadata.calledWith("bar", "baz"); req.should.eql("foo"); @@ -242,17 +251,17 @@ describe("MultiSamlStrategy#generateServiceProviderMetadata", function () { } } - const strategy = new MultiSamlStrategy({ getSamlOptions: getSamlOptions }, noop); + const strategy = new MultiSamlStrategy({ getSamlOptions }, noop); strategy.generateServiceProviderMetadata("foo" as any, "bar", "baz", noop); }); it("passes options on to saml strategy", function (done) { - const passportOptions = { + const passportOptions: MultiSamlConfig = { passReqToCallback: true, getSamlOptions: function (req: express.Request, fn: SamlOptionsCallback) { try { - fn(null); + fn(null, { cert: FAKE_CERT }); strategy._passReqToCallback!.should.eql(true); done(); } catch (err2) { @@ -268,7 +277,7 @@ describe("MultiSamlStrategy#generateServiceProviderMetadata", function () { it("should pass error to callback function", function (done) { const passportOptions = { getSamlOptions: function (req: express.Request, fn: SamlOptionsCallback) { - fn(new Error("My error"), {}); + fn(new Error("My error")); }, }; @@ -286,7 +295,7 @@ describe("MultiSamlStrategy#generateServiceProviderMetadata", function () { it("should pass result to callback function", function (done) { const passportOptions = { getSamlOptions: function (req: express.Request, fn: SamlOptionsCallback) { - fn(null, {}); + fn(null, { cert: FAKE_CERT }); }, }; diff --git a/test/saml-post-signing-tests.spec.ts b/test/saml-post-signing-tests.spec.ts index 70509667..02bb604a 100644 --- a/test/saml-post-signing-tests.spec.ts +++ b/test/saml-post-signing-tests.spec.ts @@ -1,5 +1,4 @@ import * as fs from "fs"; -import "should"; import { signSamlPost, signAuthnRequestPost } from "../src/passport-saml/saml-post-signing"; import { SamlOptions, SamlSigningOptions } from "../src/passport-saml/types"; diff --git a/test/samlTests.spec.ts b/test/samlTests.spec.ts index c0892509..bd20826e 100644 --- a/test/samlTests.spec.ts +++ b/test/samlTests.spec.ts @@ -2,14 +2,11 @@ import * as fs from "fs"; import * as url from "url"; import * as should from "should"; -import express = require("express"); +import assert = require("assert"); import { SAML } from "../src/passport-saml/saml"; -import { - RequestWithUser, - Profile, - AuthenticateOptions, - AuthorizeOptions, -} from "../src/passport-saml/types"; +import { RequestWithUser, AuthenticateOptions, AuthorizeOptions } from "../src/passport-saml/types"; +import { assertRequired } from "../src/passport-saml/utility"; +import { FAKE_CERT } from "./types"; describe("SAML.js", function () { describe("get Urls", function () { @@ -20,6 +17,7 @@ describe("SAML.js", function () { saml = new SAML({ entryPoint: "https://exampleidp.com/path?key=value", logoutUrl: "https://exampleidp.com/path?key=value", + cert: FAKE_CERT, }); req = { protocol: "https", @@ -106,8 +104,11 @@ describe("SAML.js", function () { describe("getLogoutResponseUrl", function () { it("calls callback with right host", function (done) { saml.getLogoutResponseUrl(req, {}, function (err, target) { + should.not.exist(err); try { - url.parse(target!).host!.should.equal("exampleidp.com"); + target = assertRequired(target); + const parsed = url.parse(target); + assert.strictEqual(parsed.host, "exampleidp.com"); done(); } catch (err2) { done(err2); @@ -116,8 +117,11 @@ describe("SAML.js", function () { }); it("calls callback with right protocol", function (done) { saml.getLogoutResponseUrl(req, {}, function (err, target) { + should.not.exist(err); try { - url.parse(target!).protocol!.should.equal("https:"); + target = assertRequired(target); + const parsed = url.parse(target); + assert.strictEqual(parsed.protocol, "https:"); done(); } catch (err2) { done(err2); @@ -126,8 +130,11 @@ describe("SAML.js", function () { }); it("calls callback with right path", function (done) { saml.getLogoutResponseUrl(req, {}, function (err, target) { + should.not.exist(err); try { - url.parse(target!).pathname!.should.equal("/path"); + target = assertRequired(target); + const parsed = url.parse(target); + assert.strictEqual(parsed.pathname, "/path"); done(); } catch (err2) { done(err2); @@ -136,8 +143,11 @@ describe("SAML.js", function () { }); it("calls callback with original query string", function (done) { saml.getLogoutResponseUrl(req, {}, function (err, target) { + should.not.exist(err); try { - url.parse(target!, true).query["key"]!.should.equal("value"); + target = assertRequired(target); + const parsed = url.parse(target, true); + assert.strictEqual(parsed.query["key"], "value"); done(); } catch (err2) { done(err2); @@ -146,11 +156,13 @@ describe("SAML.js", function () { }); it("calls callback with additional run-time params in query string", function (done) { saml.getLogoutResponseUrl(req, options, function (err, target) { + should.not.exist(err); try { - Object.keys(url.parse(target!, true).query).should.have.length(3); - url.parse(target!, true).query["key"]!.should.equal("value"); - url.parse(target!, true).query["SAMLResponse"]!.should.not.be.empty(); - url.parse(target!, true).query["additionalKey"]!.should.equal("additionalValue"); + target = assertRequired(target); + const parsed = url.parse(target, true); + assert.strictEqual(parsed.query["key"], "value"); + should.exist(parsed.query["SAMLResponse"]); + assert.strictEqual(parsed.query["additionalKey"], "additionalValue"); done(); } catch (err2) { done(err2); @@ -160,8 +172,11 @@ describe("SAML.js", function () { // NOTE: This test only tests existence of the assertion, not the correctness it("calls callback with saml response object", function (done) { saml.getLogoutResponseUrl(req, {}, function (err, target) { + should.not.exist(err); try { - should(url.parse(target!, true).query).have.property("SAMLResponse"); + target = assertRequired(target); + const parsed = url.parse(target, true); + should(parsed.query).have.property("SAMLResponse"); done(); } catch (err2) { done(err2); @@ -185,9 +200,8 @@ describe("SAML.js", function () { result.should.equal(regular); }); - it("does nothing to falsy", function () { - const result = saml.keyToPEM(null as any); - should.equal(result, null); + it("fails with falsy", function () { + assert.throws(() => saml.keyToPEM(null as any)); }); it("does nothing to non strings", function () { diff --git a/test/static/response-with-uncomplete-attribute.xml b/test/static/response-with-uncomplete-attribute.xml index c219abbe..f764f35e 100644 --- a/test/static/response-with-uncomplete-attribute.xml +++ b/test/static/response-with-uncomplete-attribute.xml @@ -1,54 +1,38 @@ - - -https://evil-corp.com - - - + + + https://evil-corp.com + - - - - - - -U1QoTD0C6ikXZ1QIkJqlJ5BHQws= - - -wuA92x0HttYKetEqmdm+zzUG7SwRTt3B60hacbT0i4UEA6UBU472toKBgzDWIiVAfKGXyRr8wd7d4mxnc4XeFhREX8H2RRNGy6csMx8YObQnHs9N+WswYyB4Y6XpM1oMLC3Bj+oQyl0bTlXc1J6cscQ37GcrKBXp7uWeentzp3AxhnxY+jyERfY34ShNg8sFHppXT36wqGrj/9r2QHMY66+ydfhFD8Q1QysVF4+lsThkVoqHbnCUocmikAsQyelX5SO4QBvZG9RF3S55MTVP8v0aCa9X2NKNytDvW28NpnrKfkEVyRMEbSH/qZ4bb/mdgY80i3UFYfTvkjIz9jwqBA== - - -MIIEBzCCAu+gAwIBAgIJAMFLV+m1vy2CMA0GCSqGSIb3DQEBCwUAMIGZMQswCQYDVQQGEwJGUjEVMBMGA1UECAwMc29tZS1jb21wYW55MQ4wDAYDVQQHDAVQYXJpczEVMBMGA1UECgwMc29tZS1jb21wYW55MRIwEAYDVQQLDAlldmlsLWNvcnAxFjAUBgNVBAMMDWV2aWwtY29ycC5jb20xIDAeBgkqhkiG9w0BCQEWEWJpZ0BldmlsLWNvcnAuY29tMB4XDTE1MDkwODE0NTMyN1oXDTI1MDkwNzE0NTMyN1owgZkxCzAJBgNVBAYTAkZSMRUwEwYDVQQIDAxzb21lLWNvbXBhbnkxDjAMBgNVBAcMBVBhcmlzMRUwEwYDVQQKDAxzb21lLWNvbXBhbnkxEjAQBgNVBAsMCWV2aWwtY29ycDEWMBQGA1UEAwwNZXZpbC1jb3JwLmNvbTEgMB4GCSqGSIb3DQEJARYRYmlnQGV2aWwtY29ycC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDXepJd/fXaODEXNqfvHWtQfumyS0VUnPb7oQzNuC/5IYPwtM/hGhyfdIaNsAEszjsxvmcJP7eVJH4JO4N5CoCTnsQ8SkA6x9xldQQZbnlkty0oPDRDspl/XNqlfODWXz6SlavUrMWg7knUo4RXyvgvIbMC0u4W9lDK0ujMWrjmGaf1/c0r7+dyUZ3yKBv+ZAxZuFYwRAruG1wwLWcsqp3g8n9USMqyEVz7KnP8sJd3rmCKLXFfXZRCQvQMaBNHGLsHR4LH3s6729roQc6zz1qO/pb9cBGrHW3QMWbDtcU3tsKQKJ1WBKWy96SH+X8RgmF3e/HzB6s+HSJXrPbMarKtAgMBAAGjUDBOMB0GA1UdDgQWBBScdrSwdMQDoEVeHn7XhbUiAOiEpjAfBgNVHSMEGDAWgBScdrSwdMQDoEVeHn7XhbUiAOiEpjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQB5MAIw2tRI+MWi7gFf6xSL7DjuRcUaSPL5/c0SFHpDvczhkel6/Sl4L0ERZd8WxNKffROWGWTfhz07gY6EcuNByz2XkWKEjzJmoy8rwPNum2pLXI9Qdp1RK9/sU2mvJfKAXN/kWMUPazmm4LjIxCEmS1Yk72YBS9lfqGk4rrWIJyU5Z5/Bqq2gjmdKH10eLpzxjLQ3kHWbkDUPmmR+iHCLqSDH4NB/JY0GnYKgr8Z3+CozB7907quhcOBLQbXTV1Ct95klfPUWfS/1vUJ62JXXVXLzl0g8f9ZuoZgP4SlPx7Ya6vXpGkF8b11ABOv2ln/PZ7S7rKHRNIZkiU69ezIW - - - - - - - - https://evil-corp.com - - vincent.vega@evil-corp.com - - - - - - - - urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport - - - - - vincent.vega@evil-corp.com - - - - Vincent - - - VEGA - - - - + H3LP+myWbIxzQ4sIEocOo7Qt7wE=R3BJOjqfCTgMcL5nP+5dnP2dAtd2LO4rRKLSpky3AMW5aNr0hQYVOrq3KiGVxNZWFZUdYoJi/VEbMbaSAMO6YWm/P/k2OnqvFCZjGGLYEfOmsm0unnlL9uQL3MsPjulOgssa29tvB1CfG1PAwBUpN/JIPMEYj2pmhxnr2/kZMGpQj2LfuFTnqRNCVX1jteSjoyB1DuzDeU+qDm8vEhNTGwe8kwqnjM7llUVW9FGe6QMDpE3QFdxb/1GwaIiFBkWpqEckjv8OUlqjehyi+3bby82l+OIywFFfu/+K4AMdXpxnt5rE56RJmKXx+u4WkWBeuwWR3ytVyqeCUFXYZYAamQ== +MIIDtTCCAp2gAwIBAgIJAKg4VeVcIDz1MA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMTUwODEzMDE1NDIwWhcNMTUwOTEyMDE1NDIwWjBFMQswCQYDVQQGEwJVUzETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxG3ouM7U+fXbJt69X1H6d4UNg/uRr06pFuU9RkfIwNC+yaXyptqB3ynXKsL7BFt4DCd0fflRvJAx3feJIDp16wN9GDVHcufWMYPhh2j5HcTW/j9JoIJzGhJyvO00YKBt+hHy83iN1SdChKv5y0iSyiPP5GnqFw+ayyHoM6hSO0PqBou1Xb0ZSIE+DHosBnvVna5w2AiPY4xrJl9yZHZ4Q7DfMiYTgstjETio4bX+6oLiBnYktn7DjdEslqhffVme4PuBxNojI+uCeg/sn4QVLd/iogMJfDWNuLD8326Mi/FE9cCRvFlvAiMSaebMI3zPaySsxTK7Zgj5TpEbmbHI9wIDAQABo4GnMIGkMB0GA1UdDgQWBBSVGgvoW4MhMuzBGce29PY8vSzHFzB1BgNVHSMEbjBsgBSVGgvoW4MhMuzBGce29PY8vSzHF6FJpEcwRTELMAkGA1UEBhMCVVMxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAKg4VeVcIDz1MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAJu1rqs+anD74dbdwgd3CnqnQsQDJiEXmBhG2leaGt3ve9b/9gKaJg2pyb2NyppDe1uLqh6nNXDuzg1oNZrPz5pJL/eCXPl7FhxhMUi04TtLf8LeNTCIWYZiFuO4pmhohHcv8kRvYR1+6SkLTC8j/TZerm7qvesSiTQFNapa1eNdVQ8nFwVkEtWl+JzKEM1BlRcn42sjJkijeFp7DpI7pU+PnYeiaXpRv5pJo8ogM1iFxN+SnfEs0EuQ7fhKIG9aHKi7bKZ7L6SyX7MDIGLeulEU6lf5D9BfXNmcMambiS0pXhL2QXajt96UBq8FT2KNXY8XNtR4y6MyyCzhaiZZcc8= + + + + + https://evil-corp.com + + vincent.vega@evil-corp.com + + + + + + + + urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport + + + + + vincent.vega@evil-corp.com + + + + Vincent + + + VEGA + + + + \ No newline at end of file diff --git a/test/strategy.spec.ts b/test/strategy.spec.ts index 411813d1..0f64a454 100644 --- a/test/strategy.spec.ts +++ b/test/strategy.spec.ts @@ -1,36 +1,46 @@ "use strict"; import * as sinon from "sinon"; -import { Strategy as SamlStrategy, SAML } from "../src/passport-saml"; +import { SAML, Strategy as SamlStrategy } from "../src/passport-saml"; import { RequestWithUser } from "../src/passport-saml/types"; +import { FAKE_CERT } from "./types"; const noop = () => undefined; describe("strategy#authorize", function () { + let getAuthorizeFormStub: sinon.SinonStub; + let getAuthorizeUrlStub: sinon.SinonStub; + let errorStub: sinon.SinonStub; + beforeEach(function () { - this.getAuthorizeFormStub = sinon.stub(SAML.prototype, "getAuthorizeFormAsync").resolves(); - this.getAuthorizeUrlStub = sinon.stub(SAML.prototype, "getAuthorizeUrlAsync").resolves(); + getAuthorizeFormStub = sinon.stub(SAML.prototype, "getAuthorizeFormAsync").resolves(); + getAuthorizeUrlStub = sinon.stub(SAML.prototype, "getAuthorizeUrlAsync").resolves(); + errorStub = sinon.stub(SamlStrategy.prototype, "error"); }); afterEach(function () { - this.getAuthorizeFormStub.restore(); - this.getAuthorizeUrlStub.restore(); + getAuthorizeFormStub.restore(); + getAuthorizeUrlStub.restore(); + errorStub.restore(); }); it("calls getAuthorizeForm when authnRequestBinding is HTTP-POST", function () { const strategy = new SamlStrategy( { authnRequestBinding: "HTTP-POST", + cert: FAKE_CERT, }, noop ); strategy.authenticate({} as RequestWithUser, {}); - sinon.assert.calledOnce(this.getAuthorizeFormStub); + sinon.assert.notCalled(errorStub); + sinon.assert.calledOnce(getAuthorizeFormStub); }); it("calls getAuthorizeUrl when authnRequestBinding is not HTTP-POST", function () { - const strategy = new SamlStrategy({}, noop); + const strategy = new SamlStrategy({ cert: FAKE_CERT }, noop); strategy.authenticate({} as RequestWithUser, {}); - sinon.assert.calledOnce(this.getAuthorizeUrlStub); + sinon.assert.notCalled(errorStub); + sinon.assert.calledOnce(getAuthorizeUrlStub); }); }); diff --git a/test/test-signatures.spec.ts b/test/test-signatures.spec.ts index 8daac061..8bf8f7b8 100644 --- a/test/test-signatures.spec.ts +++ b/test/test-signatures.spec.ts @@ -1,7 +1,6 @@ import { SAML } from "../lib/passport-saml/index.js"; import * as fs from "fs"; import * as sinon from "sinon"; -import "should"; import assert = require("assert"); const cert = fs.readFileSync(__dirname + "/static/cert.pem", "ascii"); @@ -11,13 +10,6 @@ describe("Signatures", function () { createBody = (pathToXml: string) => ({ SAMLResponse: fs.readFileSync(__dirname + "/static/signatures" + pathToXml, "base64"), }), - tryCatchTest = (done: Mocha.Done, func: any) => (...args: any) => { - try { - func(...args); - } catch (ex) { - done(ex); - } - }, testOneResponseBody = async ( samlResponseBody: Record, shouldErrorWith: string | false | undefined, diff --git a/test/tests.spec.ts b/test/tests.spec.ts index 253b5e6b..fbe25f4e 100644 --- a/test/tests.spec.ts +++ b/test/tests.spec.ts @@ -1,1480 +1,143 @@ "use strict"; import * as express from "express"; -import * as bodyParser from "body-parser"; -import * as passport from "passport"; import { Strategy as SamlStrategy, SAML } from "../src/passport-saml"; -import request = require("request"); import url = require("url"); -import "should"; -import * as zlib from "zlib"; import * as querystring from "querystring"; import { parseString } from "xml2js"; import * as fs from "fs"; import * as sinon from "sinon"; -import { - AuthenticateOptions, - Profile, - RACComparision, - RequestWithUser, - SamlConfig, - SamlOptions, - VerifiedCallback, -} from "../src/passport-saml/types.js"; +import { RacComparision, RequestWithUser, SamlConfig } from "../src/passport-saml/types.js"; import * as should from "should"; -import { Server } from "http"; import assert = require("assert"); +import { FAKE_CERT, TEST_CERT } from "./types"; +import { signXmlResponse } from "../src/passport-saml/utility"; -// a certificate which is re-used by several tests -const TEST_CERT = - "MIIEFzCCAv+gAwIBAgIUFJsUjPM7AmWvNtEvULSHlTTMiLQwDQYJKoZIhvcNAQEFBQAwWDELMAkGA1UEBhMCVVMxETAPBgNVBAoMCFN1YnNwYWNlMRUwEwYDVQQLDAxPbmVMb2dpbiBJZFAxHzAdBgNVBAMMFk9uZUxvZ2luIEFjY291bnQgNDIzNDkwHhcNMTQwNTEzMTgwNjEyWhcNMTkwNTE0MTgwNjEyWjBYMQswCQYDVQQGEwJVUzERMA8GA1UECgwIU3Vic3BhY2UxFTATBgNVBAsMDE9uZUxvZ2luIElkUDEfMB0GA1UEAwwWT25lTG9naW4gQWNjb3VudCA0MjM0OTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKrAzJdY9FzFLt5blArJfPzgi87EnFGlTfcV5T1TUDwLBlDkY/0ZGKnMOpf3D7ie2C4pPFOImOogcM5kpDDL7qxTXZ1ewXVyjBdMu29NG2C6NzWeQTUMUji01EcHkC8o+Pts8ANiNOYcjxEeyhEyzJKgEizblYzMMKzdrOET6QuqWo3C83K+5+5dsjDn1ooKGRwj3HvgsYcFrQl9NojgQFjoobwsiE/7A+OJhLpBcy/nSVgnoJaMfrO+JsnukZPztbntLvOl56+Vra0N8n5NAYhaSayPiv/ayhjVgjfXd1tjMVTOiDknUOwizZuJ1Y3QH94vUtBgp0WBpBSs/xMyTs8CAwEAAaOB2DCB1TAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBRQO4WpM5fWwxib49WTuJkfYDbxODCBlQYDVR0jBIGNMIGKgBRQO4WpM5fWwxib49WTuJkfYDbxOKFcpFowWDELMAkGA1UEBhMCVVMxETAPBgNVBAoMCFN1YnNwYWNlMRUwEwYDVQQLDAxPbmVMb2dpbiBJZFAxHzAdBgNVBAMMFk9uZUxvZ2luIEFjY291bnQgNDIzNDmCFBSbFIzzOwJlrzbRL1C0h5U0zIi0MA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQUFAAOCAQEACdDAAoaZFCEY5pmfwbKuKrXtO5iE8lWtiCPjCZEUuT6bXRNcqrdnuV/EAfX9WQoXjalPi0eM78zKmbvRGSTUHwWw49RHjFfeJUKvHNeNnFgTXDjEPNhMvh69kHm453lFRmB+kk6yjtXRZaQEwS8Uuo2Ot+krgNbl6oTBZJ0AHH1MtZECDloms1Km7zsK8wAi5i8TVIKkVr5b2VlhrLgFMvzZ5ViAxIMGB6w47yY4QGQB/5Q8ya9hBs9vkn+wubA+yr4j14JXZ7blVKDSTYva65Ea+PqHyrp+Wnmnbw2ObS7iWexiTy1jD3G0R2avDBFjM8Fj5DbfufsE1b0U10RTtg=="; -const ALT_TEST_CERT = - "MIIEOTCCAyGgAwIBAgIJAKZgJdKdCdL6MA0GCSqGSIb3DQEBBQUAMHAxCzAJBgNVBAYTAkFVMREwDwYDVQQIEwhWaWN0b3JpYTESMBAGA1UEBxMJTWVsYm91cm5lMSEwHwYDVQQKExhUYWJjb3JwIEhvbGRpbmdzIExpbWl0ZWQxFzAVBgNVBAMTDnN0cy50YWIuY29tLmF1MB4XDTE3MDUzMDA4NTQwOFoXDTI3MDUyODA4NTQwOFowcDELMAkGA1UEBhMCQVUxETAPBgNVBAgTCFZpY3RvcmlhMRIwEAYDVQQHEwlNZWxib3VybmUxITAfBgNVBAoTGFRhYmNvcnAgSG9sZGluZ3MgTGltaXRlZDEXMBUGA1UEAxMOc3RzLnRhYi5jb20uYXUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQD0NuMcflq3rtupKYDf4a7lWmsXy66fYe9n8jB2DuLMakEJBlzn9j6B98IZftrilTq21VR7wUXROxG8BkN8IHY+l8X7lATmD28fFdZJj0c8Qk82eoq48faemth4fBMx2YrpnhU00jeXeP8dIIaJTPCHBTNgZltMMhphklN1YEPlzefJs3YD+Ryczy1JHbwETxt+BzO1JdjBe1fUTyl6KxAwWvtsNBURmQRYlDOk4GRgdkQnfxBuCpOMeOpV8wiBAi3h65Lab9C5avu4AJlA9e4qbOmWt6otQmgy5fiJVy6bH/d8uW7FJmSmePX9sqAWa9szhjdn36HHVQsfHC+IUEX7AgMBAAGjgdUwgdIwHQYDVR0OBBYEFN6z6cuxY7FTkg1S/lIjnS4x5ARWMIGiBgNVHSMEgZowgZeAFN6z6cuxY7FTkg1S/lIjnS4x5ARWoXSkcjBwMQswCQYDVQQGEwJBVTERMA8GA1UECBMIVmljdG9yaWExEjAQBgNVBAcTCU1lbGJvdXJuZTEhMB8GA1UEChMYVGFiY29ycCBIb2xkaW5ncyBMaW1pdGVkMRcwFQYDVQQDEw5zdHMudGFiLmNvbS5hdYIJAKZgJdKdCdL6MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAMi5HyvXgRa4+kKz3dk4SwAEXzeZRcsbeDJWVUxdb6a+JQxIoG7L9rSbd6yZvP/Xel5TrcwpCpl5eikzXB02/C0wZKWicNmDEBlOfw0Pc5ngdoh6ntxHIWm5QMlAfjR0dgTlojN4Msw2qk7cP1QEkV96e2BJUaqaNnM3zMvd7cfRjPNfbsbwl6hCCCAdwrALKYtBnjKVrCGPwO+xiw5mUJhZ1n6ZivTOdQEWbl26UO60J9ItiWP8VK0d0aChn326Ovt7qC4S3AgDlaJwcKe5Ifxl/UOWePGRwXj2UUuDWFhjtVmRntMmNZbe5yE8MkEvU+4/c6LqGwTCgDenRbK53Dg="; - -interface CapturedCheck { - samlRequest?: any; - result?: any; - name: string; - expectedStatusCode: number; - samlResponse?: any; - config: Partial; - expectedNameIDStartsWith?: string; - mockDate: string; -} - -interface SAMLCheck { - samlRequest?: any; - result?: any; - name: string; - samlResponse?: any; - config: Partial; - expectedNameIDStartsWith?: string; -} - -const noop = () => undefined; +export const BAD_TEST_CERT = + "MIIEOTCCAyGgAwIBAgIJAKZgJdKdCdL6MA0GCSqGSIb3DQEBBQUAMHAxCzAJBgNVBAYTAkFVMREwDwYDVQQIEwhWaWN0b3JpYTESMBAGA1UEBxMJTWVsYm91cm5lMSEwHwYDVQQKExhUYWJjb3JwIEhvbGRpbmdzIExpbWl0ZWQxFzAVBgNVBAMTDnN0cy50YWIuY29tLmF1MB4XDTE3MDUzMDA4NTQwOFoXDTI3MDUyODA4NTQwOFowcDELMAkGA1UEBhMCQVUxETAPBgNVBAgTCFZpY3RvcmlhMRIwEAYDVQQHEwlNZWxib3VybmUxITAfBgNVBAoTGFRhYmNvcnAgSG9sZGluZ3MgTGltaXRlZDEXMBUGA1UEAxMOc3RzLnRhYi5jb20uYXUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQD0NuMcflq3rtupKYDf4a7lWmsXy66fYe9n8jB2DuLMakEJBlzn9j6B98IZftrilTq21VR7wUXROxG8BkN8IHY+l8X7lATmD28fFdZJj0c8Qk82eoq48faemth4fBMx2YrpnhU00jeXeP8dIIaJTPCHBTNgZltMMhphklN1YEPlzefJs3YD+Ryczy1JHbwETxt+BzO1JdjBe1fUTyl6KxAwWvtsNBURmQRYlDOk4GRgdkQnfxBuCpOMeOpV8wiBAi3h65Lab9C5avu4AJlA9e4qbOmWt6otQmgy5fiJVy6bH/d8uW7FJmSmePX9sqAWa9szhjdn36HHVQsfHC+IUEX7AgMBAAGjgdUwgdIwHQYDVR0OBBYEFN6z6cuxY7FTkg1S/lIjnS4x5ARWMIGiBgNVHSMEgZowgZeAFN6z6cuxY7FTkg1S/lIjnS4x5ARWoXSkcjBwMQswCQYDVQQGEwJBVTERMA8GA1UECBMIVmljdG9yaWExEjAQBgNVBAcTCU1lbGJvdXJuZTEhMB8GA1UEChMYVGFiY29ycCBIb2xkaW5ncyBMaW1pdGVkMRcwFQYDVQQDEw5zdHMudGFiLmNvbS5hdYIJAKZgJdKdCdL6MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAMi5HyvXgRa4+kKz3dk4SwAEXzeZRcsbeDJWVUxdb6a+JQxIoG7L9rSbd6yZvP/Xel5TrcwpCpl5eikzXB02/C0wZKWicNmDEBlOfw0Pc5ngdoh6ntxHIWm5QMlAfjR0dgTlojN4Msw2qk7cP1QEkV96e2BJUaqaNnM3zMvd7cfRjPNfbsbwl6hCCCAdwrALKYtBnjKVrCGPwO+xiw5mUJhZ1n6ZivTOdQEWbl26UO60J9ItiWP8VK0d0aChn326Ovt7qC4S3AgDlaJwcKe5Ifxl/UOWePGRwXj2UUuDWFhjtVmRntMmNZbe5yE8MkEvU+4/c6LqGwTCgDenRbK53Dgg"; -describe("passport-saml /", function () { - describe("captured saml responses /", function () { - let fakeClock: sinon.SinonFakeTimers; - const capturedChecks: CapturedCheck[] = [ - { - name: "Okta -- valid config should succeed", - samlResponse: { - SAMLResponse: - "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c2FtbDJwOlJlc3BvbnNlIHhtbG5zOnNhbWwycD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sIiBEZXN0aW5hdGlvbj0iaHR0cDovL2xvY2FsaG9zdC9icm93c2VyU2FtbExvZ2luIiBJRD0iaWQzMzcxMDA5NzQ3ODg2OTIzMjIwMzA1Njc3ODMiIEluUmVzcG9uc2VUbz0iXzVjMzg1YWJiMTc3MzViN2FhOGQxIiBJc3N1ZUluc3RhbnQ9IjIwMTQtMDUtMjdUMjM6Mjc6MzUuNDI2WiIgVmVyc2lvbj0iMi4wIj48c2FtbDI6SXNzdWVyIHhtbG5zOnNhbWwyPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIiBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpuYW1laWQtZm9ybWF0OmVudGl0eSI+aHR0cDovL3d3dy5va3RhLmNvbS9rdmpqNDZsc0RRRVFZVURCWklZVzwvc2FtbDI6SXNzdWVyPjxkczpTaWduYXR1cmUgeG1sbnM6ZHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPjxkczpTaWduZWRJbmZvPjxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+PGRzOlNpZ25hdHVyZU1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNyc2Etc2hhMSIvPjxkczpSZWZlcmVuY2UgVVJJPSIjaWQzMzcxMDA5NzQ3ODg2OTIzMjIwMzA1Njc3ODMiPjxkczpUcmFuc2Zvcm1zPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZSIvPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz48L2RzOlRyYW5zZm9ybXM+PGRzOkRpZ2VzdE1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNzaGExIi8+PGRzOkRpZ2VzdFZhbHVlPjRSNUNjYUVEQ3dPSCtudnVHSkY3TWRBelpIdz08L2RzOkRpZ2VzdFZhbHVlPjwvZHM6UmVmZXJlbmNlPjwvZHM6U2lnbmVkSW5mbz48ZHM6U2lnbmF0dXJlVmFsdWU+TzlycW1TUTJQd1NaZDFBeklRUDUySkY5VHBaSkJUNENpdnAxTUZZN3FzODdrU2RFQ3dNbWFWTDE2NFI4dUlCbzdscXUwOFRaTGZYeGF6ak5uRDhnV05BOWRuK2d5cFc1ZWo4S3EzK1J3cUxZUzM1M3pCeVpWcy9MSUxQZVJCK0tUS2RsbTVJSDcxdWhwbUp4a3k0T0gwNWdDSWExYnRFaXpKV1h3d1lleXpBPTwvZHM6U2lnbmF0dXJlVmFsdWU+PGRzOktleUluZm8+PGRzOlg1MDlEYXRhPjxkczpYNTA5Q2VydGlmaWNhdGU+TUlJQ29UQ0NBZ3FnQXdJQkFnSUdBVVk4elZQWU1BMEdDU3FHU0liM0RRRUJCUVVBTUlHVE1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFRwpBMVVFQ0F3S1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ3d05VMkZ1SUVaeVlXNWphWE5qYnpFTk1Bc0dBMVVFQ2d3RVQydDBZVEVVCk1CSUdBMVVFQ3d3TFUxTlBVSEp2ZG1sa1pYSXhGREFTQmdOVkJBTU1DM04xWW5Od1lXTmxjM2N4TVJ3d0dnWUpLb1pJaHZjTkFRa0IKRmcxcGJtWnZRRzlyZEdFdVkyOXRNQjRYRFRFME1EVXlOekE0TWpreU4xb1hEVFEwTURVeU56QTRNekF5TjFvd2daTXhDekFKQmdOVgpCQVlUQWxWVE1STXdFUVlEVlFRSURBcERZV3hwWm05eWJtbGhNUll3RkFZRFZRUUhEQTFUWVc0Z1JuSmhibU5wYzJOdk1RMHdDd1lEClZRUUtEQVJQYTNSaE1SUXdFZ1lEVlFRTERBdFRVMDlRY205MmFXUmxjakVVTUJJR0ExVUVBd3dMYzNWaWMzQmhZMlZ6ZHpFeEhEQWEKQmdrcWhraUc5dzBCQ1FFV0RXbHVabTlBYjJ0MFlTNWpiMjB3Z1o4d0RRWUpLb1pJaHZjTkFRRUJCUUFEZ1kwQU1JR0pBb0dCQUtZWAovVWRWOUhMWGJOS3YweWM2WUpCdHpkV051R1RQSVhXVlBDYjBPc1J2UjU0bHErMFNMUFFHSlMzOWZsV0tpcXRVaUV1c3VpaDZHZExmCk52RkFWTEdQcUJUV1E0TGM3cjUrekQ1cW5INkxpVE0xS0NiYUYzdXUyUCtUK3V0c0dxRlVaYXdBOUZFVk5Ma0lDZTM2WDF5RTQwWCsKL2pNeWd5aWVRUE1lSkFCdkFnTUJBQUV3RFFZSktvWklodmNOQVFFRkJRQURnWUVBSFNXUEc0eTFiTnlKeVNnYkZxRGNTLzZNVU5JcgpTZmtRWUl4eklHUTM3cXk1cUFXMTVZa0JVaVc5VHNHSlJjcmd1c1ZyWklpYTE4ZVl6ODRabjdGeVVaTk1GbzI0TDdsNkJTMFdDaXRJClFTY0JDS0I0UVdBL2Ixc3pxczdFdHBPY1BQMDFUWU5xMnJhcytWcVIzYVdBWExYMm9LZkNsVi9TVXdzelJmNVU4NVk9PC9kczpYNTA5Q2VydGlmaWNhdGU+PC9kczpYNTA5RGF0YT48L2RzOktleUluZm8+PC9kczpTaWduYXR1cmU+PHNhbWwycDpTdGF0dXMgeG1sbnM6c2FtbDJwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiPjxzYW1sMnA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+PC9zYW1sMnA6U3RhdHVzPjxzYW1sMjpBc3NlcnRpb24geG1sbnM6c2FtbDI9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIElEPSJpZDMzNzEwMDk3NDc4OTQ5MTAwNjY2NTIyNTEyIiBJc3N1ZUluc3RhbnQ9IjIwMTQtMDUtMjdUMjM6Mjc6MzUuNDI2WiIgVmVyc2lvbj0iMi4wIj48c2FtbDI6SXNzdWVyIEZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOm5hbWVpZC1mb3JtYXQ6ZW50aXR5IiB4bWxuczpzYW1sMj0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiI+aHR0cDovL3d3dy5va3RhLmNvbS9rdmpqNDZsc0RRRVFZVURCWklZVzwvc2FtbDI6SXNzdWVyPjxkczpTaWduYXR1cmUgeG1sbnM6ZHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPjxkczpTaWduZWRJbmZvPjxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+PGRzOlNpZ25hdHVyZU1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNyc2Etc2hhMSIvPjxkczpSZWZlcmVuY2UgVVJJPSIjaWQzMzcxMDA5NzQ3ODk0OTEwMDY2NjUyMjUxMiI+PGRzOlRyYW5zZm9ybXM+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNlbnZlbG9wZWQtc2lnbmF0dXJlIi8+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPjwvZHM6VHJhbnNmb3Jtcz48ZHM6RGlnZXN0TWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3NoYTEiLz48ZHM6RGlnZXN0VmFsdWU+UFM4dE5taVVZUXpRQVFURjh3RDFRT3dMazA0PTwvZHM6RGlnZXN0VmFsdWU+PC9kczpSZWZlcmVuY2U+PC9kczpTaWduZWRJbmZvPjxkczpTaWduYXR1cmVWYWx1ZT5XM3JiUkNFUmE1ZEtUcy9MSERwY0pEeUhjSzFESjczd2xhT3J2MXZWNmZxQ3FTTUNpclZLRVlLKzR2OG5WaThZNlZwcVJHd0trWFV2cUY0b0hKWjYzOE5YYVRiRVRjU1VXWjVUNXowbzBNVXhBL3RnL01zVzYvRlVRMXBqUWJhVlhJT3Mvc1EwVzluUXpmZkVZLzgrSkVJMldLY0I2UkZlVzVtTlkvdm9oUWM9PC9kczpTaWduYXR1cmVWYWx1ZT48ZHM6S2V5SW5mbz48ZHM6WDUwOURhdGE+PGRzOlg1MDlDZXJ0aWZpY2F0ZT5NSUlDb1RDQ0FncWdBd0lCQWdJR0FVWTh6VlBZTUEwR0NTcUdTSWIzRFFFQkJRVUFNSUdUTVFzd0NRWURWUVFHRXdKVlV6RVRNQkVHCkExVUVDQXdLUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnd3TlUyRnVJRVp5WVc1amFYTmpiekVOTUFzR0ExVUVDZ3dFVDJ0MFlURVUKTUJJR0ExVUVDd3dMVTFOUFVISnZkbWxrWlhJeEZEQVNCZ05WQkFNTUMzTjFZbk53WVdObGMzY3hNUnd3R2dZSktvWklodmNOQVFrQgpGZzFwYm1adlFHOXJkR0V1WTI5dE1CNFhEVEUwTURVeU56QTRNamt5TjFvWERUUTBNRFV5TnpBNE16QXlOMW93Z1pNeEN6QUpCZ05WCkJBWVRBbFZUTVJNd0VRWURWUVFJREFwRFlXeHBabTl5Ym1saE1SWXdGQVlEVlFRSERBMVRZVzRnUm5KaGJtTnBjMk52TVEwd0N3WUQKVlFRS0RBUlBhM1JoTVJRd0VnWURWUVFMREF0VFUwOVFjbTkyYVdSbGNqRVVNQklHQTFVRUF3d0xjM1ZpYzNCaFkyVnpkekV4SERBYQpCZ2txaGtpRzl3MEJDUUVXRFdsdVptOUFiMnQwWVM1amIyMHdnWjh3RFFZSktvWklodmNOQVFFQkJRQURnWTBBTUlHSkFvR0JBS1lYCi9VZFY5SExYYk5LdjB5YzZZSkJ0emRXTnVHVFBJWFdWUENiME9zUnZSNTRscSswU0xQUUdKUzM5ZmxXS2lxdFVpRXVzdWloNkdkTGYKTnZGQVZMR1BxQlRXUTRMYzdyNSt6RDVxbkg2TGlUTTFLQ2JhRjN1dTJQK1QrdXRzR3FGVVphd0E5RkVWTkxrSUNlMzZYMXlFNDBYKwovak15Z3lpZVFQTWVKQUJ2QWdNQkFBRXdEUVlKS29aSWh2Y05BUUVGQlFBRGdZRUFIU1dQRzR5MWJOeUp5U2diRnFEY1MvNk1VTklyClNma1FZSXh6SUdRMzdxeTVxQVcxNVlrQlVpVzlUc0dKUmNyZ3VzVnJaSWlhMThlWXo4NFpuN0Z5VVpOTUZvMjRMN2w2QlMwV0NpdEkKUVNjQkNLQjRRV0EvYjFzenFzN0V0cE9jUFAwMVRZTnEycmFzK1ZxUjNhV0FYTFgyb0tmQ2xWL1NVd3N6UmY1VTg1WT08L2RzOlg1MDlDZXJ0aWZpY2F0ZT48L2RzOlg1MDlEYXRhPjwvZHM6S2V5SW5mbz48L2RzOlNpZ25hdHVyZT48c2FtbDI6U3ViamVjdCB4bWxuczpzYW1sMj0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiI+PHNhbWwyOk5hbWVJRCBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI+YmVuQHN1YnNwYWNlc3cuY29tPC9zYW1sMjpOYW1lSUQ+PHNhbWwyOlN1YmplY3RDb25maXJtYXRpb24gTWV0aG9kPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyIj48c2FtbDI6U3ViamVjdENvbmZpcm1hdGlvbkRhdGEgSW5SZXNwb25zZVRvPSJfNWMzODVhYmIxNzczNWI3YWE4ZDEiIE5vdE9uT3JBZnRlcj0iMjAxNC0wNS0yN1QyMzozMjozNS40MjZaIiBSZWNpcGllbnQ9Imh0dHA6Ly9sb2NhbGhvc3QvYnJvd3NlclNhbWxMb2dpbiIvPjwvc2FtbDI6U3ViamVjdENvbmZpcm1hdGlvbj48L3NhbWwyOlN1YmplY3Q+PHNhbWwyOkNvbmRpdGlvbnMgTm90QmVmb3JlPSIyMDE0LTA1LTI3VDIzOjIyOjM1LjQyNloiIE5vdE9uT3JBZnRlcj0iMjAxNC0wNS0yN1QyMzozMjozNS40MjZaIiB4bWxuczpzYW1sMj0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiI+PHNhbWwyOkF1ZGllbmNlUmVzdHJpY3Rpb24+PHNhbWwyOkF1ZGllbmNlPmh0dHBzOi8vYWRtaW4uc3Vic3BhY2Vzdy5jb208L3NhbWwyOkF1ZGllbmNlPjwvc2FtbDI6QXVkaWVuY2VSZXN0cmljdGlvbj48L3NhbWwyOkNvbmRpdGlvbnM+PHNhbWwyOkF1dGhuU3RhdGVtZW50IEF1dGhuSW5zdGFudD0iMjAxNC0wNS0yN1QyMzoyNzozNS40MjZaIiBTZXNzaW9uSW5kZXg9Il81YzM4NWFiYjE3NzM1YjdhYThkMSIgeG1sbnM6c2FtbDI9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iPjxzYW1sMjpBdXRobkNvbnRleHQ+PHNhbWwyOkF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOlg1MDk8L3NhbWwyOkF1dGhuQ29udGV4dENsYXNzUmVmPjwvc2FtbDI6QXV0aG5Db250ZXh0Pjwvc2FtbDI6QXV0aG5TdGF0ZW1lbnQ+PC9zYW1sMjpBc3NlcnRpb24+PC9zYW1sMnA6UmVzcG9uc2U+", - RelayState: "", - }, - config: { - entryPoint: - "https://subspacesw1.okta.com/app/subspacesw_subspacetest_1/kvjj46lsDQEQYUDBZIYW/sso/saml", - cert: - "MIICoTCCAgqgAwIBAgIGAUY8zVPYMA0GCSqGSIb3DQEBBQUAMIGTMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEUMBIGA1UECwwLU1NPUHJvdmlkZXIxFDASBgNVBAMMC3N1YnNwYWNlc3cxMRwwGgYJKoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMB4XDTE0MDUyNzA4MjkyN1oXDTQ0MDUyNzA4MzAyN1owgZMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMQ0wCwYDVQQKDARPa3RhMRQwEgYDVQQLDAtTU09Qcm92aWRlcjEUMBIGA1UEAwwLc3Vic3BhY2VzdzExHDAaBgkqhkiG9w0BCQEWDWluZm9Ab2t0YS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKYX/UdV9HLXbNKv0yc6YJBtzdWNuGTPIXWVPCb0OsRvR54lq+0SLPQGJS39flWKiqtUiEusuih6GdLfNvFAVLGPqBTWQ4Lc7r5+zD5qnH6LiTM1KCbaF3uu2P+T+utsGqFUZawA9FEVNLkICe36X1yE40X+/jMygyieQPMeJABvAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAHSWPG4y1bNyJySgbFqDcS/6MUNIrSfkQYIxzIGQ37qy5qAW15YkBUiW9TsGJRcrgusVrZIia18eYz84Zn7FyUZNMFo24L7l6BS0WCitIQScBCKB4QWA/b1szqs7EtpOcPP01TYNq2ras+VqR3aWAXLX2oKfClV/SUwszRf5U85Y=", - }, - expectedStatusCode: 200, - expectedNameIDStartsWith: "ben", - mockDate: "2014-05-27T23:29:35.426Z", - }, - { - name: "Okta -- valid encrypted response should succeed", - samlResponse: { - SAMLResponse: - "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c2FtbDJwOlJlc3BvbnNlIHhtbG5zOnNhbWwycD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sIiBEZXN0aW5hdGlvbj0iaHR0cHM6Ly82NGY0MDk0Zi5uZ3Jvay5pby9zc28vc2FtbC9jYWxsYmFjayIgSUQ9ImlkMTk1MjM4OTMzOTcwNDQ4MjIzOTQwNDk3IiBJblJlc3BvbnNlVG89Il9jNjA1MzdkYWQzNGZhNGVjYjYxMyIgSXNzdWVJbnN0YW50PSIyMDE2LTA4LTE5VDAxOjEzOjM4LjEzOVoiIFZlcnNpb249IjIuMCI+PHNhbWwyOklzc3VlciB4bWxuczpzYW1sMj0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6bmFtZWlkLWZvcm1hdDplbnRpdHkiPmh0dHA6Ly93d3cub2t0YS5jb20vZXhrN3hkaTZheFBmb21iengwaDc8L3NhbWwyOklzc3Vlcj48ZHM6U2lnbmF0dXJlIHhtbG5zOmRzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj48ZHM6U2lnbmVkSW5mbz48ZHM6Q2Fub25pY2FsaXphdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPjxkczpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjcnNhLXNoYTEiLz48ZHM6UmVmZXJlbmNlIFVSST0iI2lkMTk1MjM4OTMzOTcwNDQ4MjIzOTQwNDk3Ij48ZHM6VHJhbnNmb3Jtcz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI2VudmVsb3BlZC1zaWduYXR1cmUiLz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+PC9kczpUcmFuc2Zvcm1zPjxkczpEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNzaGEyNTYiLz48ZHM6RGlnZXN0VmFsdWU+enQ3eG5yTDB1Vmt6azN1NHhkS3hVR0ZtbHVLamM4eXlDOWZvanJ4NXFDbz08L2RzOkRpZ2VzdFZhbHVlPjwvZHM6UmVmZXJlbmNlPjwvZHM6U2lnbmVkSW5mbz48ZHM6U2lnbmF0dXJlVmFsdWU+ZUZlczNUUEIvZVFlUVBtUHBPYlA0UDNRWklKRGMxY1cwMk5UOHJlVEp1MG9lZElDTm8vTkJEeE9rMWJoODFJMzlMdi90MWRFM0Z3azBrZ0kzRUswV0s0UVpsSWZ2WkIrTkpPSlRjZW9CUnptTXRBeHI5cVlORWlHSWxZeGdwS1BDaG95OHQzcllMV3ZCZVpJc2pKOC9iYmRudXhjSFY3bXpveFkyNHdtYXFucXExNG5QSTFBZ0lIY1NnTVRhL1lqaFJwWjJlU0o2TU9FMU0vNDByL3VvRUlPWDVjMFVoSE1Gbzh3Yml2NCtvaTJPK2RPOVdha3BDN1Y5cHVGelU0OVF6MGR6bDFXaFIwYlp4VG1TYXI2WUY2NXMwZk5DUnorSGJ5a1h3eFZ4OFo0TTUxRUJHN1NMdWlQVFVJTjFNWVRXZXVoZll6MS9Pc2YvcGoxbHFLYlZBPT08L2RzOlNpZ25hdHVyZVZhbHVlPjxkczpLZXlJbmZvPjxkczpYNTA5RGF0YT48ZHM6WDUwOUNlcnRpZmljYXRlPk1JSURvRENDQW9pZ0F3SUJBZ0lHQVZhWjA0UE9NQTBHQ1NxR1NJYjNEUUVCQlFVQU1JR1FNUXN3Q1FZRFZRUUdFd0pWVXpFVE1CRUcKQTFVRUNBd0tRMkZzYVdadmNtNXBZVEVXTUJRR0ExVUVCd3dOVTJGdUlFWnlZVzVqYVhOamJ6RU5NQXNHQTFVRUNnd0VUMnQwWVRFVQpNQklHQTFVRUN3d0xVMU5QVUhKdmRtbGtaWEl4RVRBUEJnTlZCQU1NQ0daeWIyNTBZWEJ3TVJ3d0dnWUpLb1pJaHZjTkFRa0JGZzFwCmJtWnZRRzlyZEdFdVkyOXRNQjRYRFRFMk1EZ3hOekU0TkRVek1Wb1hEVEkyTURneE56RTRORFl6TVZvd2daQXhDekFKQmdOVkJBWVQKQWxWVE1STXdFUVlEVlFRSURBcERZV3hwWm05eWJtbGhNUll3RkFZRFZRUUhEQTFUWVc0Z1JuSmhibU5wYzJOdk1RMHdDd1lEVlFRSwpEQVJQYTNSaE1SUXdFZ1lEVlFRTERBdFRVMDlRY205MmFXUmxjakVSTUE4R0ExVUVBd3dJWm5KdmJuUmhjSEF4SERBYUJna3Foa2lHCjl3MEJDUUVXRFdsdVptOUFiMnQwWVM1amIyMHdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFDdnBsUU8KTlZ3a25SeTFpQm5hb1p0c096MjhBN1hXMnRScEZXKzBMYTdSSmV4YnppSXdFeTFiUFpFTmhmd2pQWkExb0hIWnFpNWwzMTVCeFhLVwpKcW1tTm1iRENGRG8rL0ZZRkNvSFhsaWlMbTl2cURiUjFicjZCeXFlWTBHZnh5VFBLSFp4YjJGU2VzMzBUZmZEa25wTVFkLzhrQTlZCldhVzV4RGx1Mml2V0pJK3NmY09KT01kNnQrZ2NmWGo1OGE1ZlA4TXdtNlkyMjBLZVpTdnJWcEVWMktEcDlobG43Zmhob3hIWjdLL0IKWWJpZHFkd0x6ZVVRWHBiNkxJcnh0S2R1ZzJGb2ZTK09OczZ5TElRUm1yYkNCN1NWWDFRQThKSW5NbitmenJHdFptRmlIUjBhRmJ5aAppTzc4di91ZkRhNlMrWHBZeXAyYjZENFNuemVnZ25vYkFnTUJBQUV3RFFZSktvWklodmNOQVFFRkJRQURnZ0VCQUoyd2NGVmZmRkhTCmQ5cGo2UmdvTkhYWkJzV3AwSFVack5la2lTYmdvbXI0dFNEZWZXdEtiMDRuRklsUnl0ZlZzL2s3NHdtYk5pUkNFOG5EVkJyQkRGQS8KK1R2LzNQb3daWEhqWEtCb2ZVdVNjVFA0L1R3MU4veXdmN1YrWFk1a1YzVm1MQkw2YXgrVUxKYXVSL1lHSUlNc0ljL3JTMkQwNGFBYwpTY1U5cHFWaDJNTDduVEg3Z0ZxWXJ4eXBhdm1WazZLOTR2TGpzMGdnRjJUR3A3dFhDUmplT2xQUEpTK01PSkhKaFRCV1lGV3ZCTGNsClUzemNyaTN3czdHcUpNcGVpSGE3ck1vSFYwb254V3NaVFpXNTd5YmFJV0tMdDFnb0Fvb0M3aHEwcng3b05sT3ZyeXM1bGxsaEJ5U1kKWUMzeWNxY2EvRDArR3hYTGNFcjlRd1A3VFZ3PTwvZHM6WDUwOUNlcnRpZmljYXRlPjwvZHM6WDUwOURhdGE+PC9kczpLZXlJbmZvPjwvZHM6U2lnbmF0dXJlPjxzYW1sMnA6U3RhdHVzIHhtbG5zOnNhbWwycD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sIj48c2FtbDJwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPjwvc2FtbDJwOlN0YXR1cz48c2FtbDI6RW5jcnlwdGVkQXNzZXJ0aW9uIHhtbG5zOnNhbWwyPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIj48eGVuYzpFbmNyeXB0ZWREYXRhIHhtbG5zOnhlbmM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jIyIgSWQ9Il85ZThmZDJjZTRhZDAyMTJjOThlNzA5MWNhODc2NWZiNCIgVHlwZT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjRWxlbWVudCI+PHhlbmM6RW5jcnlwdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI2FlczI1Ni1jYmMiIHhtbG5zOnhlbmM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jIyIvPjxkczpLZXlJbmZvIHhtbG5zOmRzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj48ZHM6UmV0cmlldmFsTWV0aG9kIFR5cGU9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI0VuY3J5cHRlZEtleSIgVVJJPSIjXzJhZWE3YTY1MWRiYjY4NzkwMmM0ODM0MzJlZDg1NzgwIi8+PC9kczpLZXlJbmZvPjx4ZW5jOkNpcGhlckRhdGEgeG1sbnM6eGVuYz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjIj48eGVuYzpDaXBoZXJWYWx1ZT5KQVJJWlJOMnFPRFhKZ2F2TW9xZXFmZng4QTZLaGcwa0xBT3JYSzQzdW1KVWhpTUI2MzM4dE9IbHNYYWlhdjRnSDZHUG9GbzM0Y1VrWEVkY2VEb2dpSXBKSGNwWnJ3YUVaZW9wM1Q3aHpoSGxRUnBISk9aVXQrWWRqVnliQy9JMmVuNmdGMjdwdVpkTFhIU2dvQkd0akprb1Q1dGp2Yko1WWs5WVdYaHp5eXNva3djNHczNmpjdlVRVEtBUE1nQXoraUJ5Tlc0ajBLcXdHU2RCMXpqeEZjOXhvYy9Ua0FxZlcwSjFxWGJlWG5scmh6SFdQNC9kck9YK0tLS1pxM3VtYnBOZkQ2RXo1cVRueWt4ajhwbnIyV2RFZVVDWldkOTZZY3MycWVXbXY5RUlncVdydzVud3JLaE5qNVJmSzA2NXdUZWc2MFdLWjU5MHdXRjlMek0xZ1dFL2l0U0hDZ2N2NmFPSjNhRVRHV1AvU2pESlFVQmFZY2ZMeFlFNU50clF2ak5HcHVIRlkvNVhHdEIzS1dqWElkWlVFMW91ZHRqNjY0T1podVEyUERTQ2ZmSC92aGJKZ1c2Z0szaXYxM3RZeUJTaUJ6bVVzdWVhSE9HdUZJeUtpaDdxUzh3dFRHTGRjWDZuWTJaRkJZSmZQZXZaMTUzb2JKdXVvM3FFNnVndkRRTUgwL0VwZlhZZTRpUFVtbHQ5OXcwWWdTNEt0dTZaMWFZVnpYZGdWY29maVlVQzhzdzJiczBSQUNiQzVMa1Y3a1ROMndYVStuTjBvQ1lLaUxML1oxWmswbGx0c1gvV1BjY09PR3pjWENMUkwyRmxvdjhyM3BOeVNMV1AweGFCUUdodUlZNmMvYVJvRDZZOXphdG9seWFzeTVHbEJSQVJOK3Ixdy9tWW9laEJ5WnVxdnVINmhDWGF5bnU1L2szR3Q4emdoc1FoNjVwVVYvbDRQa0prMThmNGRzbEdyMUh4eWRJVjQ2YldtVzFwTlNsQ01PeXhBcW5IUEhWd1BBTjJHN3ZpRGZQbkpZZEhXOXlZdkZTUTBhczM5OGtyNkNRWnlJMXRWWXJDZHNGdllLRURrcTI2MTNHWmxsMjRPaUpiYnVHV0VJUk52RDR3VzdsSmlmYk1pQnZkSDNNci9PUHNHYStmamNmaWM3MlpGSnU5TUxoZTBSSnQ2aTVsdGxPdXB5ZU4xVjE1QUR0TnRFZ01IdzRFS3ZkUktWUDVnZytmL3FLQVZLdkV0dlVJOTRSV21DYzZEQ29BVmNRSnJzYnkxblhMOEdxcFZ0V0Qyd0VSd1pWWUh6dVlyM0UrTUFJNXl6eEJOcFlwL3kwTEVjeEdIYkZiUFRacXhuWk9WL1FaL1laWDNKK2svZXdseVhpY2RpWFNXN25RMnB2N0Rna2RqYlQ5NmRXa1hwbDlHQ0JmZTVoVGJXTDZscksxcW9oODYzSHA5c2dOcS92RjArYUlMWFZQK3Z5OHRxZ0F0MDZSRFlKaFRZWWVOSHY1c3luTXRiV2hPREh3TEIrdUVHVWtNZW5mUm9zWDl5N21XSkxIWmdMNGNwektwT1A5WlBhbktYT1RvZVcyanM5TmEwdURDUGI0YkdHdkdGTHh6Mm05U05ETmtDTFZFaEI3aC81d0dabHdYbXpVanZhQlprYnhrbGFEb1dYVG5iQU9jT01Sa1RQRks1d0JYYVRaTkFsUkhpOWh5TWQ2WkJ3OVp2RklFczY4K2wxcEhRY0dKT0RYR1JtYUZNUVMrQlVRV1JxcnNoVXpTY0d1cVljdTVsc1FtRExTSFdnaitHRnEycGE0eGQwZGJRSVN0MDdETGo1RUtmRFUzOXNCRGo1bncvd2FUNzhuYmtXMDd6cE9PbFFEd0F5dG5aRWthdEJtMzUyQnBhaEg2SkxRQzFTNG5TWTlKbmhsS09EOHJLRE5NVTR6VFJOU3FxT0JTRmlwMlRJaU0ydXYvSlVPSkNpRUNYSkFaY05vcEhwZ2UvNE5icU5ycDlzYlBTM1c1T0ZpMjdWMktON0htVk9YY1JWY2FNenZsS1YrRDNIdVUxeGxPdnlGTWI4WFhtZGRyRFV1dDBHWEhTVmNpYys1TDBuUWc4UXVKMHBIVzM1MkVPMHJ2NnRTV3cwUUV6NzZJUzNhWnkwZmkxVC82ajQwTVptR1E1NFJPTDBrRlZwU285UWhTWjgrMnE4RnFGOW5DYWJkOVFzcUVSRFQ3VzhqaWo3QklMVjZ3azMra1lkMjZReGhObmlOOHJkYTZvRnpXb25Ddzl2dzUwUHRwWS8vTUpvd3FLWGFhRFNzY2R4K3FDRWVBWm0vNWxYZFdMb2tHaXRhRU56SEtXR05vYlpJTnVyM29lS25lYmVuMVY0amlvb3RiQjA5WkhHR1R6ZGNDSHZhUXZMWjUzSndTTFdYYXM4NXF0L0ZUVHpvM3NPYUlESFBJMWVQNUhrdENQMHFTL0pyeGZFbVhtdkRrQUt4L2x6cGhLY0tpU1cvWmdqMVh1OTViZURqTklIcVBVZzF5M0d3bTBWbzlJcjR5cGhtY1RoMXZtRTV1bHNlVXJ4SlJ6TEVscGZzR2MxeU5QNHhHY1NsL05HODd6SHdMTTFIRlN5MWdCN0FJTFY4VkpCb29XdHJXQ3pqL1Y5YVBBeU9ySTFYaUpOQmlNamk0SnpCc0VvR1pNQXREc0lyNk5kNlZ2ejlvYUlubUZuUXRjWHE2c0wyTE11NjcvUFlzSGhtN3E3UUFrbHQ5WXNOaGpKUWlzcENkbFdHRk9yc0lFdWpTbGJGS0gvUldyRUwzWWJOdUV0bTRqUWY3MnNQUEMyT0M3c0JqMDZoQjVVZlZUcDM5SlJtc3pmNGUzVE52NHo0SEUramIxVlNGQkQrM0tnaHpwQTNjREVnZEphQmE2ajFrbnJ6RjJUOUZudDJrTTBFSmwydUxuS0dCUnBSU2IrZ2FUWVh3NjlaaUtJa2kyY3Q3dS9sd1BpUXFRU2FjSzJ2M3FLNmtWNFUyb09Xd3BNTkI1WXpPeE1JZTBGeEJoZFl0dEJPT0FNM0dGOWlIaFJObWlhMG5GelVRcXc0b2RzTWVRaU84cnhWNDZzRGZ6bmRpVjc3SzNaaWR5anpCalRPMEpBK0R1NU9lZ1dTaUdTZTNzdUx3Rll0U1VKbTZIN3J0am1hbEhKNEMrTisvdEJmNHVVdlRXaFFkejJOUHpiblV2bmZ4c1YyazlFMTRZSkg1UjZTUndTY1czQ1MzV2EyYmZwSnUvSUg4cGZ4RTVzQmRzZlBmMjNYR2FBdVBocmt0TVBRUzdQaEJWeGkzUk5vb3kwd0NGaTZRMWFTT2FxREJyNS9Gc1cxNy9uT2NOL0tDYmt2NFdiZlZ1eGZBSGQrSXl4M1RTbTRqVFlPY0QvZXcxLzRPT2QxQjdVK2g5Qm5kSjIwRWZiQXNxZVpuRlNyWnpFMWJLRjFwK05VaDRaMnBUMlVOYXo0WEQ5MXA1Z3JPZUsvTkdnUEJvSjIxVis4NXpISmpncjNsYWg4NVdMNFV3YUhHL2tuOTAzZGdqMTR5clYzTis2OXFJeW04ckxtWFFCeHRJYmJaaXFJM0RTNXRJaWJkd3ZUQzU3UnNzaUJ0ZFRFWWVnbFI1SGpTOWQ1ZkZpWjhNeGxsMGhaUXJaS3NERHZUZjMrZjBSVzNDV3RLTFZDRDRtaUw5WUkybXl0SXM3Q3JNSDZ0anFVbWNoNjZ2eTJJRFhqUXltVGc3cXMvSGRwQ2J2U0FvdWt2cUJlam9FRVVwSG5scko3K3B6SmJiUm5jQXFpUDVQd1VzWGZNNXpxbjJueGRxT2RUV3VYOTFGc1FUU2JrcUdHMkNEWHRiUjFiK3JiSVZFVWNHdHgvK2c1eVNYNnVFcFdYV2ZnZ1lGQzUvMWJuTUdmZ0VJYUZnVkp3L20wYVg5b3I3dzQ1T1VqNGpnNHdENG5sd3VuR1ZJUE15YUR5d2dxeWZCOWw2S3AwMjZPa2ZldnlVMVNiQkIybTN0bkZtTDhCSEVuSVplbEhyT2luV1dqMXBTc2M1WkxySU9nRWhIMm1TY3Eya3BSOXdjbU9obkRTV3A2d2h1N0RjTTZnRkw3ZGJ3aU1hSGlTMzNYR05TQlVpRWVQYnJUTVl0L0l6TjVjSGpIM0JFZ2JwY3lRMERnNHEzaVhSTElkYUM0eS9nWHkyRU1iZEFYMFRlUzRoVmxscEtFcEJpMmNsVFgwZWJFWk9aTDFpMWhpRHh6aTJGcG5xNk1qd2hrYmhzT0ZPMGtGYzdYWWVndGZFeGFsUEFWRHR1VUNRb3BScjBtM2s1b3dYSksvNUtUUktRaW5sUm1LakttN1RzNGdsRUhycHpWYWhkV1JZSWtuU2hrV3cxMnNHODdlTFJqa1pZbkxuaDZLMkEvNzlXTkpMZXYwQXlpa1VjbjlKaFBRTS9oUjBxWWc3WXQ4ZmxrQWtaOGk5Snp2Z0pWRHkzRzMySWdtY2J1MjVRTWZaUzZJV0hUSmJ1Uk5ndHNGdm1ndjhtQUZ6UnV3bUdvaXVWUmtTVURRS09VRCtObDdUcm12b0NPWDhiZFgvOHNMZnV0bGZ4YU5DcXRVN2gxcEQ5R1JQTHVSUzUvdzZVSkc5Q0JOM3JMZS9Ha20zQTFDMElaY2E3eEYwZnAzQ3JpQlN2NzFpN2VkM3crcGt2cHVMSUZ3NHZsV2thRkplZTZuQkl4WEpVMmZFMmVkTWs4RXFwSXg5SHlaZk9IbVRVY0QzN2tvbGJPTXlUeU9pQ1RNYjFreFFCNHR6bFh2VG5GK3l2Z3JPUm5GZVVYMmUrNk1lQWY4cXc2TUUwQ3NQOTBqbDR4dHpIQUtxZFlLWjNaSFhyUkZmeEJyWXY4bEN2c0ZGWEtOY25hU25WWkJ2bFVQekI2NmZYMG9WWTIxWkxwMHRUMUE2NzF5aU1odktOTHRFckZXQm9COTFTTVI1SWFlMnlidDE2cTVCWWJod214K3ZxYUo5Zjk1QzVRNUZ2ZWt4bGg3Z2gva1ZlMDl2WXI1cStpVFNqdEo1YWpvcXRBTklJejF5dDAwR1o3RVViRnBvN0VSVGk4c0E4V2JTUWFCZnJjTHljRWw0SVdjVGRTdjhYMjVIcytWeGgvM3FDQVlMdDRjam5Pa1ltS21xamc4c2hLN1RSQmdDcnFWZi84RjdqN0ZjaW9PSTBHakZYZ2tHQWExRXl3dk9RM1k5VXhNTFAvOTBIOUVaSElBRGVZckhpbzAxM0FUdlhNaTY2bU9ETUQrWXRCb21sR2YwRFYxU0g3Qi9wR1hxYS9uTFFxQUE1cDFrZEFBa0t1KzQzdStzNnVhOGk2bHRnMnNDdVlnQ0JzYUFhSXQxWXJQOGp3MG8wZEUvYmo4TTF0Y2JXOFo4UFIwb0VFOGNuK004dnVoU3JTZ2l4ZXl6bHVJSWEzR21BcDRWZ0pldHBnNjllS2RpT1ZlVzFNVUNxdit1bit4MnNOWStUNzFjZTZPMzRqM3FmVFpiNEVrS2haTWk2aEdhOS84U3ptM3FZRklUcDcrb0VrN2tzenNrSTZDZDVGSWd4c0UzTVc1elVlUEVoc1hvVzM3V3ZBZ1lpQjlTRGkyNHB0blk3dFg5R3JKb2VURllJZUt5Vi9mNEZSdmlSMVpjR2tTTEU3MmJqSGs4Skw4eTA4NlJvMVB3K2x6cWd2T01xVmV1YXhEd0laQ3lDZGxlbUJEZnNKdkFBNmVXSkhUdG90VDJjNS9Wc20wbEdGU2FxaWtEOWRRcGJoYzlZaG1rUVpEU1g2SU9FODUzalZOUUxuSEVsb0JHVEpLZEVMR0lRQjVOQVM4dzZ1R3kwN0JzbS9sYllsRG1OS0dWRXdnVm42bUVYN0xQTGxmSlJLdTdzR1pJUW9TRS8zUTA0b0thZUJPaTNIMzRFZ3VnNG9qWEl1SmRSU3pNUWU5U2hNQUNiNDAxY1RzYllNS1Y3anFsWFQzL3pGWG1DRkNsNmZuZG9Wb3RYT2xKVUlWQ3phSnlmY3gxUnFjZnBpTWJYUGMvUWR3RzR0MlYwcGRMY1lOaHp3dDFqd0plZHBYU2dCNlhxVWNDbVFDMTB4SFFrYTRxMWJmRFg3VVl3U3dOZVUydzQ1UjB1MHBrdGFhMXZ4SzJDNnVkU0Z2SkZjTTZSbXluOVl3T1VNbzBRRDlaamdncGRzRVhTZ3lUWENCUGFjUHR0YTBtRjhvRjM3NlZYRUpzNEhkdVVBei9VY3pJcUxZU21lMy9SdXk4aitCY1gydVJUVk9WLzM0bWVLTG1OMHNrY1BMWFQ2VlFHTWtjTFQvK0sxUFFsc0t5TWtRYXAyWnZiTHJEVjQ5UzJkY28zK1l0KzZ5V0ljN3BQdyswUVR3S3A1NGdVdVU1OEVia3hxMytuQmNSOW50M212S0pPZmtQaEZqeVE0WVhzeFlRY3dpdUZMeDJjcHZhOFMybnNxdnRKNGxNdEFsQzJaU3ZUMzJEcEs2NWZiaWR3ZXIvYWVyVTFLUjFqdkZxS3JETzN1SjMwRStKQytoVlFqTWpEanhCY2FBZCtFT0pPSFVqck1wYjFCcmRxa0FaaHMxeUhDSGJOekVMang0V2xUSW81WGM2UkpjdkxZcHhzckh3QVA2dzJLSTg3N21rNFhjUm5CRnV2dkZqOW8wT24raGZuei9HbDZzODZYdXVqSy9tWUhJZEhuYzFYRmxEek5aL3hCTWNFLzVZYXpIUEFQck1yZW1wYTVISmQ3UHRtN21ZVm90VllyT2lPc1JoWHUzRDlVU01Cck1mT2t4L21oVU9jTERXODJUaEpzQXV3aUEvTWk1cU54RURRR3dGSy9lbDF3a2Mybk5tZUgzTTVzTlRDcFVLOHllS25RdlV2ZVFyNzBWYThrYi9RcGJDelNPSEM4RTc0T0wyVWljK05qRzNBbkt0U2pvZSt3ZjE3R0FBN09SdkV6TW42ZVg3bndpZHMvR1FtYVhDVDZiQWdpMTQ5cmVWbGNZdXZUSDZpWGVyWHNkYzZKOGY1aU1NMHlpN0lralpXb1NRbTcrTnlpcFZHRUluaz08L3hlbmM6Q2lwaGVyVmFsdWU+PC94ZW5jOkNpcGhlckRhdGE+PC94ZW5jOkVuY3J5cHRlZERhdGE+PHhlbmM6RW5jcnlwdGVkS2V5IHhtbG5zOnhlbmM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jIyIgSWQ9Il8yYWVhN2E2NTFkYmI2ODc5MDJjNDgzNDMyZWQ4NTc4MCI+PHhlbmM6RW5jcnlwdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI3JzYS1vYWVwLW1nZjFwIiB4bWxuczp4ZW5jPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyMiPjxkczpEaWdlc3RNZXRob2QgeG1sbnM6ZHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3NoYTEiLz48L3hlbmM6RW5jcnlwdGlvbk1ldGhvZD48ZHM6S2V5SW5mbyB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+PGRzOlg1MDlEYXRhPjxkczpYNTA5Q2VydGlmaWNhdGU+TUlJRXNEQ0NBcGlnQXdJQkFnSUJBREFOQmdrcWhraUc5dzBCQVFVRkFEQVRNUkV3RHdZRFZRUURFd2d4TUM0d0xqRXVOREFlRncweApOREExTURnd01EVTBNVGxhRncweE5EQTFNRGd3TURVME1UbGFNQk14RVRBUEJnTlZCQU1UQ0RFd0xqQXVNUzQwTUlJQ0lqQU5CZ2txCmhraUc5dzBCQVFFRkFBT0NBZzhBTUlJQ0NnS0NBZ0VBNWhxamFyb0pwQithUjhGTUU3aFE5bk1WMGg3TXBLdG1nRkxjSzN2d1A2N2YKZUFLK3hkdDE3aThSeVVoeGlsOUZDRlI1SzA4V2p3bzNOaUhacUhxRUtpdHcrSUpTbmRqTFNzb05nS0VJYWlGU3VnMmVWMW9ZRWx6MAo2REJYVHhjOGlxL0xhem5kcVRVb201MU9kZTl5STlBR2E4OGNETTVpT3FxOW1odUd1dnd1THRveVU3OExkK3MxRWE2TWdmN0w4TTdmClpWTzdOY3UrRmdJekk2R3QwMzVvaFlDTEJtT29NN28wdWo3RGNNRXZLT01Geml3RjQwd1lteXAzaENMbHEzcXdrTTlwVFZKbHR1ejAKQnQxdnFEZHJxM2tUaGVBOUpITWF5UnozSS9CWnhBVjNpUmQ0aHpMS1RrZWdEOFRvVEdVMTBHbWUrWkFyMXcvZXJjNWhWck0wL1hCbQpIUWxuSTVkMzFHVS9tZklrbTBYUFRHUlNwUHk3RStkVXZqOWRqdm0vVnFEZG9qZjN1dXdpckdlTE1SbE85UC9sQ2VyVGt0VzNnMjdTClY4Z24zRVRtMk1tN3JrTnFmMjRLSnBEdjB0S0Rvc2diZGFIcjJJRVlENFJwcXlTcDhrZDI1Qmh6dXNocUtSa1M4WHU1dDdIQWxWU0gKd2lGaHVMcXJyNGRVZmtCOGtaZU0veWNmWkxDbjdvTlVERmRnakdZU1ZNcGFrTDk3c0M5c2xBVzQvOFV0WFhaeExxY3lxL1l4ZHBDeQpzUFlQMWhzQXArVmdQQzdHSTZDeWlOb2pLUE9wdE1xTFpSWW5WaUt4bE9pV0JKQnpVQlJVVnVhYzhMWHJNaUR3OGJ0V0dhMUdoNXZUCmh1RlVLc3ZtUm9ldWs3ZXlYRU45SjdqNitmVFlqbnNDQXdFQUFhTVBNQTB3Q3dZRFZSMFBCQVFEQWdUd01BMEdDU3FHU0liM0RRRUIKQlFVQUE0SUNBUURBUEZVbzBQZ2E3dkI0SWp5OXUzcXBXTFFTQ2Q0eGZ3OGw5MmlxM0pxTFZYQng4R2Y5WFZ5Nkd6YlhXSWU2cG1RSQp6bW9tL0NXUXZoNm83a2MwRjR5NGZ0c1dmcW5xM2swK0hjWDRIZXUxUE4ySFNuVVVQTnNrNUFkZ2dkMzEyOU1ISWRmYUtiOGJTdUxKCnZTTWVNYXljcm1zYitnRUY2SkZQOGtHV09QNHhRWWpBVk9EY0ZiT3lBZkV1VkFodWp3MGJxbExIVCtFbDR2bGZCcnRkVStNWnp0SXEKUWtHRFc3Y21tODZaR2lCci9nYTk3QkNuT0NyZ1pYbHJnbWlhOFNtRjdSZmE0OC9Yemg3YkRtUGZnbUhnbEw1SnhocFJYMklvUFk1WApJdG1SOElhWUtoT0JPck8vcWVyY0o2WjNyUTZmSXJzOEhyWWdlVE8vdUU5d3c2V1JXQ1dEYUNSNG9XRFZ3akt6R2ZQMW9KdEFJUStVCk5qT2RkSDVvdERhd1hKUmxRV3ByNzJxVDcrV09LL3l6SmhOMHhtWERrbXQ1cHNNWS9DRG40a1hNTTB4YVJwZllPbjVTYzFvZWxRK2gKSW04L3JIVksvMGtyaENET25pcWorTC9uZTJTb3lvWFlnUzZvME5sa05odFRIbWpqb3NFN0hWNkpmQVVncTJEOWRxMDBKTzk2N0kzUAowVkY2RjhsY2RhcTB0b3ZKaEtXUmpycmlnWWQ3NTYvM2FUMkI4M0pXQTFSQTluaVhtclhmbkFQWnJOQ2ZXdmorWDJhbFVOL2l6ZElxClJ0czBVcFdUb1ZVcjBveldnbWJtcDVaT0dZT1FoK2xOY0FwM1Y5bE9yZGJaVlNJVE00N1JZR0JaYVpOREEyUGJEekMxM01TL0VqVEUKdlNrb253dGxDZz09PC9kczpYNTA5Q2VydGlmaWNhdGU+PC9kczpYNTA5RGF0YT48L2RzOktleUluZm8+PHhlbmM6Q2lwaGVyRGF0YSB4bWxuczp4ZW5jPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyMiPjx4ZW5jOkNpcGhlclZhbHVlPm4wR3IzSWtSSWRjaDZRVHhsTDJRWkNuUVBGM3R4T0hHbXpSLytObzExUlRSZjYrczBYUGxvK0RqYVd1RVdwMkh0MldkRm5UdXM5RmJwSGF0UVdiSVI1SVdUQXFrN3ZXSWlhTW0wV3prRnVPR1hrYi82ckV4cUdOYnY4UTlJUHVMZW85VVNOOWNrZTF5RFlFK2F2NngyU1ZzUjMydHJINXBCV1VyYkNCV0pGNWwyVXRHVlNwWEVwdUFWenppUVhDWTBycXltdmZSVmFGbGhWMVMyb2RXVW1LZU5YL1ZKNXJuME5adE5Zdlo1ZXFhWWJjNnpUQ2sxMEVkUjB6czV6UDFmQmxYMmtDVE8zdlZqNWhHKzV5TUdvRFR3OWxYWXBZUDNtUVpERHFlU2hDcnNKeitPNjNpU0oxZkdqdndhSWZ5QTB4ditON2MrR3I1Zlh1UzR0clozcU9GYkx5TStNS3NWVUNJNG1wTmZsUHRYNjEvSm1NRXFBZFNreFRadFZtY3NDbDZyNlVoSVlVM2ZEOS9JaGFWZDZtSFVvMVJ5WGVnOTlZNTVBMzl6VVVtNWViNzFsVC9qZjduV0p1ZWI1SWZIa1hMQ0ExVldBZGllV2hTbHNtKzBUd1ZKRE5TYTlocUYwb1ZLL0FLcTZEV202ZStVUmVyazF5NFJscDZQa2owb1hBclJ4dkJFTTJXT0VBQWJxdndyeldtSlIwZ2lnQTdVMHB4YnpGcmRoNzdzOVZuRkFQc1Q2aDZ6WmYrbWt1d1JIQUh4Zmk1dURzU1c3RWw3cVdmMjh1dnhKeXUxL0FjbVNHNDRxSk0xelZhSlQyVmU2bXZFYktOWVJ6K2FCZzVNSG1hcWdjQm82VTd3Uk16bHVPNGlSbjVJSVZ1eDlGRTY5eUVhbWliTFNVPTwveGVuYzpDaXBoZXJWYWx1ZT48L3hlbmM6Q2lwaGVyRGF0YT48eGVuYzpSZWZlcmVuY2VMaXN0Pjx4ZW5jOkRhdGFSZWZlcmVuY2UgVVJJPSIjXzllOGZkMmNlNGFkMDIxMmM5OGU3MDkxY2E4NzY1ZmI0Ii8+PC94ZW5jOlJlZmVyZW5jZUxpc3Q+PC94ZW5jOkVuY3J5cHRlZEtleT48L3NhbWwyOkVuY3J5cHRlZEFzc2VydGlvbj48L3NhbWwycDpSZXNwb25zZT4=", - RelayState: "", - }, - config: { - entryPoint: - "https://frontapp.oktapreview.com/app/frontdev584714_front_1/exk7xdi6axPfombzx0h7/sso/saml", - cert: - "MIIDoDCCAoigAwIBAgIGAVaZ04POMA0GCSqGSIb3DQEBBQUAMIGQMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEUMBIGA1UECwwLU1NPUHJvdmlkZXIxETAPBgNVBAMMCGZyb250YXBwMRwwGgYJKoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMB4XDTE2MDgxNzE4NDUzMVoXDTI2MDgxNzE4NDYzMVowgZAxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMQ0wCwYDVQQKDARPa3RhMRQwEgYDVQQLDAtTU09Qcm92aWRlcjERMA8GA1UEAwwIZnJvbnRhcHAxHDAaBgkqhkiG9w0BCQEWDWluZm9Ab2t0YS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvplQONVwknRy1iBnaoZtsOz28A7XW2tRpFW+0La7RJexbziIwEy1bPZENhfwjPZA1oHHZqi5l315BxXKWJqmmNmbDCFDo+/FYFCoHXliiLm9vqDbR1br6ByqeY0GfxyTPKHZxb2FSes30TffDknpMQd/8kA9YWaW5xDlu2ivWJI+sfcOJOMd6t+gcfXj58a5fP8Mwm6Y220KeZSvrVpEV2KDp9hln7fhhoxHZ7K/BYbidqdwLzeUQXpb6LIrxtKdug2FofS+ONs6yLIQRmrbCB7SVX1QA8JInMn+fzrGtZmFiHR0aFbyhiO78v/ufDa6S+XpYyp2b6D4SnzeggnobAgMBAAEwDQYJKoZIhvcNAQEFBQADggEBAJ2wcFVffFHSd9pj6RgoNHXZBsWp0HUZrNekiSbgomr4tSDefWtKb04nFIlRytfVs/k74wmbNiRCE8nDVBrBDFA/+Tv/3PowZXHjXKBofUuScTP4/Tw1N/ywf7V+XY5kV3VmLBL6ax+ULJauR/YGIIMsIc/rS2D04aAcScU9pqVh2ML7nTH7gFqYrxypavmVk6K94vLjs0ggF2TGp7tXCRjeOlPPJS+MOJHJhTBWYFWvBLclU3zcri3ws7GqJMpeiHa7rMoHV0onxWsZTZW57ybaIWKLt1goAooC7hq0rx7oNlOvrys5lllhBySYYC3ycqca/D0+GxXLcEr9QwP7TVw=", - decryptionPvk: fs.readFileSync(__dirname + "/static/testshib encryption pvk.pem"), - }, - expectedStatusCode: 200, - expectedNameIDStartsWith: "xavier", - mockDate: "2016-08-19T01:15:32.681Z", - }, - { - name: "Onelogin -- invalid cert (from Okta case) should fail", - samlResponse: { - SAMLResponse: - "PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0\r\nYzpTQU1MOjIuMDphc3NlcnRpb24iIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6\r\nbmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIElEPSJSNjg5YjA3MzNiY2Nj\r\nYTIyYTEzN2UzNjU0ODMwMzEyMzMyOTQwYjFiZSIgVmVyc2lvbj0iMi4wIiBJ\r\nc3N1ZUluc3RhbnQ9IjIwMTQtMDUtMjhUMDA6MTY6MDhaIiBEZXN0aW5hdGlv\r\nbj0ie3JlY2lwaWVudH0iIEluUmVzcG9uc2VUbz0iX2E2ZmM0NmJlODRlMWUz\r\nY2YzYzUwIj48c2FtbDpJc3N1ZXI+aHR0cHM6Ly9hcHAub25lbG9naW4uY29t\r\nL3NhbWwvbWV0YWRhdGEvMzcxNzU1PC9zYW1sOklzc3Vlcj48c2FtbHA6U3Rh\r\ndHVzPjxzYW1scDpTdGF0dXNDb2RlIFZhbHVlPSJ1cm46b2FzaXM6bmFtZXM6\r\ndGM6U0FNTDoyLjA6c3RhdHVzOlN1Y2Nlc3MiLz48L3NhbWxwOlN0YXR1cz48\r\nc2FtbDpBc3NlcnRpb24geG1sbnM6eHM9Imh0dHA6Ly93d3cudzMub3JnLzIw\r\nMDEvWE1MU2NoZW1hIiB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIw\r\nMDEvWE1MU2NoZW1hLWluc3RhbmNlIiBWZXJzaW9uPSIyLjAiIElEPSJwZngz\r\nYjYzYzdiZS1mZTg2LTYyZmQtOGNiNS0xNmFiNjI3M2VmYWEiIElzc3VlSW5z\r\ndGFudD0iMjAxNC0wNS0yOFQwMDoxNjowOFoiPjxzYW1sOklzc3Vlcj5odHRw\r\nczovL2FwcC5vbmVsb2dpbi5jb20vc2FtbC9tZXRhZGF0YS8zNzE3NTU8L3Nh\r\nbWw6SXNzdWVyPjxkczpTaWduYXR1cmUgeG1sbnM6ZHM9Imh0dHA6Ly93d3cu\r\ndzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPjxkczpTaWduZWRJbmZvPjxkczpD\r\nYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53\r\nMy5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+PGRzOlNpZ25hdHVyZU1l\r\ndGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1s\r\nZHNpZyNyc2Etc2hhMSIvPjxkczpSZWZlcmVuY2UgVVJJPSIjcGZ4M2I2M2M3\r\nYmUtZmU4Ni02MmZkLThjYjUtMTZhYjYyNzNlZmFhIj48ZHM6VHJhbnNmb3Jt\r\ncz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcv\r\nMjAwMC8wOS94bWxkc2lnI2VudmVsb3BlZC1zaWduYXR1cmUiLz48ZHM6VHJh\r\nbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94\r\nbWwtZXhjLWMxNG4jIi8+PC9kczpUcmFuc2Zvcm1zPjxkczpEaWdlc3RNZXRo\r\nb2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRz\r\naWcjc2hhMSIvPjxkczpEaWdlc3RWYWx1ZT5EQ25QVFFZQmIxaEtzcGJlNmZn\r\nMVUzcTh4bjQ9PC9kczpEaWdlc3RWYWx1ZT48L2RzOlJlZmVyZW5jZT48L2Rz\r\nOlNpZ25lZEluZm8+PGRzOlNpZ25hdHVyZVZhbHVlPmUwK2FGb21BMCtKQVkw\r\nZjl0S3F6SXVxSVZTU3c3TGlGVXNuZUVES1BCV2RpVHoxc01kZ3IvMnkxZTkr\r\ncmphUzJtUm1DaS92U1FMWTN6VFl6MGhwNm5KTlUxOStUV29YbzlrSFF5V1Q0\r\nS2tlUUw0WHMvZ1ovQW9LQzIwaUhWS3RwUHBzMElRME1sL3FSb291U2l0dDZT\r\nZi9XRHoyTFYvcFdjSDJoeDV0djN4U3czNmhLMk5RYzdxdzdyMW1FWG52Y2pY\r\nUmVZbzhyclZmN1hIR0d4Tm9SSUVJQ1VJaTExMHV2c1dlbVNYZjBaMGR5YjBG\r\nVllPV3VTc1FNRGx6TnBoZUFEQmlmRk80VVRmU0VoRlp2bjhrVkNHWlVJd3Ji\r\nT2haMmQvK1lFdGd5dVRnK3F0c2xnZnk0ZHdkNFR2RWNmdVJ6UVRhemVlZnBy\r\nU0Z5aVFja0FYT2pjdz09PC9kczpTaWduYXR1cmVWYWx1ZT48ZHM6S2V5SW5m\r\nbz48ZHM6WDUwOURhdGE+PGRzOlg1MDlDZXJ0aWZpY2F0ZT5NSUlFRnpDQ0F2\r\nK2dBd0lCQWdJVUZKc1VqUE03QW1Xdk50RXZVTFNIbFRUTWlMUXdEUVlKS29a\r\nSWh2Y05BUUVGQlFBd1dERUxNQWtHQTFVRUJoTUNWVk14RVRBUEJnTlZCQW9N\r\nQ0ZOMVluTndZV05sTVJVd0V3WURWUVFMREF4UGJtVk1iMmRwYmlCSlpGQXhI\r\nekFkQmdOVkJBTU1Gazl1WlV4dloybHVJRUZqWTI5MWJuUWdOREl6TkRrd0ho\r\nY05NVFF3TlRFek1UZ3dOakV5V2hjTk1Ua3dOVEUwTVRnd05qRXlXakJZTVFz\r\nd0NRWURWUVFHRXdKVlV6RVJNQThHQTFVRUNnd0lVM1ZpYzNCaFkyVXhGVEFU\r\nQmdOVkJBc01ERTl1WlV4dloybHVJRWxrVURFZk1CMEdBMVVFQXd3V1QyNWxU\r\nRzluYVc0Z1FXTmpiM1Z1ZENBME1qTTBPVENDQVNJd0RRWUpLb1pJaHZjTkFR\r\nRUJCUUFEZ2dFUEFEQ0NBUW9DZ2dFQkFLckF6SmRZOUZ6Rkx0NWJsQXJKZlB6\r\nZ2k4N0VuRkdsVGZjVjVUMVRVRHdMQmxEa1kvMFpHS25NT3BmM0Q3aWUyQzRw\r\nUEZPSW1Pb2djTTVrcERETDdxeFRYWjFld1hWeWpCZE11MjlORzJDNk56V2VR\r\nVFVNVWppMDFFY0hrQzhvK1B0czhBTmlOT1ljanhFZXloRXl6SktnRWl6YmxZ\r\nek1NS3pkck9FVDZRdXFXbzNDODNLKzUrNWRzakRuMW9vS0dSd2ozSHZnc1lj\r\nRnJRbDlOb2pnUUZqb29id3NpRS83QStPSmhMcEJjeS9uU1Znbm9KYU1mck8r\r\nSnNudWtaUHp0Ym50THZPbDU2K1ZyYTBOOG41TkFZaGFTYXlQaXYvYXloalZn\r\namZYZDF0ak1WVE9pRGtuVU93aXpadUoxWTNRSDk0dlV0QmdwMFdCcEJTcy94\r\nTXlUczhDQXdFQUFhT0IyRENCMVRBTUJnTlZIUk1CQWY4RUFqQUFNQjBHQTFV\r\nZERnUVdCQlJRTzRXcE01Zld3eGliNDlXVHVKa2ZZRGJ4T0RDQmxRWURWUjBq\r\nQklHTk1JR0tnQlJRTzRXcE01Zld3eGliNDlXVHVKa2ZZRGJ4T0tGY3BGb3dX\r\nREVMTUFrR0ExVUVCaE1DVlZNeEVUQVBCZ05WQkFvTUNGTjFZbk53WVdObE1S\r\nVXdFd1lEVlFRTERBeFBibVZNYjJkcGJpQkpaRkF4SHpBZEJnTlZCQU1NRms5\r\ndVpVeHZaMmx1SUVGalkyOTFiblFnTkRJek5EbUNGQlNiRkl6ek93SmxyemJS\r\nTDFDMGg1VTB6SWkwTUE0R0ExVWREd0VCL3dRRUF3SUhnREFOQmdrcWhraUc5\r\ndzBCQVFVRkFBT0NBUUVBQ2REQUFvYVpGQ0VZNXBtZndiS3VLclh0TzVpRThs\r\nV3RpQ1BqQ1pFVXVUNmJYUk5jcXJkbnVWL0VBZlg5V1FvWGphbFBpMGVNNzh6\r\nS21idlJHU1RVSHdXdzQ5UkhqRmZlSlVLdkhOZU5uRmdUWERqRVBOaE12aDY5\r\na0htNDUzbEZSbUIra2s2eWp0WFJaYVFFd1M4VXVvMk90K2tyZ05ibDZvVEJa\r\nSjBBSEgxTXRaRUNEbG9tczFLbTd6c0s4d0FpNWk4VFZJS2tWcjViMlZsaHJM\r\nZ0ZNdnpaNVZpQXhJTUdCNnc0N3lZNFFHUUIvNVE4eWE5aEJzOXZrbit3dWJB\r\nK3lyNGoxNEpYWjdibFZLRFNUWXZhNjVFYStQcUh5cnArV25tbmJ3Mk9iUzdp\r\nV2V4aVR5MWpEM0cwUjJhdkRCRmpNOEZqNURiZnVmc0UxYjBVMTBSVHRnPT08\r\nL2RzOlg1MDlDZXJ0aWZpY2F0ZT48L2RzOlg1MDlEYXRhPjwvZHM6S2V5SW5m\r\nbz48L2RzOlNpZ25hdHVyZT48c2FtbDpTdWJqZWN0PjxzYW1sOk5hbWVJRCBG\r\nb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpuYW1laWQtZm9y\r\nbWF0OnRyYW5zaWVudCI+cGxvZXJAc3Vic3BhY2Vzdy5jb208L3NhbWw6TmFt\r\nZUlEPjxzYW1sOlN1YmplY3RDb25maXJtYXRpb24gTWV0aG9kPSJ1cm46b2Fz\r\naXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyIj48c2FtbDpTdWJqZWN0\r\nQ29uZmlybWF0aW9uRGF0YSBOb3RPbk9yQWZ0ZXI9IjIwMTQtMDUtMjhUMDA6\r\nMTk6MDhaIiBSZWNpcGllbnQ9IntyZWNpcGllbnR9IiBJblJlc3BvbnNlVG89\r\nIl9hNmZjNDZiZTg0ZTFlM2NmM2M1MCIvPjwvc2FtbDpTdWJqZWN0Q29uZmly\r\nbWF0aW9uPjwvc2FtbDpTdWJqZWN0PjxzYW1sOkNvbmRpdGlvbnMgTm90QmVm\r\nb3JlPSIyMDE0LTA1LTI4VDAwOjEzOjA4WiIgTm90T25PckFmdGVyPSIyMDE0\r\nLTA1LTI4VDAwOjE5OjA4WiI+PHNhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj48\r\nc2FtbDpBdWRpZW5jZT57YXVkaWVuY2V9PC9zYW1sOkF1ZGllbmNlPjwvc2Ft\r\nbDpBdWRpZW5jZVJlc3RyaWN0aW9uPjwvc2FtbDpDb25kaXRpb25zPjxzYW1s\r\nOkF1dGhuU3RhdGVtZW50IEF1dGhuSW5zdGFudD0iMjAxNC0wNS0yOFQwMDox\r\nNjowN1oiIFNlc3Npb25Ob3RPbk9yQWZ0ZXI9IjIwMTQtMDUtMjlUMDA6MTY6\r\nMDhaIiBTZXNzaW9uSW5kZXg9Il8zMGE0YWY1MC1jODJiLTAxMzEtZjhiNS03\r\nODJiY2I1NmZjYWEiPjxzYW1sOkF1dGhuQ29udGV4dD48c2FtbDpBdXRobkNv\r\nbnRleHRDbGFzc1JlZj51cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YWM6\r\nY2xhc3NlczpQYXNzd29yZFByb3RlY3RlZFRyYW5zcG9ydDwvc2FtbDpBdXRo\r\nbkNvbnRleHRDbGFzc1JlZj48L3NhbWw6QXV0aG5Db250ZXh0Pjwvc2FtbDpB\r\ndXRoblN0YXRlbWVudD48L3NhbWw6QXNzZXJ0aW9uPjwvc2FtbHA6UmVzcG9u\r\nc2U+Cgo=\r\n", - }, - config: { - entryPoint: - "https://subspacesw1.okta.com/app/subspacesw_subspacetest_1/kvjj46lsDQEQYUDBZIYW/sso/saml", - cert: - "MIICoTCCAgqgAwIBAgIGAUY8zVPYMA0GCSqGSIb3DQEBBQUAMIGTMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEUMBIGA1UECwwLU1NPUHJvdmlkZXIxFDASBgNVBAMMC3N1YnNwYWNlc3cxMRwwGgYJKoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMB4XDTE0MDUyNzA4MjkyN1oXDTQ0MDUyNzA4MzAyN1owgZMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMQ0wCwYDVQQKDARPa3RhMRQwEgYDVQQLDAtTU09Qcm92aWRlcjEUMBIGA1UEAwwLc3Vic3BhY2VzdzExHDAaBgkqhkiG9w0BCQEWDWluZm9Ab2t0YS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKYX/UdV9HLXbNKv0yc6YJBtzdWNuGTPIXWVPCb0OsRvR54lq+0SLPQGJS39flWKiqtUiEusuih6GdLfNvFAVLGPqBTWQ4Lc7r5+zD5qnH6LiTM1KCbaF3uu2P+T+utsGqFUZawA9FEVNLkICe36X1yE40X+/jMygyieQPMeJABvAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAHSWPG4y1bNyJySgbFqDcS/6MUNIrSfkQYIxzIGQ37qy5qAW15YkBUiW9TsGJRcrgusVrZIia18eYz84Zn7FyUZNMFo24L7l6BS0WCitIQScBCKB4QWA/b1szqs7EtpOcPP01TYNq2ras+VqR3aWAXLX2oKfClV/SUwszRf5U85Y=", - }, - expectedStatusCode: 500, - mockDate: "2014-05-28T00:16:08Z", - }, - { - name: "Onelogin -- valid config should succeed", - samlResponse: { - SAMLResponse: - "PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0\r\nYzpTQU1MOjIuMDphc3NlcnRpb24iIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6\r\nbmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIElEPSJSNjg5YjA3MzNiY2Nj\r\nYTIyYTEzN2UzNjU0ODMwMzEyMzMyOTQwYjFiZSIgVmVyc2lvbj0iMi4wIiBJ\r\nc3N1ZUluc3RhbnQ9IjIwMTQtMDUtMjhUMDA6MTY6MDhaIiBEZXN0aW5hdGlv\r\nbj0ie3JlY2lwaWVudH0iIEluUmVzcG9uc2VUbz0iX2E2ZmM0NmJlODRlMWUz\r\nY2YzYzUwIj48c2FtbDpJc3N1ZXI+aHR0cHM6Ly9hcHAub25lbG9naW4uY29t\r\nL3NhbWwvbWV0YWRhdGEvMzcxNzU1PC9zYW1sOklzc3Vlcj48c2FtbHA6U3Rh\r\ndHVzPjxzYW1scDpTdGF0dXNDb2RlIFZhbHVlPSJ1cm46b2FzaXM6bmFtZXM6\r\ndGM6U0FNTDoyLjA6c3RhdHVzOlN1Y2Nlc3MiLz48L3NhbWxwOlN0YXR1cz48\r\nc2FtbDpBc3NlcnRpb24geG1sbnM6eHM9Imh0dHA6Ly93d3cudzMub3JnLzIw\r\nMDEvWE1MU2NoZW1hIiB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIw\r\nMDEvWE1MU2NoZW1hLWluc3RhbmNlIiBWZXJzaW9uPSIyLjAiIElEPSJwZngz\r\nYjYzYzdiZS1mZTg2LTYyZmQtOGNiNS0xNmFiNjI3M2VmYWEiIElzc3VlSW5z\r\ndGFudD0iMjAxNC0wNS0yOFQwMDoxNjowOFoiPjxzYW1sOklzc3Vlcj5odHRw\r\nczovL2FwcC5vbmVsb2dpbi5jb20vc2FtbC9tZXRhZGF0YS8zNzE3NTU8L3Nh\r\nbWw6SXNzdWVyPjxkczpTaWduYXR1cmUgeG1sbnM6ZHM9Imh0dHA6Ly93d3cu\r\ndzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPjxkczpTaWduZWRJbmZvPjxkczpD\r\nYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53\r\nMy5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+PGRzOlNpZ25hdHVyZU1l\r\ndGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1s\r\nZHNpZyNyc2Etc2hhMSIvPjxkczpSZWZlcmVuY2UgVVJJPSIjcGZ4M2I2M2M3\r\nYmUtZmU4Ni02MmZkLThjYjUtMTZhYjYyNzNlZmFhIj48ZHM6VHJhbnNmb3Jt\r\ncz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcv\r\nMjAwMC8wOS94bWxkc2lnI2VudmVsb3BlZC1zaWduYXR1cmUiLz48ZHM6VHJh\r\nbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94\r\nbWwtZXhjLWMxNG4jIi8+PC9kczpUcmFuc2Zvcm1zPjxkczpEaWdlc3RNZXRo\r\nb2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRz\r\naWcjc2hhMSIvPjxkczpEaWdlc3RWYWx1ZT5EQ25QVFFZQmIxaEtzcGJlNmZn\r\nMVUzcTh4bjQ9PC9kczpEaWdlc3RWYWx1ZT48L2RzOlJlZmVyZW5jZT48L2Rz\r\nOlNpZ25lZEluZm8+PGRzOlNpZ25hdHVyZVZhbHVlPmUwK2FGb21BMCtKQVkw\r\nZjl0S3F6SXVxSVZTU3c3TGlGVXNuZUVES1BCV2RpVHoxc01kZ3IvMnkxZTkr\r\ncmphUzJtUm1DaS92U1FMWTN6VFl6MGhwNm5KTlUxOStUV29YbzlrSFF5V1Q0\r\nS2tlUUw0WHMvZ1ovQW9LQzIwaUhWS3RwUHBzMElRME1sL3FSb291U2l0dDZT\r\nZi9XRHoyTFYvcFdjSDJoeDV0djN4U3czNmhLMk5RYzdxdzdyMW1FWG52Y2pY\r\nUmVZbzhyclZmN1hIR0d4Tm9SSUVJQ1VJaTExMHV2c1dlbVNYZjBaMGR5YjBG\r\nVllPV3VTc1FNRGx6TnBoZUFEQmlmRk80VVRmU0VoRlp2bjhrVkNHWlVJd3Ji\r\nT2haMmQvK1lFdGd5dVRnK3F0c2xnZnk0ZHdkNFR2RWNmdVJ6UVRhemVlZnBy\r\nU0Z5aVFja0FYT2pjdz09PC9kczpTaWduYXR1cmVWYWx1ZT48ZHM6S2V5SW5m\r\nbz48ZHM6WDUwOURhdGE+PGRzOlg1MDlDZXJ0aWZpY2F0ZT5NSUlFRnpDQ0F2\r\nK2dBd0lCQWdJVUZKc1VqUE03QW1Xdk50RXZVTFNIbFRUTWlMUXdEUVlKS29a\r\nSWh2Y05BUUVGQlFBd1dERUxNQWtHQTFVRUJoTUNWVk14RVRBUEJnTlZCQW9N\r\nQ0ZOMVluTndZV05sTVJVd0V3WURWUVFMREF4UGJtVk1iMmRwYmlCSlpGQXhI\r\nekFkQmdOVkJBTU1Gazl1WlV4dloybHVJRUZqWTI5MWJuUWdOREl6TkRrd0ho\r\nY05NVFF3TlRFek1UZ3dOakV5V2hjTk1Ua3dOVEUwTVRnd05qRXlXakJZTVFz\r\nd0NRWURWUVFHRXdKVlV6RVJNQThHQTFVRUNnd0lVM1ZpYzNCaFkyVXhGVEFU\r\nQmdOVkJBc01ERTl1WlV4dloybHVJRWxrVURFZk1CMEdBMVVFQXd3V1QyNWxU\r\nRzluYVc0Z1FXTmpiM1Z1ZENBME1qTTBPVENDQVNJd0RRWUpLb1pJaHZjTkFR\r\nRUJCUUFEZ2dFUEFEQ0NBUW9DZ2dFQkFLckF6SmRZOUZ6Rkx0NWJsQXJKZlB6\r\nZ2k4N0VuRkdsVGZjVjVUMVRVRHdMQmxEa1kvMFpHS25NT3BmM0Q3aWUyQzRw\r\nUEZPSW1Pb2djTTVrcERETDdxeFRYWjFld1hWeWpCZE11MjlORzJDNk56V2VR\r\nVFVNVWppMDFFY0hrQzhvK1B0czhBTmlOT1ljanhFZXloRXl6SktnRWl6YmxZ\r\nek1NS3pkck9FVDZRdXFXbzNDODNLKzUrNWRzakRuMW9vS0dSd2ozSHZnc1lj\r\nRnJRbDlOb2pnUUZqb29id3NpRS83QStPSmhMcEJjeS9uU1Znbm9KYU1mck8r\r\nSnNudWtaUHp0Ym50THZPbDU2K1ZyYTBOOG41TkFZaGFTYXlQaXYvYXloalZn\r\namZYZDF0ak1WVE9pRGtuVU93aXpadUoxWTNRSDk0dlV0QmdwMFdCcEJTcy94\r\nTXlUczhDQXdFQUFhT0IyRENCMVRBTUJnTlZIUk1CQWY4RUFqQUFNQjBHQTFV\r\nZERnUVdCQlJRTzRXcE01Zld3eGliNDlXVHVKa2ZZRGJ4T0RDQmxRWURWUjBq\r\nQklHTk1JR0tnQlJRTzRXcE01Zld3eGliNDlXVHVKa2ZZRGJ4T0tGY3BGb3dX\r\nREVMTUFrR0ExVUVCaE1DVlZNeEVUQVBCZ05WQkFvTUNGTjFZbk53WVdObE1S\r\nVXdFd1lEVlFRTERBeFBibVZNYjJkcGJpQkpaRkF4SHpBZEJnTlZCQU1NRms5\r\ndVpVeHZaMmx1SUVGalkyOTFiblFnTkRJek5EbUNGQlNiRkl6ek93SmxyemJS\r\nTDFDMGg1VTB6SWkwTUE0R0ExVWREd0VCL3dRRUF3SUhnREFOQmdrcWhraUc5\r\ndzBCQVFVRkFBT0NBUUVBQ2REQUFvYVpGQ0VZNXBtZndiS3VLclh0TzVpRThs\r\nV3RpQ1BqQ1pFVXVUNmJYUk5jcXJkbnVWL0VBZlg5V1FvWGphbFBpMGVNNzh6\r\nS21idlJHU1RVSHdXdzQ5UkhqRmZlSlVLdkhOZU5uRmdUWERqRVBOaE12aDY5\r\na0htNDUzbEZSbUIra2s2eWp0WFJaYVFFd1M4VXVvMk90K2tyZ05ibDZvVEJa\r\nSjBBSEgxTXRaRUNEbG9tczFLbTd6c0s4d0FpNWk4VFZJS2tWcjViMlZsaHJM\r\nZ0ZNdnpaNVZpQXhJTUdCNnc0N3lZNFFHUUIvNVE4eWE5aEJzOXZrbit3dWJB\r\nK3lyNGoxNEpYWjdibFZLRFNUWXZhNjVFYStQcUh5cnArV25tbmJ3Mk9iUzdp\r\nV2V4aVR5MWpEM0cwUjJhdkRCRmpNOEZqNURiZnVmc0UxYjBVMTBSVHRnPT08\r\nL2RzOlg1MDlDZXJ0aWZpY2F0ZT48L2RzOlg1MDlEYXRhPjwvZHM6S2V5SW5m\r\nbz48L2RzOlNpZ25hdHVyZT48c2FtbDpTdWJqZWN0PjxzYW1sOk5hbWVJRCBG\r\nb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpuYW1laWQtZm9y\r\nbWF0OnRyYW5zaWVudCI+cGxvZXJAc3Vic3BhY2Vzdy5jb208L3NhbWw6TmFt\r\nZUlEPjxzYW1sOlN1YmplY3RDb25maXJtYXRpb24gTWV0aG9kPSJ1cm46b2Fz\r\naXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyIj48c2FtbDpTdWJqZWN0\r\nQ29uZmlybWF0aW9uRGF0YSBOb3RPbk9yQWZ0ZXI9IjIwMTQtMDUtMjhUMDA6\r\nMTk6MDhaIiBSZWNpcGllbnQ9IntyZWNpcGllbnR9IiBJblJlc3BvbnNlVG89\r\nIl9hNmZjNDZiZTg0ZTFlM2NmM2M1MCIvPjwvc2FtbDpTdWJqZWN0Q29uZmly\r\nbWF0aW9uPjwvc2FtbDpTdWJqZWN0PjxzYW1sOkNvbmRpdGlvbnMgTm90QmVm\r\nb3JlPSIyMDE0LTA1LTI4VDAwOjEzOjA4WiIgTm90T25PckFmdGVyPSIyMDE0\r\nLTA1LTI4VDAwOjE5OjA4WiI+PHNhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj48\r\nc2FtbDpBdWRpZW5jZT57YXVkaWVuY2V9PC9zYW1sOkF1ZGllbmNlPjwvc2Ft\r\nbDpBdWRpZW5jZVJlc3RyaWN0aW9uPjwvc2FtbDpDb25kaXRpb25zPjxzYW1s\r\nOkF1dGhuU3RhdGVtZW50IEF1dGhuSW5zdGFudD0iMjAxNC0wNS0yOFQwMDox\r\nNjowN1oiIFNlc3Npb25Ob3RPbk9yQWZ0ZXI9IjIwMTQtMDUtMjlUMDA6MTY6\r\nMDhaIiBTZXNzaW9uSW5kZXg9Il8zMGE0YWY1MC1jODJiLTAxMzEtZjhiNS03\r\nODJiY2I1NmZjYWEiPjxzYW1sOkF1dGhuQ29udGV4dD48c2FtbDpBdXRobkNv\r\nbnRleHRDbGFzc1JlZj51cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YWM6\r\nY2xhc3NlczpQYXNzd29yZFByb3RlY3RlZFRyYW5zcG9ydDwvc2FtbDpBdXRo\r\nbkNvbnRleHRDbGFzc1JlZj48L3NhbWw6QXV0aG5Db250ZXh0Pjwvc2FtbDpB\r\ndXRoblN0YXRlbWVudD48L3NhbWw6QXNzZXJ0aW9uPjwvc2FtbHA6UmVzcG9u\r\nc2U+Cgo=\r\n", - }, - config: { - entryPoint: "https://app.onelogin.com/trust/saml2/http-post/sso/371755", - cert: TEST_CERT, - }, - expectedStatusCode: 200, - expectedNameIDStartsWith: "ploer", - mockDate: "2014-05-28T00:16:08Z", - }, - { - name: "Testshib -- valid encrypted response should succeed", - samlResponse: { - SAMLResponse: - "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c2FtbDJwOlJlc3BvbnNlIHhtbG5zOnNhbWwycD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sIiBEZXN0aW5hdGlvbj0iaHR0cDovL2xvY2FsaG9zdC9icm93c2VyU2FtbExvZ2luIiBJRD0iXzdmOWU5NWM3MTE2NTRhYTQxYjMyNmY4Yjg0N2Y3YTEzIiBJblJlc3BvbnNlVG89Il8zMTM4ZDY3NWQ2ZWQ0MTZkNDNkNiIgSXNzdWVJbnN0YW50PSIyMDE0LTA2LTAyVDE3OjQ4OjU2LjgyMFoiIFZlcnNpb249IjIuMCI+PHNhbWwyOklzc3VlciB4bWxuczpzYW1sMj0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6bmFtZWlkLWZvcm1hdDplbnRpdHkiPmh0dHBzOi8vaWRwLnRlc3RzaGliLm9yZy9pZHAvc2hpYmJvbGV0aDwvc2FtbDI6SXNzdWVyPjxzYW1sMnA6U3RhdHVzPjxzYW1sMnA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+PC9zYW1sMnA6U3RhdHVzPjxzYW1sMjpFbmNyeXB0ZWRBc3NlcnRpb24geG1sbnM6c2FtbDI9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iPjx4ZW5jOkVuY3J5cHRlZERhdGEgeG1sbnM6eGVuYz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjIiBJZD0iX2ZiYjE0NzQzNTEwYTYyOTJmODc1MGVmZGUzOWJhNzI2IiBUeXBlPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNFbGVtZW50Ij48eGVuYzpFbmNyeXB0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjYWVzMTI4LWNiYyIgeG1sbnM6eGVuYz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjIi8+PGRzOktleUluZm8geG1sbnM6ZHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPjx4ZW5jOkVuY3J5cHRlZEtleSBJZD0iXzQ0YTEwOTk0MDVkYzgxMDJlYTFmZmM3Zjk0YzJhZjkyIiB4bWxuczp4ZW5jPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyMiPjx4ZW5jOkVuY3J5cHRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNyc2Etb2FlcC1tZ2YxcCIgeG1sbnM6eGVuYz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjIj48ZHM6RGlnZXN0TWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3NoYTEiIHhtbG5zOmRzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIi8+PC94ZW5jOkVuY3J5cHRpb25NZXRob2Q+PGRzOktleUluZm8+PGRzOlg1MDlEYXRhPjxkczpYNTA5Q2VydGlmaWNhdGU+TUlJRXNEQ0NBcGlnQXdJQkFnSUJBREFOQmdrcWhraUc5dzBCQVFVRkFEQVRNUkV3RHdZRFZRUURFd2d4TUM0d0xqRXVOREFlRncweApOREExTURnd01EVTBNVGxhRncweE5EQTFNRGd3TURVME1UbGFNQk14RVRBUEJnTlZCQU1UQ0RFd0xqQXVNUzQwTUlJQ0lqQU5CZ2txCmhraUc5dzBCQVFFRkFBT0NBZzhBTUlJQ0NnS0NBZ0VBNWhxamFyb0pwQithUjhGTUU3aFE5bk1WMGg3TXBLdG1nRkxjSzN2d1A2N2YKZUFLK3hkdDE3aThSeVVoeGlsOUZDRlI1SzA4V2p3bzNOaUhacUhxRUtpdHcrSUpTbmRqTFNzb05nS0VJYWlGU3VnMmVWMW9ZRWx6MAo2REJYVHhjOGlxL0xhem5kcVRVb201MU9kZTl5STlBR2E4OGNETTVpT3FxOW1odUd1dnd1THRveVU3OExkK3MxRWE2TWdmN0w4TTdmClpWTzdOY3UrRmdJekk2R3QwMzVvaFlDTEJtT29NN28wdWo3RGNNRXZLT01Geml3RjQwd1lteXAzaENMbHEzcXdrTTlwVFZKbHR1ejAKQnQxdnFEZHJxM2tUaGVBOUpITWF5UnozSS9CWnhBVjNpUmQ0aHpMS1RrZWdEOFRvVEdVMTBHbWUrWkFyMXcvZXJjNWhWck0wL1hCbQpIUWxuSTVkMzFHVS9tZklrbTBYUFRHUlNwUHk3RStkVXZqOWRqdm0vVnFEZG9qZjN1dXdpckdlTE1SbE85UC9sQ2VyVGt0VzNnMjdTClY4Z24zRVRtMk1tN3JrTnFmMjRLSnBEdjB0S0Rvc2diZGFIcjJJRVlENFJwcXlTcDhrZDI1Qmh6dXNocUtSa1M4WHU1dDdIQWxWU0gKd2lGaHVMcXJyNGRVZmtCOGtaZU0veWNmWkxDbjdvTlVERmRnakdZU1ZNcGFrTDk3c0M5c2xBVzQvOFV0WFhaeExxY3lxL1l4ZHBDeQpzUFlQMWhzQXArVmdQQzdHSTZDeWlOb2pLUE9wdE1xTFpSWW5WaUt4bE9pV0JKQnpVQlJVVnVhYzhMWHJNaUR3OGJ0V0dhMUdoNXZUCmh1RlVLc3ZtUm9ldWs3ZXlYRU45SjdqNitmVFlqbnNDQXdFQUFhTVBNQTB3Q3dZRFZSMFBCQVFEQWdUd01BMEdDU3FHU0liM0RRRUIKQlFVQUE0SUNBUURBUEZVbzBQZ2E3dkI0SWp5OXUzcXBXTFFTQ2Q0eGZ3OGw5MmlxM0pxTFZYQng4R2Y5WFZ5Nkd6YlhXSWU2cG1RSQp6bW9tL0NXUXZoNm83a2MwRjR5NGZ0c1dmcW5xM2swK0hjWDRIZXUxUE4ySFNuVVVQTnNrNUFkZ2dkMzEyOU1ISWRmYUtiOGJTdUxKCnZTTWVNYXljcm1zYitnRUY2SkZQOGtHV09QNHhRWWpBVk9EY0ZiT3lBZkV1VkFodWp3MGJxbExIVCtFbDR2bGZCcnRkVStNWnp0SXEKUWtHRFc3Y21tODZaR2lCci9nYTk3QkNuT0NyZ1pYbHJnbWlhOFNtRjdSZmE0OC9Yemg3YkRtUGZnbUhnbEw1SnhocFJYMklvUFk1WApJdG1SOElhWUtoT0JPck8vcWVyY0o2WjNyUTZmSXJzOEhyWWdlVE8vdUU5d3c2V1JXQ1dEYUNSNG9XRFZ3akt6R2ZQMW9KdEFJUStVCk5qT2RkSDVvdERhd1hKUmxRV3ByNzJxVDcrV09LL3l6SmhOMHhtWERrbXQ1cHNNWS9DRG40a1hNTTB4YVJwZllPbjVTYzFvZWxRK2gKSW04L3JIVksvMGtyaENET25pcWorTC9uZTJTb3lvWFlnUzZvME5sa05odFRIbWpqb3NFN0hWNkpmQVVncTJEOWRxMDBKTzk2N0kzUAowVkY2RjhsY2RhcTB0b3ZKaEtXUmpycmlnWWQ3NTYvM2FUMkI4M0pXQTFSQTluaVhtclhmbkFQWnJOQ2ZXdmorWDJhbFVOL2l6ZElxClJ0czBVcFdUb1ZVcjBveldnbWJtcDVaT0dZT1FoK2xOY0FwM1Y5bE9yZGJaVlNJVE00N1JZR0JaYVpOREEyUGJEekMxM01TL0VqVEUKdlNrb253dGxDZz09PC9kczpYNTA5Q2VydGlmaWNhdGU+PC9kczpYNTA5RGF0YT48L2RzOktleUluZm8+PHhlbmM6Q2lwaGVyRGF0YSB4bWxuczp4ZW5jPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyMiPjx4ZW5jOkNpcGhlclZhbHVlPlFPVExoZmlOQ1NoNkduNWRQWmQ1QjB3SEJFUE1nSGxpV3h6TEp2eDlRTWxjelNXdWpnaUp3d2x3N2M4UHZTTUtNZXUxaDhhbTd0ZEUwcnAyd1JpSUJjenpHTlhUbm5xaFM1NzFzMFBLb2hRN0h4ZVRXZ0YxSTJidFcyWTBzSUJsNlYvanV4blBVQjF3SUhQcXdMYVlvdkN6enlWeWVrQnlZamI2WUp1eENPR0xGL3U5MTA1L1NjSXQxd1hja0NrTUlOcWY0dUdaT0FNOGZKQTFzQW9ldzZDcnJ1UG5OakVDWmNhcVNBZjZFUC94MGIwaXUvelZRT09ZMHRZMVRRQmpMRDFROVFoS282TWhCL2dYTS9waW54SnUzeFFxQTVIWEtzVGdWSml3SVlCQldwcHFzb1ZWUWRGUXErYlkyOVpHWDlzTSswc2tnbGJWQm50czhDQnFNdHRISkxTNTRNRnJ4aTJ0Y0x1OHVzek5rNGF6ajVOdmxZMlFmaTJTTGdvbDNaTS9DNGIwRERGS0xXc2Y4K1lkaEZoNnlPb1lkN2NmZ3hkZTlqK0YrVHI2U3pzeXlybjczTnVPd0hJWHV2N2ExZHduS3pIM29oV2ZBSjN1a3JTbWZqRWNrRURkNmkxQWRiajBYZUhYMENsZmlNK0IrRzl3aENhV0VIeFRiL3RjeHVXMTdJY3B4cTJIakZCUmM0bkVIaFRLOVRwcHJKOFFPYkJTZ2puZWxINjN6MmxSSVFWc3lYUDZmV2Q4Z3FhNG1kQ3VaUGs3YWppdDU2dlBxclBZUk1yRzU1N0VaUjkyVm42ZWZya2dDbGtoUnE0VmpPbE1GTWZYNkNKVXBzSW5HVHlISnRqdFgvR1U1NWRib1dqVEt0bmRxeEMyNTZLbTJlM3NWT2ZUOUpzPTwveGVuYzpDaXBoZXJWYWx1ZT48L3hlbmM6Q2lwaGVyRGF0YT48L3hlbmM6RW5jcnlwdGVkS2V5PjwvZHM6S2V5SW5mbz48eGVuYzpDaXBoZXJEYXRhIHhtbG5zOnhlbmM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jIyI+PHhlbmM6Q2lwaGVyVmFsdWU+dytwQjNXTlcxcUV1YmhDQ0tPUmtkc1ZNYTUzOVB5czJMSmR1dGlzN1RuMlhwdm5xREdZUG9YblZqSCtTdTQrTVRQdi9nZ0FnUlJnUW84NE1SUVJHZUs2OE1pcDhMLzRhQ3Y2ZmZUM0czVEpPdGc5Y3BhQVI4RkpyMXp6NFZhbi9UWGtScmpVRWRIUVlJTmhkR3ZUYVdSWUx3dDA2SExRckVoNlgxekJLbHFHanNEenk3Zm5LT1E5SEJqY01INnNGSTNENVZnOUtoc085QmFTNG9aL1J1LytUdC84SWg2VUhDcURTM0VUcnVqTUdCdDNRMFhaMWxCOHpWSUVIVXhoemlhZkF4aGlzZ1B3QWxYcTR6T0hteSt6YnJxekFQVjhhQ3JBT2RtbkZwYlA1VUV4MnRNcWJ5aU9jZVJ0R3doaUV5R3ozT1pyM0dtMXR3ZTJtNW05S05OZmt4eW56N0hVQndqS3hkcTNQTmFiMFNhOWVmNWRESHhiNHdFVDVJYUtSRGF6TDVlQWVkUnljL1FlbG1aNFhUQWljVGtySGRpR1VxeUhQdm9Jd2VmU3hhK3pyQTNoRjl1ODlLMmdQQTlGTHljWUJMc3AzVU5HMC90RkxkRnFhK01CVGNRZTRYSWJodlJsTVI1TU1zUHNPbm9Fclp4ckVXNjdvUzBpakJsaUlrY2VCcE1HaWJ6VUZtUDJsRFcxeDRjTEprTFlxWmVua1hSVURvbmVicXVjdXNIdFNOenNTK0g2MU8rNTI3NFhOM3FvRzZpdEZLVEUybjVQRTU0M0hMOG5Ib1dmWjdBTG1VeUdZSzAxTnVjWjF0L3B5SytCNkFhT3RSY1lvMWlwcTVWNzU5cmNUTGFVdFoxeVdCNVlZSVdtMUkvNGt4WEw0bGpPcng5V1BiMUJGTnJVZ0FBQTV5Qzd1T2s1MXh4S1RiaGZoakpra3AwcEU5WWF3cTdBY2lrb2VadDRJcDFpQ0JoZjZCdjB6RCs3c0tyeE5XQUxzNXVYOU40ZDgxekZBamU0eGlHMDFZUzBjOWtEWk5zU1E3ZDI2alE5ait0YVBYa2Z6aFVRZithdnczMXVDbk0vNFBHckc1UGlPeFhrVmcrUnVrZGVaUjU1VEEzZXBmamhSbEM1UmFnWGlGeURLOEcrK0dDSEJvVUtBN0pNcmtacmlZczdDNE9SNVdDQVNrOUhTTEIrc0VicnJTcnF1aGpKZUNTR0ZyQmpGckw3OGx3QnI2QWxhN2Z6bzhmNnNmL25nSUNxNDNmL1FzYXpwWHpyZzlYN3NTWTBONDJJaUtIN2FiSzhSTXpvbmpETzhFb1p1U0tLT00vN3VVSGxMalVXbjh1V0dBa21zV01qN2lUa3hQRkxRNVFKM3VzZHl6U1FndUZRMERVVmF3ZmJlb2NtMk90cjNTdC8wT3VVOHBzdHZodHdTWnVJc2s2NFk5MGFGZ01kRHcvdWJNS3lzQUh0aXowdUhGenpDMW9iTmFaY3BLS0tVUml5ZkYvNkY0dmhNbDErRC9ubWpUQTdRVm9yQURDS0lpUnFlNXRzdG1tcHJIbWRibFpmaGI5cGcxcDJBMVZPTStzRldoRG9QWXk4dWRaKzBQSGRmTkh6bDNLVHNIVjhUSmR4bTBXTTJiWURsYi9TSTBsZVBRUytSWUlsODJ0V2ZqUzIwT2VuZ1RoZjdHWUFLUGJUaUtnWHdJazMzbE5pa2cxSWdRMGhPcnFlZUV1MHBvb05NV1pOMlJBVkZpY1hmUkRhQ21lOFFFWUdWY0ZaOTFGWkY5MWRPZ3Z1cXY5NHVCcUlsUWtVWWl0SUh2cEFWc2RuU1VjQk4xN3JPMWFQVUcvemlUNFc5K1RsWFVTMmxjcXRKR2xwTjFCejd4Wm5GOXZBQjRDNy9xRC9XcFhZUlJUQ2VuUTJZbStMeHcxNXJMQThadURaTU1qdWxyMXErN016K0h0dXBtalV1eVJJRjNMZmtRVkFMa1YwUTl1bGw3djgyOVRHN2ZhSE00ZjJOR20xVHdXTkw0aXVJVHBsTy9LTHJBR08ydHRQdE1rZ1RXdzV5WWVTd0pSQ1B4SFhTa0pVQ01EblcxU05KZHZSNUJ6RXNZakpTQ0Q0dkpqTVJTM015ZHJldzBzdFNHZkwzajdScjgvUlVXbDlXTWlQOEFyU1d4S3JFRCtBNzJua3hlNnFtRFZtNm91YWpiSk9rYVY4cDZMNW1XWUZ3MnBobVpQRElvTndCUzY3K1lGc28yOXJTL1dOZUJRQ1RTVWR1VW9tbk4yRFNib0x5eTVzM0o3M2xyNXBrZktQODgvL2dDQUxQbG0zQ1E3VktiK0RIUDd0VUxpaFVkTmJYV29YL2IzZXk0QzdDRmhtclhYRTViQ25sWnQ2TFlIZlhNSjJvRmxDOHJyOTBSY0VZWjlnNjVRdktOVDFabVJVR3JveVBYbjdHTThhU1N5RGZLeDhCQ0V1d3FYL1VzN2tWWkxDczhyMW1xQUl1eTlMb09oVlIrMEJ4TDZhTTRLMEpvLzhIUy9mNE80c1BRd2JGMWp4UU1ZYVlabHBLdzBzL0o4UGtLSnBBUzlyM1RtajZFVlNqb3dscmtmT1hpUVhaWkY3dXVwdlNvOUwrVnVXZ1Vuckg4dXpmMUhlQVhUMXlYRDAyT0dTR2JyUnF2alJZWFhTVFFpTnZxQXo3UzBKOHJSNTE5WUFiQk5KdzlwUExXRjVMZWZ2VVc0NnpYcDVZdE9LTWlENnk5dmJMaTlJbHRzM0MydUxJY0ZIbXRRMmZ1RlBwbzIzeTI5YXV4U2IxdXQ2Si9BK2tyTFRKWWFjNEczS3B3ZVFnVEs2dXBHeGdZM2p3c2FzTjMyWGs3eC9rUzZsVThBM0FjNElmQTh3VlMyVFF4S0FaZnNJNHFrZVJqNVkvUWlkTmM0TTlWWUZCeXo1aHNJdmxObjFtMy9oV1dvdDRDVzhlNzFENGN4UnUvQTJjVGwwSHBybWZ2cVFmTVhmMU5kZ0Fya0kwSVIydkMwUWZCYm9FS0tDYno3RThJTWJJbjdWK1BmdGdoOG4zOXMvU2F5c21TMXMzdzRHdFFvZHdRMTdmK2gyRVR0RVZOMkh2VWZMRENoQy82dGVhaU9hd3dJWXFvUGlaczlOZDFrZUZ3YlFOSGpkbUE4ZW43UHYzdXhIRTFLcmduUXNqci9yZ3c1ZEdwNjdmSFpkcUpuZHgyVVNPV2tnZmdLRHZ0SlNXSWZtT2I3WnluUGpxZ3ZLYzV5RWFQUXQ2TUNXVXF0WEEvVTJ5WG9wWGhDYVN3cXFIZlYzVGRrb3JkcURucW9jYTJKdDRGaUdTQWhEclY0VVNSblVvUWoxblArS1F3RktDVDcvME5Rb3Q0VWhWWndVNlAxUFRUSExBNkEva1JoMkFsaGZZMTlzOUh0SVl0SHpMTm9nbWFkT3pxR3NCNHFSQko2blZ2N3lXVER2ejhtVVdMTjNmVlBzdnUxZS9YYTlKUWQzK3pKUXRVSXhQWjBPaW9jbWh2M0U1TzFRSzNodVgvdTdWaXAwbFBEcHNvMkRmTDNqNU50blBhQWt4TUEzVDN2ek56WFcyV3ZyeE5HYjkvRDdESEpuZ3NqdUF4OWw4VTl1RHJPNzVmaDJiREc3RzhFUUNBRktQMkJjZWxybkREY1B3d0wwQS9wSW1zaEdwUlFzMzgzeERDZGxVa0toTUJnbDI1cXJmWGJCQVJOQmFtV0hFNGZQem1iYk9MYWtRUVg3VFJJRFNWTGNiTTQrdU1idlROL0R2TUFXZlA2ZW9YU1pHWGQxME9ET2FPVzZJVzNITG9seUg4QzU0cUwxN2dVeFJRMElNdjNGc2tHYmZ5enBvWi9GRmdBa2I2Qjg4eW9EOW5SbTB2czJmYlRrd1ZLNDNENE5iN1JuOHZtL1lCZGkwVytXTUdMdU9lb1d5RGl6NVk2YnZNR3kySjI5V2pwaFgwaGphWGVySlVKdFpRT0tLclAybUdCWjVNSHlmMUtFSVdraFdtemlOYTZXbmFzSU8xM2o2TkRqYnptbkF1QTU1VzBFQlhVMHRYTE1pckJvd29iR3lzVXZMOVBvTTV0TDBuOUVYTGtwbHhCS0gxVmZtNG1MR2M2cGNVVU5sK0dWc3daaHdZVVNtV3hNQU10M25YemkrSlFWOUIwRGhCU3gweGZ2bEZkSmUvSkcrTmM1ZmprcTlSbkFWMHNweXlwTnZacnZ4NXZ3T3d3Q3hCc0VNSWV3RDlNOHQzM2VGRnRYRnRBeDF2Zlo0SzlQV2VRblV0Rm9yR05ZYjF5cVFnbHIxMGxGeVpXWWdpMHJSUXlqTHFmdWxSODRmTkpUYjNabnNIeWNuK1I3WXBsM3k2WWlvRUF1bThtYlRmRVZCNUVoTXptaVB1Tjc1V2NxY1JTZDdlYnprY0JBL2JYNGZYMEVRWSs5cE41dUt6WE1USzU0OTZPM09KV2R4ZXllQXQrN0cwZVNkREo4OXEvbGp6T1g5OVFZU01zQ0NISWwrdElYdG9aMjFqb2dZVnEzc1RtUGhscThHVHZmeElPSTI2WnM3cm1FOWJFTGs2U1NYeFFoNy9KSUEyZXVSY0RUWDlSM1FXalMrUFRnTUJObEZXYTlEOERycVJPVWVyT0V6eFMwYS9oTXJyUHZub1o3US9mTDgzQ1JhanM4RTBwYndGZnNuSVF6MVoxNWlXeklBVlFFRnBOZXZ3eVkzSmlYSndkRTM1dVIzbVRYdXRkNzRrLzNuTXYrQmszVjZYRnpkMVp5dDg2Nkd2Vmd1alMrSWFqNllRNmZGckc0NW1lekU3d3VJWDBWMlZVU1Rjam9tcndaNThXclhvM2M1MHdRNHNlV0tkNnV2WCt5dlNBWnlxclhTbkdhWDNZUHlBemVhMFFWa2JLWGhCWHVLYUpGMzNMS1RFWGt6eVJvOThLRko2Z1dPSExWQm5mRTdrWlFKUEkzNEpTSnZzZTExbmlmYkMzZ1lpK3MzSlVSOGQwSmI1c1JkcU5Fc1l2d1lIM0xQcGRYRkxOUXBNM2ViYm1UUGdKR2J5bFJTUDFWelJVRURsbXgwbjRVUGs5SGFZNksvZkZWSEt0ek0vVnpJUm5IK1VRRnFXN1RSMkxFdXI5dmNKcytGaGFieG8xUHc3VlV0R1lZVkJMMjVZbC9aQmR2THFBY3hLN0xVTFk4Yk80V2lCKzcwMlUxZERQRENaQWNYWnlid0duNDQxRE1rcHJrck5DOHVxbkFQRHA0ekNMTnBENWM4NXg2Q2pDMDlJei9TYnorbkxLcXgyRnY3MVBCcmh6RXlvbHNVNWpEZVVtSGcrTFplVzk0dlVNQ2lZalY5b09ma0ZIa2RHbk10NGl6dmJPd3FETG5JSFhCWU9MYUNuNmUwd005Ums4NjhtRWpoYXRBUXRvRm1lKzMxL0M2clJ1cW1NbSszNVNKY0ROejZuWEFVWWVVOFZjVkorNU5IWE56MUF1UXh3cWo2VlNsMHRmSmZUaDk0V1gxNHlzSXlBZnlTeTlDTXNHMW9aNDZ2djJkNEZETjFyMWwvN3hEYWtBYmZLUGNPK0F3U0dGWkxCQ09DaFR4dkVlQTdvWlhJVFRLTVdPUlJZVXVkb3JOQ2FOKy9IMktzcHo4eTMzUm54OHJaTUd2cnlPUm5nME5LSnBrT1oyOGlDczVGQmJPa2UvZWROeVFibm90b3hCUjJIbHcydVY0SWRsd1c1NVZkcHNtaWJ5alArS0NaSFNGOWV2WTFHci9VdDVYL0l1S21NbTBiWGQ2UFp0VjcyWWxkbDg3NHB2YSt3Y0NsOTBwcTRIU2x1T0JFQm9haUt1cXQyUCtHY1BZa2tRSitEUVBYdktYYlFWanZWbCtXS3FPL1VZbmRTMlR4b3dINk5ZMWNuVE5INGtWR3RmaHNSc3FCQzlSRjRsSGxRK2w4UnZaTmZMcHliWjJ0T3RwSmZaUXQyNTNnZE4zZGdzUEsxVTVISEZKd2RwYzA2QlY4YTNueWZvNWh4aEorOXVaTisxdDdGYUtpdng5QWhscE8xTytMcGIxenovM1VucVBRR3FwbEJVemtINko0VmVSTFdHNDd0SkVZNVRZbmxrZHpvTGF3dUdjM3ZwMWhZUjZZVEg0Y0I5bGlsMUs5cHgvUFI5UGRhM2R4T25zelZ5ZG13RWw3QjZHb3VrK3hXMWF5NG1iT0hDa3JwdEVoa3NEcjY4WWE3b05GUXVZMnNEUmdla0VidHo2RFZFako1b0hkUmVvSFZMRm1TM3pSZ3lwNFJRZ3YxMG9wUWNpTU1hNER1OGh4dVpVN3JYMVNGYm5uQ2owaWZoZDUyd2JyOVEyWisyYnNKaTEvczZkV05JS3ZDaDhxUE9YU21IL21YMFpETkFDbDV4K0phTTNOTGl4dEFlZFVxcjV6dDlza0t4Qzd5cURUbVhMRkIzRjJNRm15UzBtMFVRcDdDb2daUVI0MjI4TlR5WFpSK3FYZExhMUlaU1owOC9BOGUwMzRJZ0FzS0YvQXhJdFNuNllZZXhHOXFYeU5aU2x2OGpPS3FiWGdidWZnZHorVjY2OHVGMnpzRk5RY2pBUUxydGxqWnRFR0g4WEIwMFozT3ptb1ZmcWFqWFhMSlN6UjcwSFJHUWpYK2d6SFVBUlF3Z2ZxUVFtVWcwZmtBMStyNmZPWTVYMDVRdkxNaFprWmJwN0RuUmczQUZKMU9hYnRPYndJek9WRmhsN0p2SzZlTk9kK0NTNkttZUZSS3BwSExCNSt4Z3NpNGRSQ1VYL3VGZkVFZ2FxNm0xems5OXprSWpTZDdRWFhXQUpIWE52K1NuUjcwTzVkdkRTam1ZTSt2MnVVOGZNMlBiZzU1OWJ1LzIvYW5VQkxEMWJxZG9pTC9GSHFlb1B2am1zRktkcFdnRjFHRGVkam9sVmRzUWNTeUxoYlN0eXUzSWE5RU1OK0h6V3ZmVXd4M1hSOGQxMHpSS2lBL1J0Qjc1cUVFZm5UQTREa1grVUxCVGd1WkxkWklJYVVFTG5XN1UwZldnVEFuaEdqTVp1Tmdkdk5qb1BGRmRlcmxmRFplazM0aDVGQUJvM2srTlJuUk9USmhZaUhHOHE2MDF0NEVsaklCVnoyYkw4UW9vL055ejE4VFF5NkRGMUVhK08rVXkwUlhlMk51QXZBekpUVlAwU3prMGE4WTU3d0xZVElPdW55UUtKY3JxemVaSkUyZVFKNVM4bUtlR25ZOE5MRVFaNlpXZS9CK0UvOU9ORVFCSGhPUzVkMUtVV2NvakV4SE53b2p4VUF2cTZYOThmM3orRzYrakorcEl1aFNlaEdjcFFOVFVxYjIvSjdsbU9vSHF2dy9lbG9MRk96b3pya21wQURUVERDV1FsMCs0QllkbmRPN0FOZFVhRlBYMmtlUk1YaUQzZ3lxWDlzcmdTVHBzSlRuTHZWR2I0KzhrT1kzcSt5aUd0T3ZxMFpCZmZ5aDcyd3E2alVuNjZPR0hXS2oxM3RNM29GcnR3WmZXK3FnMDhCSGg1amlFVjFBbUhUSm1CaHUvSWtZMWl1YnpCZm0xdm4xMGhSYllnb2N6UkFNV3dkVWhIWnBmMUdjVUV2NUQ5djMrL1g5SjUxRWFwVGdaQlhtaUZqdUVtQ3lQK3hXWmRnTTFLRlZpTWhObWhiYjJwNmZSaEt3d2VwUGhrR1VKbnR1aTNuZDh3SFNlSVpQR3d4bDNpcFBJSmMzLzR6UTdmMk9tTFByNTNoMzRqRGhrTUhCcW1acURCV3VQelZQdFd3cEZCb01yZWpKTThCUGcxUmZtUEJxUEw2NnFBWFJJQnRnTmdIaHhLRm00dTVMd1o1Y1J0UGZkVXUrcTlvZ215WDNnU00zSDVYeXo3K0lSaWIySGErRmpHMmNGU2hvbXJTS0RaZFpGaEJOYVdnd21rU0Z1TDlpdTF5QmlLZ1p1aERCUkZMNVZOb08wd1hLWGJBeC90OG9hdElyTjRHRDlWYk4ycWdwMFBvRUw5dXVyUDZFU2prcTFJa3Q5UGtBOVNrMkRhMTZFdEJsM0ovRG9DSUczdDNvbFRjZ2RMakJ0N0R6S0EyWVlmMTRiZGI1ZStVOHBkbzMxemVWbkZTUXBTdmZUVU5KNHVuTlVkbEtIcFoxb1BDQnAyQy9wTVppMkJxVk1QdGNDeWZsb1Z5UlZFQ0RsZlBvLytQMzYvTy9aUEJKZ3pqcWdxOXdRUnliaW9FKzE4RDh0UXM3RGNaYnZ1VGlSbFlCNUkrRkd6YTk4QUZ1YTlCZklYR2d5b0pPNXNVVnVVYWh1VEYvYjZWc2ZSNmlRVlFXWVNvWlNTRnlMaFFTQncvM0FXUDRld0xOd0ZSZFFqUWM0NWpkdkdibmliY20ySVBVQjdzN0xhb1UvV2l2MWY0ZEoyMUlpSFlKQU1jeGo1cW9mNVNxNUdCVElDV0c3TVA4SHFFOHFmcElIVUsybTZRRVBMenFWNEkyTjlMRUxZTWE5OWZubzcxaDc4cDJhd20wR2VWSmZUd3k4V2NkYTJuQXlhMEFwd20wUGhIdFJoeDBrRTlQTDM0VWd4NUEzR3gxbDdiZjkxa3BqeE9QT2NoSjREUGFaSER3MmIvYk9HcnNDcTBJZms4dkluM2JxcFk5U3VYbmZpOHpHenNTb05SanhHZWdKOVZiYkh6MFpCK0p4b2JzdldDK2Z2MEpSb2FQM2ZDSkZ1bGdsazdOVk5EK05hTEgvR1JqVDJTbCtjS3ByV0R5THlqK0UzcE1yY2ZLWFNKck0xZHIyaVl6RDBPb3BRVWpnR2IxOFIzczdKVXZ0QVBSOUp5MU1KcmlIbjNjV2xZSXZCWXRmQ3lXQ1pIdUc5NTF1c3dYMWt1WURYaGVDUmM1ek9Td2o3SDVWR295eWZSdzBMdnNVZmdhUXNhT2dibTZLTFJRcGtQZkl5ajdiMWE2M3RrQ0lOSTI5cHNyTUdVei8zT0JYbkNBR0d2Q3BxZkFwL3hHZzByaDdwMWVLaE93alUrN2NVbWlUS0lsUFR6TVBITWtJMytFTDlLc1JzV2J5OGoyWVdmUWNCUHAwNGQ2QzVNdHJYeCtFMWhoamNTSEE3RklpSkh2V0V6RVpxRkpXTDFCdHp1UFEwNlVZcVRDb3ZxTjlnZFFkZ2NUQ1lub2FpN2QvZ2M1alJ1TGowa2QwdjVxbHlyQzdsYjJ5eGVLQmFuamVxVXloQ1FXdGc4MmlZVWJOMkVhSVZiVXBRaUlOT0YxTTZ2TmFtZUFGL3I2QjZRSy94VUxEalMvWms4WExhMG82ekYySWh4VjExUTkrRUZ4d2h1MVVZZkltbkx5OU5MODJtRFNQTUZxS3FsT1NvQWRxcTU3cUxvZmpGMndwY3kyTTdvWGNrQ2tPaVp6T0U5QkMzeGNsVjhoRVB4T2JKQTNjSk96emc4UUl2RjNrSmxaNTJMcDlMSnQ5Q0g2ZThyZEM5clZNZ1BneDU5YWlLTm1iMkFsOXBaN0hpUWZWRUZ6QkM5SEhDVjRPZzVKZmt1VzY1UjM0QUI5SUt3WEFyTmNDTWFWajcyL2ZPbnRhdVNDa0NIZ3RKdVdDNlphcmFzaVpIYWc2NG1kOFVWbWtwRUJ2WE1OYTZJN3Rtdm1YL1lmUE0rR3Y2MzcvR1U1MmhGRTRIRVVsK2l3eWlJQ2JxSndHNDNQenRkcVpuWTRvYmNydVlJMGJKSFdSSzhhZ1Y1aTkxbGx2Qm14WDVtelB1Y3Aza0JJSkkyWDNFVEZ6SDlRam8xbktrYzhtY3FlSWtmZWl3ekpyL1ZmeEY4UXg2VmJWSjJhN1o2VEVNd2NiVDRJUVZjaUlyKzYxUEdlV0svT3F5bGdZODIzMTczNHhpSG4rcGFnWGpCc2lObHcxQm1ublhKeHZucTJWRGw5Z0h0czJqMy9mcjYrbEgvaDUvY25CK3czRElVQzlGTmI5UFZtb1B6dkdJZ2ZiV2puSDI3SFRZOFBjaDRpZkVHV0tRMG51a2s2TytpbW4yUTdkZC9LYnNYTW5IdXZETDRHNmxlVWZpTnFvMUQ2OGJJT0IyQ3dkeHVvbk5yenF6ZVBZb0VPdHZQSE9iRmhuVUlXaGJXUG84Skx3ZU80UFNIYjh4U3dOa1JVOVNZaHZ1bWwyZWZMWi9maFo3ZzdGb2lIeS90ak1sK3R6am9JL0tKNEh2YWdINTYySEg4VFlNYUtCekpzVjdJdkVEV2J2Um9HOU1FWnlBdlJ0amw5WmZUMjlTdkgvRk9zejgrQ0lzZUJWSlVpbitDK3pZZDhKTDNIMzdkSDNLVVZxN0xKYXBESEZrRmtSU09tNllHKzVicnAxTXV0a3FaSUpaMWxOd00xTW15L1pmSnFWMUl6SHdPWE0rVm5xa25WZVk5SytUNzNiRlRWOUhmR0VQMVduVEczNWxjbUoyUHZ1TmdhVTIyejNWUVdmalNndG1JMWthVUxtd3lHMytlcmF4Nk1yWFJsZFBXYXJlZi9BTFJGUURhOWdXSTQzT3czVFE2SXpFRkVsS0czaDdHdE5Ba1NFU003MCtsWnNjMURUYnNrMjdNRUlha25MdUhjMkJXYmYra213eUIxZEozVENmd1RMR0VQZFNBa3BCa0ZtaXphMVFVeVhib3lsQnpZcEpucFYxV0dWWDU0R1hjb0p1cnR2RzNwN2lPeTlBcEpZcUl6U1pMYVJIazhCRHpXdDJjekhsMWJaN1VZMGNtbUxQUzlBc05SYnJWdm9LY2htUVNUZTJMcG9rTWpaaXltVGp0TG1OM2wxRE5VNHgxQXhTT1FhTVptNHlvUm94YUY2ckNBUlI4MFdsbHRvY0ZDK0lYTVJHMzlBSklwUGlRRTNwQmVHNHcrWTBGVUFLTDZHUmtqL01YbnQ0ckw5M1BkRldTZjN6MjU4V3VsQjRIcmVoaHl3UEh2dk5NUUJQYUtVb3VKc3BRZmpKYWpnMmZUbDVDMGVxZVh6UEM0YXM5M25uczY0ME5kQk95T3dRTnpXbEdsL3FrekUwS203WXBjRk43UjM2YnRqczF2dWtoQ0p4Z2hqTk5QUHdvei9xWE9oNDVYN2h4djQ1eHA5M0h6TE1Eam81ZjNEWXVsSkRNVXhONitySmdGdDJwbTV1VklkSXVNdFQ5eTBqcXdJS0dEaDR6elQ0MDloVzFZY204ZTVjRkdKWjRNeWR3dHNOT0l1Q0J0d2NFdDUxdG9kVWRYVDBrcTJ1WmR4TGorNGhZYk52OXVhMEFYLy9JNTdydVZhTTdkdVZDZU9oR1BVWU1UQWgzSnk5UktXOUxUbG9vVDJQM1JRQXJ1SFNGajlzSFBhQ2RodXNFbnY1V1VNWWd2SVAybU9YRUdhbCtVeTh6M3FZRStyU0Q5N0tRUkUvOEJlRkM5b3pNUUlEVXhQOW0veXVFMUlKdHBrVzRNaHdtM3MvUzVpK3kyeG1pQ1JlT3d3ZVV6Y3dMcjBNR08raHhtSDM2anVMQU5ZdEVWZm80aHlUY1FSdU83MVRMOE1NbWhITEJnVi85ZGN0Qkx4WC9CRmxRQW9SNVh3UHhWYktNMENMNE13RnNwUE1SYno3NXRzajJVZDQ2TkxoRHVBRjZVVGpNdDVxclBYT1pMSUNNQVFsbGlUK1lMZzhMamdTYWdKdkFSamEwczlWUnVOaHJIRjVXMU1LY0haRThLQklWR0orYktjLzFLWGJFT3dmUHUrZUxmSS9QR0pScnZlL2NWbTBhUXRMZ1VFcmNwRUpUd1FhMWUrRE1aUHJQS0h5VU9WZ1FkMTBEbTNTMElrL2NNWGx3d3RUSDVFR2YvZXY3R1pOL3EwYWRyY1l3OHVhZXRTVENzOXlhdnpuMTQ4M1RsRUQ1WE82eWZHOE9MdDA3TjFDNmxMTElpajdVb0xrRUlKTGpGY2VCTWU3amhOK1BmN2M3MXVzYUVCMjgrdDRSOEsvbGV6bzc4MEYzNS9ZZklKeGErVHZxbGVxdGhGempzSnpYOHZHSXNlWHpQaU1acHpwbUlxVHhNbzhPTUtlcFlPN3hnRzZZakUvZGtvdFFhUTIwUTRiOWpPaVdYNW9KSDAzcGY3eW9Sc0ZyamE3ZTBFeU1haGNsRlR1TE5lc3dXNkgyTDVhTzlzU3hjUG50a2JNbXpJMFcva1g1eUNMdlQ5WGIrVFM2cUdCV3pSNGFQdzVNQVpuWGxFcStUYm5RWTkrOW9xSTZoclMyQ1VUaUNFMWIzVjBOQ2w2bW1RK2NzV0tRSllBdVM1cDdqRzRDelcyVFQ5QXpGczNteE1TTnk3cVUzQ21sdlhNcDB0UDdnNEovWE91UXdBWCtMNDlidmVxZ2VWRUs4VVFkNDZZS2lRVkdsUHluNGF2T0k0a0dXNjF6NjVJUVBOOVVPcjZFRnhPVXhJWnIyb0ZEdEdVUnFuSzNVZEt3Nzdtdjkxd1NkZGRESlIvTHRkN0c3Wlpmb2lHR3hHZ0lLYzNCQUxleHhUOE5aMGNReHVFL0ZTMlR4dWVzSEJ6Y3lSNXkvVGZOeHVGdlRENzFrZElEeEk4eXVsYzZoUXNjMktudnZXTmNSMEREU3FmeFhDUjJ4OGp1WmxBWkkxbXUzcDBNRTJiV0lpUXgzaEFwdmljSkorN0JoRGhZLzh4R2U0WEVrVXhEMnJ3NWFremRWSkdWSTdEQnAwazhHMEV2YWw2cmUvQnpjRlVFNFJhZW94T1U2bDVRRjNZYjY2NnpvTnEzNzhnTzd1cDY2TmoxZGdFZ1lwOVhJUVN2WEo3Uk9FcEFscUxMRC82NWdOWE5wYmlWNXZ1bG1iMC9nQ3ZqcThRZ2ozTU0xNjAzNUhFZGlTdEpoanMydFJKRGhJQ1lHR09PVWdpTmlmKzYyOWlSajBGWHdNcXhncE9nTytLalo0R24vQU5YSFhGK0U5TlVLVk95c0ZkWkpQUGNpYU1kVXEva2F5TzFNSVluTktRQWZWRHgrME5qMVN5S0Yxc3dLVmxxY3NsaUVzaXVRaS9nL1NFaU9Gam9UcEcxSFdxdkdTYXJpU2VubXc5eE9ZZy9uU0F0c0FrMlF4S0g5Sk91TUh1L0N5c3REUXdOMHV0ZmpjNHNUQnFXdkRONUY1a3JXa1J1eWxLRkQvK0M4MjBCd2R2d3VobU0wQlplZklmTlJmQzd3cHF6dlRUWE1Dc1I0anFaeElsSW9Tc2swWjhtcGN4bGF5ZGx4RWVhdXYrYytYZ2xRV05QVFhMcEEwWm5WaW5uUS8wQXNPcm9Lb2ZnaGphcENneTFCL0FrMnk3Z0NXV1RrRkp6ZmZWQzY5QnJnOEYrYzJJMkRKa3JkT056aGcyVFB1YzduM0dFZXJHdWIxMkZybFRMODdzM2UwNk45QUl6TFlGa3M0MmVEWnN1QUlwM0ZmSzU0MlkzT0U3dkR2eFd5UEhFd1ZuanNyWndZaGdBUXV4Tjc2cUVRTkh6bkl3TzQyRzQwRTRFYlRTRUFTbDAyR2VQMUZ0TEVsZ1hZUEJXSGFtNnZhd0t2M0lMTmNNZmRBdnJJOThFZmx6K3Vma0pRL29JZFdnYU90Ly9BeXpPRFE1Zm94ZzhCNE5IQ3F4MTVWQ1dTWk9EV0RkOFlnTzNBcUNDSlpzMGxzdTN1TGpWcjZ0Z0thND08L3hlbmM6Q2lwaGVyVmFsdWU+PC94ZW5jOkNpcGhlckRhdGE+PC94ZW5jOkVuY3J5cHRlZERhdGE+PC9zYW1sMjpFbmNyeXB0ZWRBc3NlcnRpb24+PC9zYW1sMnA6UmVzcG9uc2U+", - }, - config: { - entryPoint: "https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO", - cert: - "MIIEDjCCAvagAwIBAgIBADANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJVUzEVMBMGA1UECBMMUGVubnN5bHZhbmlhMRMwEQYDVQQHEwpQaXR0c2J1cmdoMREwDwYDVQQKEwhUZXN0U2hpYjEZMBcGA1UEAxMQaWRwLnRlc3RzaGliLm9yZzAeFw0wNjA4MzAyMTEyMjVaFw0xNjA4MjcyMTEyMjVaMGcxCzAJBgNVBAYTAlVTMRUwEwYDVQQIEwxQZW5uc3lsdmFuaWExEzARBgNVBAcTClBpdHRzYnVyZ2gxETAPBgNVBAoTCFRlc3RTaGliMRkwFwYDVQQDExBpZHAudGVzdHNoaWIub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArYkCGuTmJp9eAOSGHwRJo1SNatB5ZOKqDM9ysg7CyVTDClcpu93gSP10nH4gkCZOlnESNgttg0r+MqL8tfJC6ybddEFB3YBo8PZajKSe3OQ01Ow3yT4I+Wdg1tsTpSge9gEz7SrC07EkYmHuPtd71CHiUaCWDv+xVfUQX0aTNPFmDixzUjoYzbGDrtAyCqA8f9CN2txIfJnpHE6q6CmKcoLADS4UrNPlhHSzd614kR/JYiks0K4kbRqCQF0Dv0P5Di+rEfefC6glV8ysC8dB5/9nb0yh/ojRuJGmgMWHgWk6h0ihjihqiu4jACovUZ7vVOCgSE5Ipn7OIwqd93zp2wIDAQABo4HEMIHBMB0GA1UdDgQWBBSsBQ869nh83KqZr5jArr4/7b+QazCBkQYDVR0jBIGJMIGGgBSsBQ869nh83KqZr5jArr4/7b+Qa6FrpGkwZzELMAkGA1UEBhMCVVMxFTATBgNVBAgTDFBlbm5zeWx2YW5pYTETMBEGA1UEBxMKUGl0dHNidXJnaDERMA8GA1UEChMIVGVzdFNoaWIxGTAXBgNVBAMTEGlkcC50ZXN0c2hpYi5vcmeCAQAwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAjR29PhrCbk8qLN5MFfSVk98t3CT9jHZoYxd8QMRLI4j7iYQxXiGJTT1FXs1nd4Rha9un+LqTfeMMYqISdDDI6tv8iNpkOAvZZUosVkUo93pv1T0RPz35hcHHYq2yee59HJOco2bFlcsH8JBXRSRrJ3Q7Eut+z9uo80JdGNJ4/SJy5UorZ8KazGj16lfJhOBXldgrhppQBb0Nq6HKHguqmwRfJ+WkxemZXzhediAjGeka8nz8JjwxpUjAiSWYKLtJhGEaTqCYxCCX2Dw+dOTqUzHOZ7WKv4JXPK5G/Uhr8K/qhmFT2nIQi538n6rVYLeWj8Bbnl+ev0peYzxFyF5sQA==", - identifierFormat: "urn:oasis:names:tc:SAML:2.0:nameid-format:transient", - decryptionPvk: fs.readFileSync(__dirname + "/static/testshib encryption pvk.pem"), - }, - expectedStatusCode: 200, - mockDate: "2014-06-02T17:48:56.820Z", - }, - ]; - - let server: Server; - - function testForCheck(check: CapturedCheck) { - return function (done: Mocha.Done) { - const pp = new passport.Authenticator(); - const app = express(); - app.use(bodyParser.urlencoded({ extended: false })); - app.use(pp.initialize()); - const config = check.config; - config.callbackUrl = "http://localhost:3033/login"; - let profile: Profile; - pp.use( - new SamlStrategy( - config, - function (_profile: Profile | null | undefined, done: VerifiedCallback): void { - if (_profile) { - profile = _profile; - done(null, { id: profile.nameID }); - } - } - ) - ); - - let userSerialized = false; - pp.serializeUser(function (user, done) { - userSerialized = true; - done(null, user); - }); - - fakeClock = sinon.useFakeTimers(Date.parse(check.mockDate)); - - app.post("/login", pp.authenticate("saml"), function (req, res) { - res.status(200).send("200 OK"); - }); - - app.use(function ( - err: Error, - _req: express.Request, - res: express.Response, - _next: express.NextFunction - ) { - res.status(500).send(err.stack); - }); - - server = app.listen(3033, function () { - const requestOpts = { - url: "http://localhost:3033/login", - method: "POST", - form: check.samlResponse, - }; - // TODO remove usage of request module - request(requestOpts, function (err: Error | null, response: any, body: any) { - try { - should.not.exist(err); - response.statusCode.should.equal(check.expectedStatusCode, body); - if (response.statusCode == 200) { - userSerialized.should.be.true; - if (check.expectedNameIDStartsWith) - profile!.nameID!.should.startWith(check.expectedNameIDStartsWith); - } - done(); - } catch (err2) { - done(err2); - } - }); - }); - }; - } - - function testPassReqToCallback(check: CapturedCheck) { - return function (done: Mocha.Done) { - const pp = new passport.Authenticator(); - const app = express(); - app.use(bodyParser.urlencoded({ extended: false })); - app.use(pp.initialize()); - const config = { ...check.config }; - config.callbackUrl = "http://localhost:3033/login"; - config.passReqToCallback = true; - let passedRequest: express.Request | null = null; - pp.use( - new SamlStrategy( - config, - function ( - req: express.Request, - _profile: Profile | null | undefined, - done: VerifiedCallback - ) { - if (_profile) { - passedRequest = req; - done(null, { id: _profile!.nameID }); - } - } - ) - ); - pp.serializeUser(function (user, done) { - done(null, user); - }); - fakeClock = sinon.useFakeTimers(Date.parse(check.mockDate)); - app.post("/login", pp.authenticate("saml"), function (req, res) { - res.status(200).send("200 OK"); - }); - app.use(function ( - err: Error | null, - req: express.Request, - res: express.Response, - next: express.NextFunction - ) { - // console.log( err.stack ); - res.status(500).send("500 Internal Server Error"); - }); - server = app.listen(3033, function () { - const requestOpts = { - url: "http://localhost:3033/login", - method: "POST", - form: check.samlResponse, - }; - // TODO remove usage of request module - request(requestOpts, function (err: any, response: any, body: any) { - try { - should.not.exist(err); - response.statusCode.should.equal(check.expectedStatusCode); - if (response.statusCode == 200) { - should.exist(passedRequest); - passedRequest!.url!.should.eql("/login"); - passedRequest!.method!.should.eql("POST"); - should(passedRequest!.body).match(check.samlResponse); - } else { - should.not.exist(passedRequest); - } - done(); - } catch (err2) { - done(err2); - } - }); - }); - }; - } +export const noop = (): void => undefined; - for (const check of capturedChecks) { - it(check.name, testForCheck(check)); - it(check.name + " passReqToCallback", testPassReqToCallback(check)); - } +describe("passport-saml /", function () { + describe("saml.js / ", function () { + it("should throw an error if cert property is provided to saml constructor but is empty", function () { + should(function () { + const strategy = new SAML({ cert: undefined as any }); + typeof strategy.options.cert === "undefined"; + }).throw("cert is required"); + }); - afterEach(function (done) { - fakeClock.restore(); - server.close(done); + it("generateUniqueID should generate 20 char IDs", function () { + const samlObj = new SAML({ entryPoint: "foo", cert: FAKE_CERT }); + for (let i = 0; i < 200; i++) { + samlObj.generateUniqueID().length.should.eql(20); + } }); - }); - describe("captured SAML requests /", function () { - const logoutChecks: CapturedCheck[] = [ - { - name: "Logout", - config: { - skipRequestCompression: true, - entryPoint: "https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO", - cert: fs.readFileSync(__dirname + "/static/cert.pem", "ascii"), - identifierFormat: "urn:oasis:names:tc:SAML:2.0:nameid-format:transient", - }, - samlRequest: { - SAMLRequest: fs.readFileSync( - __dirname + "/static/logout_request_with_good_signature.xml", - "base64" - ), - }, - expectedStatusCode: 200, - mockDate: "2014-06-02T17:48:56.820Z", - result: { - "samlp:LogoutResponse": { + it("generateLogoutRequest", function (done) { + try { + const expectedRequest = { + "samlp:LogoutRequest": { $: { "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion", + //ID: '_85ba0a112df1ffb57805', Version: "2.0", - Destination: "https://wwwexampleIdp.com/saml", - InResponseTo: "pfxd4d369e8-9ea1-780c-aff8-a1d11a9862a1", - }, - "saml:Issuer": ["onelogin_saml"], - "samlp:Status": [ - { - "samlp:StatusCode": [ - { - $: { - Value: "urn:oasis:names:tc:SAML:2.0:status:Success", - }, - }, - ], - }, - ], - }, - }, - }, - ]; - - const capturedChecks: SAMLCheck[] = [ - { - name: "Empty Config", - config: {}, - result: { - "samlp:AuthnRequest": { - $: { - "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", - Version: "2.0", - ProtocolBinding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", - AssertionConsumerServiceURL: "http://localhost:3033/login", - Destination: "https://wwwexampleIdp.com/saml", + //IssueInstant: '2014-05-29T03:32:23Z', + Destination: "foo", }, "saml:Issuer": [ { _: "onelogin_saml", $: { "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion" } }, ], - "samlp:NameIDPolicy": [ - { - $: { - "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", - Format: "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", - AllowCreate: "true", - }, - }, - ], - "samlp:RequestedAuthnContext": [ - { - $: { "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", Comparison: "exact" }, - "saml:AuthnContextClassRef": [ - { - _: "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport", - $: { "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion" }, - }, - ], - }, - ], - }, - }, - }, - { - name: "Empty Config w/ HTTP-POST binding", - config: { authnRequestBinding: "HTTP-POST" }, - result: { - "samlp:AuthnRequest": { - $: { - "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", - Version: "2.0", - ProtocolBinding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", - AssertionConsumerServiceURL: "http://localhost:3033/login", - Destination: "https://wwwexampleIdp.com/saml", - }, - "saml:Issuer": [ - { _: "onelogin_saml", $: { "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion" } }, - ], - "samlp:NameIDPolicy": [ - { - $: { - "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", - Format: "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", - AllowCreate: "true", - }, - }, - ], - "samlp:RequestedAuthnContext": [ - { - $: { "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", Comparison: "exact" }, - "saml:AuthnContextClassRef": [ - { - _: "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport", - $: { "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion" }, - }, - ], - }, - ], + "saml:NameID": [{ _: "bar", $: { Format: "foo" } }], }, - }, - }, - { - name: "Config #2", - config: { - issuer: "http://exampleSp.com/saml", - identifierFormat: "alternateIdentifier", - passive: true, - attributeConsumingServiceIndex: "123", - forceAuthn: false, - }, - result: { - "samlp:AuthnRequest": { - $: { - "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", - Version: "2.0", - ProtocolBinding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", - AssertionConsumerServiceURL: "http://localhost:3033/login", - AttributeConsumingServiceIndex: "123", - Destination: "https://wwwexampleIdp.com/saml", - IsPassive: "true", - }, - "saml:Issuer": [ - { - _: "http://exampleSp.com/saml", - $: { "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion" }, - }, - ], - "samlp:NameIDPolicy": [ - { - $: { - "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", - Format: "alternateIdentifier", - AllowCreate: "true", - }, - }, - ], - "samlp:RequestedAuthnContext": [ - { - $: { "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", Comparison: "exact" }, - "saml:AuthnContextClassRef": [ - { - _: "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport", - $: { "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion" }, - }, - ], - }, - ], - }, - }, - }, - { - name: "Uncompressed config #2", - config: { - issuer: "http://exampleSp.com/saml", - identifierFormat: "alternateIdentifier", - passive: true, - attributeConsumingServiceIndex: "123", - skipRequestCompression: true, - }, - result: { - "samlp:AuthnRequest": { - $: { - "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", - Version: "2.0", - ProtocolBinding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", - AssertionConsumerServiceURL: "http://localhost:3033/login", - AttributeConsumingServiceIndex: "123", - Destination: "https://wwwexampleIdp.com/saml", - IsPassive: "true", - }, - "saml:Issuer": [ - { - _: "http://exampleSp.com/saml", - $: { "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion" }, - }, - ], - "samlp:NameIDPolicy": [ - { - $: { - "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", - Format: "alternateIdentifier", - AllowCreate: "true", - }, - }, - ], - "samlp:RequestedAuthnContext": [ - { - $: { "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", Comparison: "exact" }, - "saml:AuthnContextClassRef": [ - { - _: "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport", - $: { "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion" }, - }, - ], - }, - ], - }, - }, - }, - { - name: "Config #3", - config: { - issuer: "http://exampleSp.com/saml", - identifierFormat: "alternateIdentifier", - passive: true, - attributeConsumingServiceIndex: "123", - skipRequestCompression: true, - disableRequestedAuthnContext: true, - forceAuthn: true, - }, - result: { - "samlp:AuthnRequest": { - $: { - "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", - Version: "2.0", - ProtocolBinding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", - AssertionConsumerServiceURL: "http://localhost:3033/login", - AttributeConsumingServiceIndex: "123", - Destination: "https://wwwexampleIdp.com/saml", - IsPassive: "true", - ForceAuthn: "true", - }, - "saml:Issuer": [ - { - _: "http://exampleSp.com/saml", - $: { "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion" }, - }, - ], - "samlp:NameIDPolicy": [ - { - $: { - "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", - Format: "alternateIdentifier", - AllowCreate: "true", - }, - }, - ], - }, - }, - }, - { - name: "Config with AuthnContext", - config: { - issuer: "http://exampleSp.com/saml", - identifierFormat: "alternateIdentifier", - passive: true, - attributeConsumingServiceIndex: "123", - authnContext: "myAuthnContext", - }, - result: { - "samlp:AuthnRequest": { - $: { - "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", - Version: "2.0", - ProtocolBinding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", - AssertionConsumerServiceURL: "http://localhost:3033/login", - AttributeConsumingServiceIndex: "123", - Destination: "https://wwwexampleIdp.com/saml", - IsPassive: "true", - }, - "saml:Issuer": [ - { - _: "http://exampleSp.com/saml", - $: { "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion" }, - }, - ], - "samlp:NameIDPolicy": [ - { - $: { - "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", - Format: "alternateIdentifier", - AllowCreate: "true", - }, - }, - ], - "samlp:RequestedAuthnContext": [ - { - $: { "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", Comparison: "exact" }, - "saml:AuthnContextClassRef": [ - { - _: "myAuthnContext", - $: { "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion" }, - }, - ], - }, - ], - }, - }, - }, - { - name: "Config with multiple AuthnContext", - config: { - issuer: "http://exampleSp.com/saml", - identifierFormat: "alternateIdentifier", - passive: true, - attributeConsumingServiceIndex: "123", - authnContext: ["myAuthnContext", "myAuthnContext2"], - }, - result: { - "samlp:AuthnRequest": { - $: { - "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", - Version: "2.0", - ProtocolBinding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", - AssertionConsumerServiceURL: "http://localhost:3033/login", - AttributeConsumingServiceIndex: "123", - Destination: "https://wwwexampleIdp.com/saml", - IsPassive: "true", - }, - "saml:Issuer": [ - { - _: "http://exampleSp.com/saml", - $: { "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion" }, - }, - ], - "samlp:NameIDPolicy": [ - { - $: { - "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", - Format: "alternateIdentifier", - AllowCreate: "true", - }, - }, - ], - "samlp:RequestedAuthnContext": [ - { - $: { "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", Comparison: "exact" }, - "saml:AuthnContextClassRef": [ - { - _: "myAuthnContext", - $: { "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion" }, - }, - { - _: "myAuthnContext2", - $: { "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion" }, - }, - ], - }, - ], - }, - }, - }, - { - name: "Config with ProviderName", - config: { - issuer: "http://exampleSp.com/saml", - identifierFormat: "alternateIdentifier", - providerName: "myProviderName", - }, - result: { - "samlp:AuthnRequest": { - $: { - "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", - Version: "2.0", - ProtocolBinding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", - ProviderName: "myProviderName", - AssertionConsumerServiceURL: "http://localhost:3033/login", - Destination: "https://wwwexampleIdp.com/saml", - }, - "saml:Issuer": [ - { - _: "http://exampleSp.com/saml", - $: { "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion" }, - }, - ], - "samlp:NameIDPolicy": [ - { - $: { - "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", - Format: "alternateIdentifier", - AllowCreate: "true", - }, - }, - ], - "samlp:RequestedAuthnContext": [ - { - $: { "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", Comparison: "exact" }, - "saml:AuthnContextClassRef": [ - { - _: "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport", - $: { "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion" }, - }, - ], - }, - ], + }; + + const samlObj = new SAML({ entryPoint: "foo", cert: FAKE_CERT }); + const logoutRequestPromise = samlObj.generateLogoutRequest({ + user: { + nameIDFormat: "foo", + nameID: "bar", }, - }, - }, - { - name: "Remove NameIDPolicy, AuthnRequest, and AssertionConsumerServiceURL Config", - config: { - identifierFormat: null, - disableRequestedAuthnContext: true, - disableRequestACSUrl: true, - }, - result: { - "samlp:AuthnRequest": { + } as RequestWithUser); + + logoutRequestPromise + .then(function (logoutRequest) { + parseString(logoutRequest, function (err, doc) { + try { + delete doc["samlp:LogoutRequest"]["$"]["ID"]; + delete doc["samlp:LogoutRequest"]["$"]["IssueInstant"]; + doc.should.eql(expectedRequest); + done(); + } catch (err2) { + done(err2); + } + }); + }) + .catch((err: Error) => { + done(err); + }); + } catch (err3) { + done(err3); + } + }); + + it("generateLogoutRequest adds the NameQualifier and SPNameQualifier to the saml request", function (done) { + try { + const expectedRequest = { + "samlp:LogoutRequest": { $: { "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", + "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion", + //ID: '_85ba0a112df1ffb57805', Version: "2.0", - ProtocolBinding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", - Destination: "https://wwwexampleIdp.com/saml", + //IssueInstant: '2014-05-29T03:32:23Z', + Destination: "foo", }, "saml:Issuer": [ { _: "onelogin_saml", $: { "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion" } }, ], - }, - }, - }, - { - name: "Config with full Scoping config", - config: { - issuer: "http://exampleSp.com/saml", - identifierFormat: "alternateIdentifier", - scoping: { - proxyCount: 2, - requesterId: "fooBarRequesterId", - idpList: [ - { - entries: [ - { - providerId: "myScopingProviderId", - name: "myScopingProviderName", - loc: "myScopingProviderLoc", - }, - ], - getComplete: "https://www.getcompleteidplist.com", - }, - ], - }, - }, - result: { - "samlp:AuthnRequest": { - $: { - AssertionConsumerServiceURL: "http://localhost:3033/login", - Destination: "https://wwwexampleIdp.com/saml", - ProtocolBinding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", - Version: "2.0", - "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", - }, - "saml:Issuer": [ - { - $: { - "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion", - }, - _: "http://exampleSp.com/saml", - }, - ], - "samlp:NameIDPolicy": [ - { - $: { - AllowCreate: "true", - Format: "alternateIdentifier", - "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", - }, - }, - ], - "samlp:RequestedAuthnContext": [ + "saml:NameID": [ { - $: { - Comparison: "exact", - "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", - }, - "saml:AuthnContextClassRef": [ - { - $: { - "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion", - }, - _: "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport", - }, - ], - }, - ], - "samlp:Scoping": [ - { - $: { - "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", - ProxyCount: "2", - }, - "samlp:IDPList": [ - { - $: { - "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", - }, - "samlp:GetComplete": ["https://www.getcompleteidplist.com"], - "samlp:IDPEntry": [ - { - $: { - Loc: "myScopingProviderLoc", - Name: "myScopingProviderName", - ProviderID: "myScopingProviderId", - "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", - }, - }, - ], - }, - ], - "samlp:RequesterID": ["fooBarRequesterId"], - }, - ], - }, - }, - }, - { - name: "Config with Scoping config without proxyCount and requesterId", - config: { - issuer: "http://exampleSp.com/saml", - identifierFormat: "alternateIdentifier", - scoping: { - idpList: [ - { - entries: [ - { - providerId: "myScopingProviderId", - name: "myScopingProviderName", - loc: "myScopingProviderLoc", - }, - ], - getComplete: "https://www.getcompleteidplist.com", - }, - ], - }, - }, - result: { - "samlp:AuthnRequest": { - $: { - AssertionConsumerServiceURL: "http://localhost:3033/login", - Destination: "https://wwwexampleIdp.com/saml", - ProtocolBinding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", - Version: "2.0", - "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", - }, - "saml:Issuer": [ - { - $: { - "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion", - }, - _: "http://exampleSp.com/saml", - }, - ], - "samlp:NameIDPolicy": [ - { - $: { - AllowCreate: "true", - Format: "alternateIdentifier", - "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", - }, - }, - ], - "samlp:RequestedAuthnContext": [ - { - $: { - Comparison: "exact", - "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", - }, - "saml:AuthnContextClassRef": [ - { - $: { - "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion", - }, - _: "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport", - }, - ], - }, - ], - "samlp:Scoping": [ - { - $: { - "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", - }, - "samlp:IDPList": [ - { - $: { - "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", - }, - "samlp:GetComplete": ["https://www.getcompleteidplist.com"], - "samlp:IDPEntry": [ - { - $: { - Loc: "myScopingProviderLoc", - Name: "myScopingProviderName", - ProviderID: "myScopingProviderId", - "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", - }, - }, - ], - }, - ], - }, - ], - }, - }, - }, - { - name: "Config with Scoping config without proxyCount, requesterId, getComplete", - config: { - issuer: "http://exampleSp.com/saml", - identifierFormat: "alternateIdentifier", - scoping: { - idpList: [ - { - entries: [ - { - providerId: "myScopingProviderId", - name: "myScopingProviderName", - loc: "myScopingProviderLoc", - }, - ], - }, - ], - }, - }, - result: { - "samlp:AuthnRequest": { - $: { - AssertionConsumerServiceURL: "http://localhost:3033/login", - Destination: "https://wwwexampleIdp.com/saml", - ProtocolBinding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", - Version: "2.0", - "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", - }, - "saml:Issuer": [ - { - $: { - "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion", - }, - _: "http://exampleSp.com/saml", - }, - ], - "samlp:NameIDPolicy": [ - { - $: { - AllowCreate: "true", - Format: "alternateIdentifier", - "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", - }, - }, - ], - "samlp:RequestedAuthnContext": [ - { - $: { - Comparison: "exact", - "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", - }, - "saml:AuthnContextClassRef": [ - { - $: { - "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion", - }, - _: "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport", - }, - ], - }, - ], - "samlp:Scoping": [ - { - $: { - "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", - }, - "samlp:IDPList": [ - { - $: { - "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", - }, - "samlp:IDPEntry": [ - { - $: { - Loc: "myScopingProviderLoc", - Name: "myScopingProviderName", - ProviderID: "myScopingProviderId", - "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", - }, - }, - ], - }, - ], - }, - ], - }, - }, - }, - { - name: - "Config with Scoping config without proxyCount, requesterId, idpList getComplete, entry name, entry loc", - config: { - issuer: "http://exampleSp.com/saml", - identifierFormat: "alternateIdentifier", - scoping: { - idpList: [ - { - entries: [ - { - providerId: "myScopingProviderId", - }, - ], - }, - ], - }, - }, - result: { - "samlp:AuthnRequest": { - $: { - AssertionConsumerServiceURL: "http://localhost:3033/login", - Destination: "https://wwwexampleIdp.com/saml", - ProtocolBinding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", - Version: "2.0", - "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", - }, - "saml:Issuer": [ - { - $: { - "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion", - }, - _: "http://exampleSp.com/saml", - }, - ], - "samlp:NameIDPolicy": [ - { - $: { - AllowCreate: "true", - Format: "alternateIdentifier", - "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", - }, - }, - ], - "samlp:RequestedAuthnContext": [ - { - $: { - Comparison: "exact", - "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", - }, - "saml:AuthnContextClassRef": [ - { - $: { - "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion", - }, - _: "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport", - }, - ], - }, - ], - "samlp:Scoping": [ - { - $: { - "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", - }, - "samlp:IDPList": [ - { - $: { - "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", - }, - "samlp:IDPEntry": [ - { - $: { - ProviderID: "myScopingProviderId", - "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", - }, - }, - ], - }, - ], - }, - ], - }, - }, - }, - { - name: "Config with Scoping and multiple IDPList entries", - config: { - issuer: "http://exampleSp.com/saml", - identifierFormat: "alternateIdentifier", - scoping: { - idpList: [ - { - entries: [ - { - providerId: "myScopingProviderId", - }, - { - providerId: "myOtherScopingProviderId", - }, - ], - }, - ], - }, - }, - result: { - "samlp:AuthnRequest": { - $: { - AssertionConsumerServiceURL: "http://localhost:3033/login", - Destination: "https://wwwexampleIdp.com/saml", - ProtocolBinding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", - Version: "2.0", - "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", - }, - "saml:Issuer": [ - { - $: { - "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion", - }, - _: "http://exampleSp.com/saml", - }, - ], - "samlp:NameIDPolicy": [ - { - $: { - AllowCreate: "true", - Format: "alternateIdentifier", - "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", - }, - }, - ], - "samlp:RequestedAuthnContext": [ - { - $: { - Comparison: "exact", - "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", - }, - "saml:AuthnContextClassRef": [ - { - $: { - "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion", - }, - _: "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport", - }, - ], - }, - ], - "samlp:Scoping": [ - { - $: { - "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", - }, - "samlp:IDPList": [ - { - $: { - "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", - }, - "samlp:IDPEntry": [ - { - $: { - ProviderID: "myScopingProviderId", - "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", - }, - }, - { - $: { - ProviderID: "myOtherScopingProviderId", - "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", - }, - }, - ], - }, - ], - }, - ], - }, - }, - }, - ]; - - let server: Server; - - function testForCheck(check: SAMLCheck) { - return function (done: Mocha.Done) { - const app = express(); - app.use(bodyParser.urlencoded({ extended: false })); - app.use(passport.initialize()); - const config = check.config; - config.callbackUrl = "http://localhost:3033/login"; - config.entryPoint = "https://wwwexampleIdp.com/saml"; - let profile: Profile; - passport.use( - new SamlStrategy( - config, - function (_profile: Profile | null | undefined, done: VerifiedCallback) { - if (_profile) { - profile = _profile; - done(null, profile); - } - } - ) - ); - - let userSerialized = false; - passport.serializeUser(function (user, done) { - userSerialized = true; - done(null, user); - }); - - app.get( - "/login", - passport.authenticate("saml", { - samlFallback: "login-request", - session: false, - } as AuthenticateOptions), - function (req, res) { - res.status(200).send("200 OK"); - } - ); - - app.use(function ( - err: Error | null, - req: express.Request, - res: express.Response, - next: express.NextFunction - ) { - // console.log( err.stack ); - res.status(500).send("500 Internal Server Error"); - }); - - server = app.listen(3033, function () { - const requestOpts = { - url: "http://localhost:3033/login", - method: "get", - followRedirect: false, - }; - - function helper(err: Error | null, samlRequest: Buffer) { - try { - should.not.exist(err); - parseString(samlRequest.toString(), function (err, doc) { - try { - should.not.exist(err); - delete doc["samlp:AuthnRequest"]["$"]["ID"]; - delete doc["samlp:AuthnRequest"]["$"]["IssueInstant"]; - doc.should.eql(check.result); - done(); - } catch (err2) { - done(err2); - } - }); - } catch (err2) { - done(err2); - } - } - - // TODO remove usage of request module - request(requestOpts, function (err: Error | null, response: any, body: any) { - try { - should.not.exist(err); - - let encodedSamlRequest; - if (check.config.authnRequestBinding === "HTTP-POST") { - response.statusCode.should.equal(200); - body.should.match(/[^]*/); - encodedSamlRequest = body.match(/ { + done(err); + }); + } catch (err3) { + done(err3); + } }); it("generateLogoutResponse", function (done) { @@ -1498,7 +161,7 @@ describe("passport-saml /", function () { }, }; - const samlObj = new SAML({ entryPoint: "foo" }); + const samlObj = new SAML({ entryPoint: "foo", cert: FAKE_CERT }); const logoutRequest = samlObj.generateLogoutResponse({} as express.Request, { ID: "quux" }); parseString(logoutRequest, function (err, doc) { try { @@ -1512,48 +175,56 @@ describe("passport-saml /", function () { }); }); - it("generateLogoutRequest", function (done) { - const expectedRequest = { - "samlp:LogoutRequest": { - $: { - "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", - "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion", - //ID: '_85ba0a112df1ffb57805', - Version: "2.0", - //IssueInstant: '2014-05-29T03:32:23Z', - Destination: "foo", + it("generateLogoutRequest with session index", function (done) { + try { + const expectedRequest = { + "samlp:LogoutRequest": { + $: { + "xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol", + "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion", + //ID: '_85ba0a112df1ffb57805', + Version: "2.0", + //IssueInstant: '2014-05-29T03:32:23Z', + Destination: "foo", + }, + "saml:Issuer": [ + { _: "onelogin_saml", $: { "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion" } }, + ], + "saml:NameID": [{ _: "bar", $: { Format: "foo" } }], + "saml2p:SessionIndex": [ + { _: "session-id", $: { "xmlns:saml2p": "urn:oasis:names:tc:SAML:2.0:protocol" } }, + ], }, - "saml:Issuer": [ - { _: "onelogin_saml", $: { "xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion" } }, - ], - "saml:NameID": [{ _: "bar", $: { Format: "foo" } }], - "saml2p:SessionIndex": [ - { _: "session-id", $: { "xmlns:saml2p": "urn:oasis:names:tc:SAML:2.0:protocol" } }, - ], - }, - }; - - const samlObj = new SAML({ entryPoint: "foo" }); - const logoutRequestPromise = samlObj.generateLogoutRequest({ - user: { - nameIDFormat: "foo", - nameID: "bar", - sessionIndex: "session-id", - }, - } as RequestWithUser); + }; - logoutRequestPromise.then(function (logoutRequest) { - parseString(logoutRequest, function (err, doc) { - try { - delete doc["samlp:LogoutRequest"]["$"]["ID"]; - delete doc["samlp:LogoutRequest"]["$"]["IssueInstant"]; - doc.should.eql(expectedRequest); - done(); - } catch (err2) { - done(err2); - } - }); - }); + const samlObj = new SAML({ entryPoint: "foo", cert: FAKE_CERT }); + const logoutRequestPromise = samlObj.generateLogoutRequest({ + user: { + nameIDFormat: "foo", + nameID: "bar", + sessionIndex: "session-id", + }, + } as RequestWithUser); + + logoutRequestPromise + .then(function (logoutRequest) { + parseString(logoutRequest, function (err, doc) { + try { + delete doc["samlp:LogoutRequest"]["$"]["ID"]; + delete doc["samlp:LogoutRequest"]["$"]["IssueInstant"]; + doc.should.eql(expectedRequest); + done(); + } catch (err2) { + done(err2); + } + }); + }) + .catch((err: Error) => { + done(err); + }); + } catch (err3) { + done(err3); + } }); it("generateLogoutRequest saves id and instant to cache", function (done) { @@ -1577,7 +248,7 @@ describe("passport-saml /", function () { }, }; - const samlObj = new SAML({ entryPoint: "foo" }); + const samlObj = new SAML({ entryPoint: "foo", cert: FAKE_CERT }); const cacheSaveSpy = sinon.spy(samlObj.cacheProvider, "saveAsync"); const logoutRequestPromise = samlObj.generateLogoutRequest({ user: { @@ -1607,7 +278,7 @@ describe("passport-saml /", function () { describe("generateServiceProviderMetadata tests /", function () { function testMetadata( - samlConfig: Partial, + samlConfig: SamlConfig, expectedMetadata: string, signingCert?: string ) { @@ -1627,11 +298,12 @@ describe("passport-saml /", function () { } it("config with callbackUrl and decryptionPvk should pass", function () { - const samlConfig: Partial = { + const samlConfig = { issuer: "http://example.serviceprovider.com", callbackUrl: "http://example.serviceprovider.com/saml/callback", identifierFormat: "urn:oasis:names:tc:SAML:2.0:nameid-format:transient", decryptionPvk: fs.readFileSync(__dirname + "/static/testshib encryption pvk.pem"), + cert: FAKE_CERT, }; const expectedMetadata = fs.readFileSync( __dirname + "/static/expected metadata.xml", @@ -1646,6 +318,7 @@ describe("passport-saml /", function () { issuer: "http://example.serviceprovider.com", callbackUrl: "http://example.serviceprovider.com/saml/callback", identifierFormat: "urn:oasis:names:tc:SAML:2.0:nameid-format:transient", + cert: FAKE_CERT, }; const expectedMetadata = fs.readFileSync( __dirname + "/static/expected metadata without key.xml", @@ -1663,6 +336,7 @@ describe("passport-saml /", function () { path: "/saml/callback", identifierFormat: "urn:oasis:names:tc:SAML:2.0:nameid-format:transient", decryptionPvk: fs.readFileSync(__dirname + "/static/testshib encryption pvk.pem"), + cert: FAKE_CERT, }; const expectedMetadata = fs.readFileSync( __dirname + "/static/expected metadata.xml", @@ -1679,6 +353,7 @@ describe("passport-saml /", function () { host: "example.serviceprovider.com", path: "/saml/callback", identifierFormat: "urn:oasis:names:tc:SAML:2.0:nameid-format:transient", + cert: FAKE_CERT, }; const expectedMetadata = fs.readFileSync( __dirname + "/static/expected metadata without key.xml", @@ -1697,6 +372,7 @@ describe("passport-saml /", function () { identifierFormat: "urn:oasis:names:tc:SAML:2.0:nameid-format:transient", decryptionPvk: fs.readFileSync(__dirname + "/static/testshib encryption pvk.pem"), privateCert: fs.readFileSync(__dirname + "/static/acme_tools_com.key"), + cert: FAKE_CERT, }; const expectedMetadata = fs.readFileSync( __dirname + "/static/expectedMetadataWithBothKeys.xml", @@ -1716,6 +392,7 @@ describe("passport-saml /", function () { identifierFormat: "urn:oasis:names:tc:SAML:2.0:nameid-format:transient", decryptionPvk: fs.readFileSync(__dirname + "/static/testshib encryption pvk.pem"), privateKey: fs.readFileSync(__dirname + "/static/acme_tools_com.key"), + cert: FAKE_CERT, }; const expectedMetadata = fs.readFileSync( __dirname + "/static/expectedMetadataWithBothKeys.xml", @@ -1734,6 +411,7 @@ describe("passport-saml /", function () { identifierFormat: "urn:oasis:names:tc:SAML:2.0:nameid-format:transient", decryptionPvk: fs.readFileSync(__dirname + "/static/testshib encryption pvk.pem"), logoutCallbackUrl: "http://example.serviceprovider.com/logout", + cert: FAKE_CERT, }; const samlObj = new SAML(samlConfig); @@ -1746,7 +424,7 @@ describe("passport-saml /", function () { metadata.should.containEql(samlConfig.logoutCallbackUrl); }); - it("#certToPEM should generate valid certificate", function (done) { + it("#certToPEM should generate valid certificate", function () { const samlConfig = { entryPoint: "https://app.onelogin.com/trust/saml2/http-post/sso/371755", cert: "-----BEGIN CERTIFICATE-----" + TEST_CERT + "-----END CERTIFICATE-----", @@ -1755,14 +433,21 @@ describe("passport-saml /", function () { const samlObj = new SAML(samlConfig); const certificate = samlObj.certToPEM(samlConfig.cert); - if (certificate.match(/BEGIN/g)!.length == 1 && certificate.match(/END/g)!.length == 1) { - done(); - } else { - done("Certificate should have only 1 BEGIN and 1 END block"); + if (!(certificate.match(/BEGIN/g)!.length == 1 && certificate.match(/END/g)!.length == 1)) { + throw Error("Certificate should have only 1 BEGIN and 1 END block"); } }); describe("validatePostResponse checks /", function () { + let fakeClock: sinon.SinonFakeTimers; + + afterEach(() => { + if (fakeClock) { + // If fakeClock is retored in a test, it will cause intermittant problems for other tests + fakeClock.restore(); + } + }); + it("response with junk content should explain the XML or base64 is not valid", async () => { const samlObj = new SAML({ cert: TEST_CERT }); await assert.rejects(samlObj.validatePostResponseAsync({ SAMLResponse: "BOOM" }), { @@ -1787,28 +472,30 @@ describe("passport-saml /", function () { 'https://idp.testshib.org/idp/shibboleth'; const base64xml = Buffer.from(xml).toString("base64"); const container = { SAMLResponse: base64xml }; - const samlObj = new SAML({}); + const samlObj = new SAML({ cert: FAKE_CERT }); await assert.rejects(samlObj.validatePostResponseAsync(container), { message: /Responder.*InvalidNameIDPolicy/, }); }); it("accept response with an attributeStatement element without attributeValue", async () => { - const fakeClock = sinon.useFakeTimers(Date.parse("2015-08-31T08:55:00+00:00")); + fakeClock = sinon.useFakeTimers(Date.parse("2015-08-31T08:55:00+00:00")); + const container = { SAMLResponse: fs .readFileSync(__dirname + "/static/response-with-uncomplete-attribute.xml") .toString("base64"), }; - const samlObj = new SAML({}); + + const signingCert = fs.readFileSync(__dirname + "/static/cert.pem", "utf-8"); + + const samlObj = new SAML({ cert: signingCert }); const { profile } = await samlObj.validatePostResponseAsync(container); profile!.issuer!.should.eql("https://evil-corp.com"); profile!.nameID!.should.eql("vincent.vega@evil-corp.com"); should(profile).have.property("evil-corp.egroupid").eql("vincent.vega@evil-corp.com"); // attributes without attributeValue child should be ignored should(profile).not.have.property("evilcorp.roles"); - - fakeClock.restore(); }); it("removes InResponseTo value if response validation fails", async () => { @@ -1830,24 +517,21 @@ describe("passport-saml /", function () { // Mock the SAML request being passed through Passport-SAML await samlObj.cacheProvider.saveAsync(requestId, new Date().toISOString()); - try { - await samlObj.validatePostResponseAsync(container); - should.not.exist(true); - } catch (err) { - should.exist(err); - err!.message!.should.match("Invalid signature"); - } - try { - await samlObj.validatePostResponseAsync(container); - should.not.exist(true); - } catch (err) { - should.exist(err); - err!.message!.should.match("InResponseTo is not valid"); - } + + await assert.rejects(samlObj.validatePostResponseAsync(container), { + message: /Invalid signature/, + }); + + await assert.rejects(samlObj.validatePostResponseAsync(container), { + message: /InResponseTo is not valid/, + }); }); describe("validatePostResponse xml signature checks /", function () { + const ALT_TEST_CERT = + "MIIEOTCCAyGgAwIBAgIJAKZgJdKdCdL6MA0GCSqGSIb3DQEBBQUAMHAxCzAJBgNVBAYTAkFVMREwDwYDVQQIEwhWaWN0b3JpYTESMBAGA1UEBxMJTWVsYm91cm5lMSEwHwYDVQQKExhUYWJjb3JwIEhvbGRpbmdzIExpbWl0ZWQxFzAVBgNVBAMTDnN0cy50YWIuY29tLmF1MB4XDTE3MDUzMDA4NTQwOFoXDTI3MDUyODA4NTQwOFowcDELMAkGA1UEBhMCQVUxETAPBgNVBAgTCFZpY3RvcmlhMRIwEAYDVQQHEwlNZWxib3VybmUxITAfBgNVBAoTGFRhYmNvcnAgSG9sZGluZ3MgTGltaXRlZDEXMBUGA1UEAxMOc3RzLnRhYi5jb20uYXUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQD0NuMcflq3rtupKYDf4a7lWmsXy66fYe9n8jB2DuLMakEJBlzn9j6B98IZftrilTq21VR7wUXROxG8BkN8IHY+l8X7lATmD28fFdZJj0c8Qk82eoq48faemth4fBMx2YrpnhU00jeXeP8dIIaJTPCHBTNgZltMMhphklN1YEPlzefJs3YD+Ryczy1JHbwETxt+BzO1JdjBe1fUTyl6KxAwWvtsNBURmQRYlDOk4GRgdkQnfxBuCpOMeOpV8wiBAi3h65Lab9C5avu4AJlA9e4qbOmWt6otQmgy5fiJVy6bH/d8uW7FJmSmePX9sqAWa9szhjdn36HHVQsfHC+IUEX7AgMBAAGjgdUwgdIwHQYDVR0OBBYEFN6z6cuxY7FTkg1S/lIjnS4x5ARWMIGiBgNVHSMEgZowgZeAFN6z6cuxY7FTkg1S/lIjnS4x5ARWoXSkcjBwMQswCQYDVQQGEwJBVTERMA8GA1UECBMIVmljdG9yaWExEjAQBgNVBAcTCU1lbGJvdXJuZTEhMB8GA1UEChMYVGFiY29ycCBIb2xkaW5ncyBMaW1pdGVkMRcwFQYDVQQDEw5zdHMudGFiLmNvbS5hdYIJAKZgJdKdCdL6MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAMi5HyvXgRa4+kKz3dk4SwAEXzeZRcsbeDJWVUxdb6a+JQxIoG7L9rSbd6yZvP/Xel5TrcwpCpl5eikzXB02/C0wZKWicNmDEBlOfw0Pc5ngdoh6ntxHIWm5QMlAfjR0dgTlojN4Msw2qk7cP1QEkV96e2BJUaqaNnM3zMvd7cfRjPNfbsbwl6hCCCAdwrALKYtBnjKVrCGPwO+xiw5mUJhZ1n6ZivTOdQEWbl26UO60J9ItiWP8VK0d0aChn326Ovt7qC4S3AgDlaJwcKe5Ifxl/UOWePGRwXj2UUuDWFhjtVmRntMmNZbe5yE8MkEvU+4/c6LqGwTCgDenRbK53Dg="; let fakeClock: sinon.SinonFakeTimers; + beforeEach(function () { fakeClock = sinon.useFakeTimers(Date.parse("2014-05-28T00:13:09Z")); }); @@ -1859,6 +543,32 @@ describe("passport-saml /", function () { entryPoint: "https://app.onelogin.com/trust/saml2/http-post/sso/371755", cert: TEST_CERT, }; + const noCertSamlConfig = { + entryPoint: "https://app.onelogin.com/trust/saml2/http-post/sso/371755", + } as SamlConfig; + const badCertSamlConfig = { + entryPoint: "https://app.onelogin.com/trust/saml2/http-post/sso/371755", + cert: BAD_TEST_CERT, + }; + + it("must have a cert to construct a SAML object", function () { + try { + new SAML(noCertSamlConfig); + } catch (err) { + should.exist(err); + err!.message!.should.match(/cert is required/); + } + }); + + it("must have a valid cert to construct a SAML object", function () { + try { + new SAML(badCertSamlConfig); + } catch (err) { + should.exist(err); + err!.message!.should.match(/cert is required/); + } + }); + it("valid onelogin xml document should validate", async () => { const xml = 'https://app.onelogin.com/saml/metadata/371755' + @@ -1873,26 +583,62 @@ describe("passport-saml /", function () { const { profile } = await samlObj.validatePostResponseAsync(container); profile!.nameID!.should.startWith("ploer"); }); - - it("onelogin xml document with altered assertion should fail", async () => { + it("valid onelogin xml document should fail with no cert", function () { const xml = 'https://app.onelogin.com/saml/metadata/371755' + 'https://app.onelogin.com/saml/metadata/371755DCnPTQYBb1hKspbe6fg1U3q8xn4=e0+aFomA0+JAY0f9tKqzIuqIVSSw7LiFUsneEDKPBWdiTz1sMdgr/2y1e9+rjaS2mRmCi/vSQLY3zTYz0hp6nJNU19+TWoXo9kHQyWT4KkeQL4Xs/gZ/AoKC20iHVKtpPps0IQ0Ml/qRoouSitt6Sf/WDz2LV/pWcH2hx5tv3xSw36hK2NQc7qw7r1mEXnvcjXReYo8rrVf7XHGGxNoRIEICUIi110uvsWemSXf0Z0dyb0FVYOWuSsQMDlzNpheADBifFO4UTfSEhFZvn8kVCGZUIwrbOhZ2d/+YEtgyuTg+qtslgfy4dwd4TvEcfuRzQTazeefprSFyiQckAXOjcw==' + TEST_CERT + - 'ben@subspacesw.com{audience}urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport' + + 'ploer@subspacesw.com{audience}urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport' + ""; const base64xml = Buffer.from(xml).toString("base64"); const container = { SAMLResponse: base64xml }; - const samlObj = new SAML(samlConfig); try { - await samlObj.validatePostResponseAsync(container); - should.not.exist(true); + const samlObj = new SAML(noCertSamlConfig); } catch (err) { should.exist(err); - err!.message!.should.match("Invalid signature"); + err!.message!.should.match(/cert is required/); } }); + it("onelogin xml document with altered NameID should fail", async () => { + const xml = + 'https://app.onelogin.com/saml/metadata/371755' + + 'https://app.onelogin.com/saml/metadata/371755DCnPTQYBb1hKspbe6fg1U3q8xn4=e0+aFomA0+JAY0f9tKqzIuqIVSSw7LiFUsneEDKPBWdiTz1sMdgr/2y1e9+rjaS2mRmCi/vSQLY3zTYz0hp6nJNU19+TWoXo9kHQyWT4KkeQL4Xs/gZ/AoKC20iHVKtpPps0IQ0Ml/qRoouSitt6Sf/WDz2LV/pWcH2hx5tv3xSw36hK2NQc7qw7r1mEXnvcjXReYo8rrVf7XHGGxNoRIEICUIi110uvsWemSXf0Z0dyb0FVYOWuSsQMDlzNpheADBifFO4UTfSEhFZvn8kVCGZUIwrbOhZ2d/+YEtgyuTg+qtslgfy4dwd4TvEcfuRzQTazeefprSFyiQckAXOjcw==' + + TEST_CERT + + 'aaaaaaaa@bbbbb.local{audience}urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport' + + ""; + const base64xml = Buffer.from(xml).toString("base64"); + const container = { SAMLResponse: base64xml }; + const samlObj = new SAML(samlConfig); + await assert.rejects(samlObj.validatePostResponseAsync(container), /Invalid signature/); + }); + + it("onelogin xml document with altered assertion name should fail", async () => { + const xml = + 'https://app.onelogin.com/saml/metadata/371755' + + 'https://app.onelogin.com/saml/metadata/371755DCnPTQYBb1hKspbe6fg1U3q8xn4=e0+aFomA0+JAY0f9tKqzIuqIVSSw7LiFUsneEDKPBWdiTz1sMdgr/2y1e9+rjaS2mRmCi/vSQLY3zTYz0hp6nJNU19+TWoXo9kHQyWT4KkeQL4Xs/gZ/AoKC20iHVKtpPps0IQ0Ml/qRoouSitt6Sf/WDz2LV/pWcH2hx5tv3xSw36hK2NQc7qw7r1mEXnvcjXReYo8rrVf7XHGGxNoRIEICUIi110uvsWemSXf0Z0dyb0FVYOWuSsQMDlzNpheADBifFO4UTfSEhFZvn8kVCGZUIwrbOhZ2d/+YEtgyuTg+qtslgfy4dwd4TvEcfuRzQTazeefprSFyiQckAXOjcw==' + + TEST_CERT + + 'aaaaaaaa@bbbbb.local{audience}urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport' + + ""; + const base64xml = Buffer.from(xml).toString("base64"); + const container = { SAMLResponse: base64xml }; + const samlObj = new SAML(samlConfig); + await assert.rejects(samlObj.validatePostResponseAsync(container), /Invalid signature/); + }); + + it("onelogin xml document with altered assertion should fail", async () => { + const xml = + 'https://app.onelogin.com/saml/metadata/371755' + + 'https://app.onelogin.com/saml/metadata/371755DCnPTQYBb1hKspbe6fg1U3q8xn4=e0+aFomA0+JAY0f9tKqzIuqIVSSw7LiFUsneEDKPBWdiTz1sMdgr/2y1e9+rjaS2mRmCi/vSQLY3zTYz0hp6nJNU19+TWoXo9kHQyWT4KkeQL4Xs/gZ/AoKC20iHVKtpPps0IQ0Ml/qRoouSitt6Sf/WDz2LV/pWcH2hx5tv3xSw36hK2NQc7qw7r1mEXnvcjXReYo8rrVf7XHGGxNoRIEICUIi110uvsWemSXf0Z0dyb0FVYOWuSsQMDlzNpheADBifFO4UTfSEhFZvn8kVCGZUIwrbOhZ2d/+YEtgyuTg+qtslgfy4dwd4TvEcfuRzQTazeefprSFyiQckAXOjcw==' + + TEST_CERT + + 'ben@subspacesw.com{audience}urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport' + + ""; + const base64xml = Buffer.from(xml).toString("base64"); + const container = { SAMLResponse: base64xml }; + const samlObj = new SAML(samlConfig); + await assert.rejects(samlObj.validatePostResponseAsync(container), /Invalid signature/); + }); + it("onelogin xml document with duplicate altered assertion should fail", async () => { const xml = 'https://app.onelogin.com/saml/metadata/371755' + @@ -1906,13 +652,7 @@ describe("passport-saml /", function () { const base64xml = Buffer.from(xml).toString("base64"); const container = { SAMLResponse: base64xml }; const samlObj = new SAML(samlConfig); - try { - await samlObj.validatePostResponseAsync(container); - should.not.exist(true); - } catch (err) { - should.exist(err); - err!.message!.should.match(/Invalid signature/); - } + await assert.rejects(samlObj.validatePostResponseAsync(container), /Invalid signature/); }); it("onelogin xml document with extra unsigned & altered assertion should fail", async () => { @@ -1926,13 +666,7 @@ describe("passport-saml /", function () { const base64xml = Buffer.from(xml).toString("base64"); const container = { SAMLResponse: base64xml }; const samlObj = new SAML(samlConfig); - try { - await samlObj.validatePostResponseAsync(container); - should.not.exist(true); - } catch (err) { - should.exist(err); - err!.message!.should.match(/Invalid signature/); - } + await assert.rejects(samlObj.validatePostResponseAsync(container), /Invalid signature/); }); it("onelogin xml document with extra nexted assertion should fail", async () => { @@ -1949,13 +683,7 @@ describe("passport-saml /", function () { const base64xml = Buffer.from(xml).toString("base64"); const container = { SAMLResponse: base64xml }; const samlObj = new SAML(samlConfig); - try { - await samlObj.validatePostResponseAsync(container); - should.not.exist(true); - } catch (err) { - should.exist(err); - err!.message!.should.match("Invalid signature"); - } + await assert.rejects(samlObj.validatePostResponseAsync(container), /Invalid signature/); }); it("multiple certs should validate with one of the certs", async () => { @@ -1977,7 +705,7 @@ describe("passport-saml /", function () { }); it("cert as a function should validate with the returned cert", async () => { - const functionCertSamlConfig: Partial = { + const functionCertSamlConfig: SamlConfig = { entryPoint: samlConfig.entryPoint, cert: function (callback) { callback(null, samlConfig.cert); @@ -1997,7 +725,7 @@ describe("passport-saml /", function () { }); it("cert as a function should validate with one of the returned certs", async () => { - const functionMultiCertSamlConfig: Partial = { + const functionMultiCertSamlConfig: SamlConfig = { entryPoint: samlConfig.entryPoint, cert: function (callback) { callback(null, [ALT_TEST_CERT, samlConfig.cert]); @@ -2018,7 +746,7 @@ describe("passport-saml /", function () { it("cert as a function should return an error if the cert function returns an error", async () => { const errorToReturn = new Error("test"); - const functionErrorCertSamlConfig: Partial = { + const functionErrorCertSamlConfig: SamlConfig = { entryPoint: samlConfig.entryPoint, cert: function (callback) { callback(errorToReturn); @@ -2048,8 +776,8 @@ describe("passport-saml /", function () { const spNameQualifier = "https://sp.example.org/sp/entity"; const format = "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"; const xml = - "" + - '' + + '' + + '' + "" + '' + "" + @@ -2067,9 +795,14 @@ describe("passport-saml /", function () { "" + "" + ""; - const base64xml = Buffer.from(xml).toString("base64"); + + const signingKey = fs.readFileSync(__dirname + "/static/key.pem"); + const signingCert = fs.readFileSync(__dirname + "/static/cert.pem", "utf-8"); + const signedXml = signXmlResponse(xml, { privateKey: signingKey }); + + const base64xml = Buffer.from(signedXml).toString("base64"); const container = { SAMLResponse: base64xml }; - const samlObj = new SAML({}); + const samlObj = new SAML({ cert: signingCert }); const { profile } = await samlObj.validatePostResponseAsync(container); const eptid = profile!["urn:oid:1.3.6.1.4.1.5923.1.1.1.10"] as any; const nameid = eptid["NameID"][0]; @@ -2078,27 +811,65 @@ describe("passport-saml /", function () { nameid.$.SPNameQualifier.should.equal(spNameQualifier); nameid.$.Format.should.equal(format); }); - }); - it("An undefined value given with an object should still be undefined", async () => { - const xml = - "" + - '' + - "" + - '' + - '' + - "" + - "" + - "" + - ""; - const base64xml = Buffer.from(xml).toString("base64"); - const container = { SAMLResponse: base64xml }; - const samlObj = new SAML({}); - const { profile } = await samlObj.validatePostResponseAsync(container); - should(profile!["attributeName"]).be.undefined(); + it("XML AttributeValue without signature should throw", async () => { + const nameid_opaque_string = "*******************************"; + const nameQualifier = "https://idp.example.org/idp/saml"; + const spNameQualifier = "https://sp.example.org/sp/entity"; + const format = "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"; + const xml = + "" + + '' + + "" + + '' + + "" + + '' + + nameid_opaque_string + + "" + + "" + + "" + + "" + + "" + + ""; + const base64xml = Buffer.from(xml).toString("base64"); + const container = { SAMLResponse: base64xml }; + const samlObj = new SAML({ cert: TEST_CERT }); + await assert.rejects(samlObj.validatePostResponseAsync(container), { + message: "Invalid signature", + }); + }); + + it("An undefined value given with an object should still be undefined", async () => { + const xml = + '' + + '' + + "" + + '' + + '' + + "" + + "" + + "" + + ""; + + const signingKey = fs.readFileSync(__dirname + "/static/key.pem"); + const signingCert = fs.readFileSync(__dirname + "/static/cert.pem", "utf-8"); + const signedXml = signXmlResponse(xml, { privateKey: signingKey }); + + const base64xml = Buffer.from(signedXml).toString("base64"); + const container = { SAMLResponse: base64xml }; + const samlObj = new SAML({ cert: signingCert }); + const { profile } = await samlObj.validatePostResponseAsync(container); + should(profile!["attributeName"]).be.undefined(); + }); }); }); @@ -2112,18 +883,20 @@ describe("passport-saml /", function () { }); it("acme_tools request signed with sha256", async () => { - const samlConfig: Partial = { + const samlConfig: SamlConfig = { entryPoint: "https://adfs.acme_tools.com/adfs/ls/", issuer: "acme_tools_com", callbackUrl: "https://relyingparty/adfs/postResponse", privateCert: fs.readFileSync(__dirname + "/static/acme_tools_com.key", "utf-8"), - authnContext: + authnContext: [ "http://schemas.microsoft.com/ws/2008/06/identity/authenticationmethod/password", + ], identifierFormat: null, signatureAlgorithm: "sha256", additionalParams: { customQueryStringParam: "CustomQueryStringParamValue", }, + cert: FAKE_CERT, }; const samlObj = new SAML(samlConfig); samlObj.generateUniqueID = function () { @@ -2138,18 +911,20 @@ describe("passport-saml /", function () { qry.customQueryStringParam?.should.match("CustomQueryStringParamValue"); }); it("acme_tools request signed with sha256 when using privateKey", async () => { - const samlConfig: Partial = { + const samlConfig: SamlConfig = { entryPoint: "https://adfs.acme_tools.com/adfs/ls/", issuer: "acme_tools_com", callbackUrl: "https://relyingparty/adfs/postResponse", privateKey: fs.readFileSync(__dirname + "/static/acme_tools_com.key", "utf-8"), - authnContext: + authnContext: [ "http://schemas.microsoft.com/ws/2008/06/identity/authenticationmethod/password", + ], identifierFormat: null, signatureAlgorithm: "sha256", additionalParams: { customQueryStringParam: "CustomQueryStringParamValue", }, + cert: FAKE_CERT, }; const samlObj = new SAML(samlConfig); samlObj.generateUniqueID = function () { @@ -2164,17 +939,19 @@ describe("passport-saml /", function () { qry.customQueryStringParam?.should.match("CustomQueryStringParamValue"); }); it("acme_tools request not signed if missing entry point", async () => { - const samlConfig: Partial = { + const samlConfig: SamlConfig = { entryPoint: "", issuer: "acme_tools_com", callbackUrl: "https://relyingparty/adfs/postResponse", privateCert: fs.readFileSync(__dirname + "/static/acme_tools_com.key", "utf-8"), - authnContext: + authnContext: [ "http://schemas.microsoft.com/ws/2008/06/identity/authenticationmethod/password", + ], signatureAlgorithm: "sha256", additionalParams: { customQueryStringParam: "CustomQueryStringParamValue", }, + cert: FAKE_CERT, }; const samlObj = new SAML(samlConfig); samlObj.generateUniqueID = function () { @@ -2184,21 +961,23 @@ describe("passport-saml /", function () { const request = 'onelogin_samlurn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport'; await assert.rejects(samlObj.requestToUrlAsync(request, null, "authorize", {}), { - message: '"entryPoint" config parameter is required for signed messages', + message: "entryPoint is required", }); }); it("acme_tools request not signed if missing entry point when using privateKey", async () => { - const samlConfig: Partial = { + const samlConfig: SamlConfig = { entryPoint: "", issuer: "acme_tools_com", callbackUrl: "https://relyingparty/adfs/postResponse", privateKey: fs.readFileSync(__dirname + "/static/acme_tools_com.key", "utf-8"), - authnContext: + authnContext: [ "http://schemas.microsoft.com/ws/2008/06/identity/authenticationmethod/password", + ], signatureAlgorithm: "sha256", additionalParams: { customQueryStringParam: "CustomQueryStringParamValue", }, + cert: FAKE_CERT, }; const samlObj = new SAML(samlConfig); samlObj.generateUniqueID = function () { @@ -2208,22 +987,23 @@ describe("passport-saml /", function () { const request = 'onelogin_samlurn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport'; await assert.rejects(samlObj.requestToUrlAsync(request, null, "authorize", {}), { - message: '"entryPoint" config parameter is required for signed messages', + message: "entryPoint is required", }); }); it("acme_tools request signed with sha1", async () => { - const samlConfig: Partial = { + const samlConfig: SamlConfig = { entryPoint: "https://adfs.acme_tools.com/adfs/ls/", issuer: "acme_tools_com", callbackUrl: "https://relyingparty/adfs/postResponse", - privateCert: fs.readFileSync(__dirname + "/static/acme_tools_com.key", "utf-8"), - authnContext: + privateKey: fs.readFileSync(__dirname + "/static/acme_tools_com.key", "utf-8"), + authnContext: [ "http://schemas.microsoft.com/ws/2008/06/identity/authenticationmethod/password", - identifierFormat: null, + ], signatureAlgorithm: "sha1", additionalParams: { customQueryStringParam: "CustomQueryStringParamValue", }, + cert: fs.readFileSync(__dirname + "/static/acme_tools_com.cert", "utf-8"), }; const samlObj = new SAML(samlConfig); samlObj.generateUniqueID = function () { @@ -2233,23 +1013,25 @@ describe("passport-saml /", function () { const qry = querystring.parse(url.parse(authorizeUrl).query || ""); qry.SigAlg?.should.match("http://www.w3.org/2000/09/xmldsig#rsa-sha1"); qry.Signature?.should.match( - "MeFo+LjufxP5A+sCRwzR/YH/RV6W14aYSFjUdie62JxkI6hDcVhoSZQUJ3wtWMhL59gJj05tTFnXAZRqUQVsavyy41cmUZVeCsat0gaHBQOILXpp9deB0iSJt1EVQTOJkVx8uu2/WYu/bBiH7w2bpwuCf1gJhlqZb/ca3B6yjHSMjnnVfc2LbNPWHpE5464lrs79VjDXf9GQWfrBr95dh3P51IAb7C+77KDWQUl9WfZfyyuEgS83vyZ0UGOxT4AObJ6NOcLs8+iidDdWJJkBaKQev6U+AghCjLQUYOrflivLIIyqATKu2q9PbOse6Phmnxok50+broXSG23+e+742Q==" + "GrRJDEmwB74g4CjGS4gjDhzR9Mnpo7S1DJyhaLxVRA97tumydsrJNpqbKDAsepnoFcZeS+mWn3hZSE/blsyCUJn5+010MsgQ8KWmRgaFEYPOfbBa2KK1atXNN8zcSdNukpd/rI17fB1HDeCPG8t+rtaE/QviL7DpsF6hGXbWSTi8eSRSNmLKFIntvS81CNiZdub1NI8hSd/rU873u7RfWnv6ChPltCs41Znj49ke6UJaSrHX7kNlnbzqw5WLJp73d3/yT2Yew+xpGDKAGrjYtbQEY0zZ8chfKWyTobLV9vNF1DCFRDeZbLrCzL29elo/aZ8BuIEdTNv/IOZoOMZxRA==" ); qry.customQueryStringParam?.should.match("CustomQueryStringParamValue"); }); it("acme_tools request signed with sha1 when using privateKey", async () => { - const samlConfig: Partial = { + const samlConfig: SamlConfig = { entryPoint: "https://adfs.acme_tools.com/adfs/ls/", issuer: "acme_tools_com", callbackUrl: "https://relyingparty/adfs/postResponse", privateKey: fs.readFileSync(__dirname + "/static/acme_tools_com.key", "utf-8"), - authnContext: + authnContext: [ "http://schemas.microsoft.com/ws/2008/06/identity/authenticationmethod/password", + ], identifierFormat: null, signatureAlgorithm: "sha1", additionalParams: { customQueryStringParam: "CustomQueryStringParamValue", }, + cert: FAKE_CERT, }; const samlObj = new SAML(samlConfig); samlObj.generateUniqueID = function () { @@ -2269,6 +1051,7 @@ describe("passport-saml /", function () { it("should not pass any additional params by default", function () { const samlConfig = { entryPoint: "https://app.onelogin.com/trust/saml2/http-post/sso/371755", + cert: FAKE_CERT, }; const samlObj = new SAML(samlConfig); @@ -2281,6 +1064,7 @@ describe("passport-saml /", function () { it("should not pass any additional params by default apart from the RelayState in request query", function () { const samlConfig = { entryPoint: "https://app.onelogin.com/trust/saml2/http-post/sso/371755", + cert: FAKE_CERT, }; const samlObj = new SAML(samlConfig); @@ -2298,6 +1082,7 @@ describe("passport-saml /", function () { it("should not pass any additional params by default apart from the RelayState in request body", function () { const samlConfig = { entryPoint: "https://app.onelogin.com/trust/saml2/http-post/sso/371755", + cert: FAKE_CERT, }; const samlObj = new SAML(samlConfig); @@ -2318,6 +1103,7 @@ describe("passport-saml /", function () { additionalParams: { queryParam: "queryParamValue", }, + cert: FAKE_CERT, }; const samlObj = new SAML(samlConfig); @@ -2334,6 +1120,7 @@ describe("passport-saml /", function () { additionalAuthorizeParams: { queryParam: "queryParamValue", }, + cert: FAKE_CERT, }; const samlObj = new SAML(samlConfig); @@ -2354,6 +1141,7 @@ describe("passport-saml /", function () { additionalLogoutParams: { queryParam: "queryParamValue", }, + cert: FAKE_CERT, }; const samlObj = new SAML(samlConfig); @@ -2380,6 +1168,7 @@ describe("passport-saml /", function () { additionalLogoutParams: { queryParam2: "queryParamValueLogout", }, + cert: FAKE_CERT, }; const samlObj = new SAML(samlConfig); @@ -2413,6 +1202,7 @@ describe("passport-saml /", function () { additionalLogoutParams: { queryParam2: "queryParamValueLogout", }, + cert: FAKE_CERT, }; const samlObj = new SAML(samlConfig); const options = { @@ -2458,6 +1248,7 @@ describe("passport-saml /", function () { additionalLogoutParams: { queryParam: "queryParamValueLogout", }, + cert: FAKE_CERT, }; const samlObj = new SAML(samlConfig); @@ -2485,6 +1276,7 @@ describe("passport-saml /", function () { additionalLogoutParams: { queryParam: "queryParamValueLogout", }, + cert: FAKE_CERT, }; const samlObj = new SAML(samlConfig); const options = { @@ -2510,24 +1302,36 @@ describe("passport-saml /", function () { additionalLogoutParams.should.containEql({ queryParam: "queryParamRuntimeValue" }); }); - it("should check the value of the option `RACComparison`", function () { - const samlObjBadComparisonType = new SAML({ RACComparison: "bad_value" as any }); + it("should check the value of the option `racComparison`", function () { + assert.throws( + () => { + new SAML({ + racComparison: "bad_value" as RacComparision, + cert: FAKE_CERT, + }).options; + }, + { message: "racComparison must be one of ['exact', 'minimum', 'maximum', 'better']" } + ); + + const samlObjBadComparisonType = new SAML({ + cert: FAKE_CERT, + }); should.equal( - samlObjBadComparisonType.options.RACComparison, + samlObjBadComparisonType.options.racComparison, "exact", - "the default value of the option `RACComparison` must be exact" + "the default value of the option `racComparison` must be exact" ); - const validComparisonTypes: RACComparision[] = ["exact", "minimum", "maximum", "better"]; + const validComparisonTypes: RacComparision[] = ["exact", "minimum", "maximum", "better"]; let samlObjValidComparisonType: SAML; - validComparisonTypes.forEach(function (RACComparison) { - samlObjValidComparisonType = new SAML({ RACComparison: RACComparison }); - should.equal(samlObjValidComparisonType.options.RACComparison, RACComparison); + validComparisonTypes.forEach(function (racComparison) { + samlObjValidComparisonType = new SAML({ racComparison, cert: FAKE_CERT }); + should.equal(samlObjValidComparisonType.options.racComparison, racComparison); }); }); }); - describe("InResponseTo validation checks /", function () { + describe("InResponseTo validation checks /", () => { let fakeClock: sinon.SinonFakeTimers; afterEach(function () { @@ -2702,20 +1506,21 @@ describe("passport-saml /", function () { should.not.exist(value); }); - describe("InResponseTo server cache expiration tests /", function () { + describe("InResponseTo server cache expiration tests /", () => { it("should expire a cached request id after the time", async () => { const requestId = "_dfab47d5d46374cd4b71"; const samlConfig = { validateInResponseTo: true, requestIdExpirationPeriodMs: 100, + cert: FAKE_CERT, }; const samlObj = new SAML(samlConfig); // Mock the SAML request being passed through Passport-SAML await samlObj.cacheProvider.saveAsync(requestId, new Date().toISOString()); - await new Promise((resolve) => setTimeout(resolve, 300)); + await (() => new Promise((resolve) => setTimeout(resolve, 300)))(); const value = await samlObj.cacheProvider.getAsync(requestId); should.not.exist(value); }); @@ -2728,6 +1533,7 @@ describe("passport-saml /", function () { const samlConfig = { validateInResponseTo: true, requestIdExpirationPeriodMs: 100, + cert: FAKE_CERT, }; const samlObj = new SAML(samlConfig); @@ -2744,7 +1550,7 @@ describe("passport-saml /", function () { should.not.exist(value2); const value3 = await samlObj.cacheProvider.getAsync(requestId); should.exist(value3); - await new Promise((resolve) => setTimeout(resolve, 300)); + await (() => new Promise((resolve) => setTimeout(resolve, 300)))(); const value4 = await samlObj.cacheProvider.getAsync(requestId); should.not.exist(value4); }); @@ -2886,13 +1692,33 @@ describe("passport-saml /", function () { profile!.nameID!.should.startWith("ploer"); }); - it("onelogin xml document with audience and no AudienceRestriction should not pass", async function () { - const xml = - 'https://app.onelogin.com/saml/metadata/371755' + - 'https://app.onelogin.com/saml/metadata/371755DCnPTQYBb1hKspbe6fg1U3q8xn4=e0+aFomA0+JAY0f9tKqzIuqIVSSw7LiFUsneEDKPBWdiTz1sMdgr/2y1e9+rjaS2mRmCi/vSQLY3zTYz0hp6nJNU19+TWoXo9kHQyWT4KkeQL4Xs/gZ/AoKC20iHVKtpPps0IQ0Ml/qRoouSitt6Sf/WDz2LV/pWcH2hx5tv3xSw36hK2NQc7qw7r1mEXnvcjXReYo8rrVf7XHGGxNoRIEICUIi110uvsWemSXf0Z0dyb0FVYOWuSsQMDlzNpheADBifFO4UTfSEhFZvn8kVCGZUIwrbOhZ2d/+YEtgyuTg+qtslgfy4dwd4TvEcfuRzQTazeefprSFyiQckAXOjcw==' + - TEST_CERT + - 'ploer@subspacesw.comurn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport' + - ""; + it("onelogin xml document with audience and no AudienceRestriction should not pass", async () => { + const signingCert = fs.readFileSync(__dirname + "/static/cert.pem", "utf-8"); + const xml = ` + https://app.onelogin.com/saml/metadata/371755 + + + EiaOqK9GBBENUFaN2AVlYOvlq8E=vUf14oqiSWa2xhU1hZBEF3Z9JcYsVzWq+B1vXg0vQfN8GEfmooRogtA3oLs2SKhtpcVwEPuIVf6hRnEL713STMBbYxhnk/om+zMor82bQgn+eR/n3g3AWFPbLGxHbYXK06X47Vo+RRm5H8xVb9FiECXYs6CUCtVksAnitDp0pFgB8G5FKx2OwKALg1LNsKItkzWfI7yaQPKyywFwGgDqXVJYiD1v1HKb3JEvpiL96vVOYSI1+7j/Jy2brYJfs4ADnuAKEXVDgYdtaIQrGa+0x2W9KInCWgR+H40nBTHecE5NGeE01/s0is8nVbmSVrdpBpw44t2xcnp+TzozTGTm2g== +MIIDtTCCAp2gAwIBAgIJAKg4VeVcIDz1MA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMTUwODEzMDE1NDIwWhcNMTUwOTEyMDE1NDIwWjBFMQswCQYDVQQGEwJVUzETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxG3ouM7U+fXbJt69X1H6d4UNg/uRr06pFuU9RkfIwNC+yaXyptqB3ynXKsL7BFt4DCd0fflRvJAx3feJIDp16wN9GDVHcufWMYPhh2j5HcTW/j9JoIJzGhJyvO00YKBt+hHy83iN1SdChKv5y0iSyiPP5GnqFw+ayyHoM6hSO0PqBou1Xb0ZSIE+DHosBnvVna5w2AiPY4xrJl9yZHZ4Q7DfMiYTgstjETio4bX+6oLiBnYktn7DjdEslqhffVme4PuBxNojI+uCeg/sn4QVLd/iogMJfDWNuLD8326Mi/FE9cCRvFlvAiMSaebMI3zPaySsxTK7Zgj5TpEbmbHI9wIDAQABo4GnMIGkMB0GA1UdDgQWBBSVGgvoW4MhMuzBGce29PY8vSzHFzB1BgNVHSMEbjBsgBSVGgvoW4MhMuzBGce29PY8vSzHF6FJpEcwRTELMAkGA1UEBhMCVVMxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAKg4VeVcIDz1MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAJu1rqs+anD74dbdwgd3CnqnQsQDJiEXmBhG2leaGt3ve9b/9gKaJg2pyb2NyppDe1uLqh6nNXDuzg1oNZrPz5pJL/eCXPl7FhxhMUi04TtLf8LeNTCIWYZiFuO4pmhohHcv8kRvYR1+6SkLTC8j/TZerm7qvesSiTQFNapa1eNdVQ8nFwVkEtWl+JzKEM1BlRcn42sjJkijeFp7DpI7pU+PnYeiaXpRv5pJo8ogM1iFxN+SnfEs0EuQ7fhKIG9aHKi7bKZ7L6SyX7MDIGLeulEU6lf5D9BfXNmcMambiS0pXhL2QXajt96UBq8FT2KNXY8XNtR4y6MyyCzhaiZZcc8= + + + + + https://app.onelogin.com/saml/metadata/371755 + + ploer@subspacesw.com + + + + + + + + urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport + + + +`; const base64xml = Buffer.from(xml).toString("base64"); const container = { SAMLResponse: base64xml }; @@ -2900,6 +1726,7 @@ describe("passport-saml /", function () { entryPoint: "https://app.onelogin.com/trust/saml2/http-post/sso/371755", audience: "http://sp.example.com", acceptedClockSkewMs: -1, + cert: signingCert, }; const samlObj = new SAML(samlConfig); await assert.rejects(samlObj.validatePostResponseAsync(container), { @@ -2908,12 +1735,36 @@ describe("passport-saml /", function () { }); it("onelogin xml document with audience not matching AudienceRestriction should not pass", async () => { - const xml = - 'https://app.onelogin.com/saml/metadata/371755' + - 'https://app.onelogin.com/saml/metadata/371755DCnPTQYBb1hKspbe6fg1U3q8xn4=e0+aFomA0+JAY0f9tKqzIuqIVSSw7LiFUsneEDKPBWdiTz1sMdgr/2y1e9+rjaS2mRmCi/vSQLY3zTYz0hp6nJNU19+TWoXo9kHQyWT4KkeQL4Xs/gZ/AoKC20iHVKtpPps0IQ0Ml/qRoouSitt6Sf/WDz2LV/pWcH2hx5tv3xSw36hK2NQc7qw7r1mEXnvcjXReYo8rrVf7XHGGxNoRIEICUIi110uvsWemSXf0Z0dyb0FVYOWuSsQMDlzNpheADBifFO4UTfSEhFZvn8kVCGZUIwrbOhZ2d/+YEtgyuTg+qtslgfy4dwd4TvEcfuRzQTazeefprSFyiQckAXOjcw==' + - TEST_CERT + - 'ploer@subspacesw.com{audience}urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport' + - ""; + const signingCert = fs.readFileSync(__dirname + "/static/cert.pem", "utf-8"); + const xml = ` + https://app.onelogin.com/saml/metadata/371755 + + + SYyNspaWBrl3SgQGlt8RysQRfXI=km4aqMCXkupbpp/YR2+dOD2BbnGKO1MDVRMPKSzSx3BwsXhBBYlFNx1ht46zZDSQF60iAHd/vHGcJV8V7QNCeuIgfmTNkh2jtcF5ghRNfmvgpLRwRt1dT/UqApzo8kdRKXcUu0yxziPKFoE6EEvF/NR+YV/aAngEH3dCbOsN1u56zBOa4DZ7EMoWgwmPodaHOgNy4xazv5+Cb+mQM8YC1060EvipIrRU28BWIb3teUlfHL3L8AMlyCqjkw21dkbVOcHNy080oWW+MkjMGbt6r3y2iwpDfJMk5R63T5fXFDIStbVD7ss2BzqGccBykHomm/hU9GYWoO+0kW/reLiXiw== +MIIDtTCCAp2gAwIBAgIJAKg4VeVcIDz1MA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMTUwODEzMDE1NDIwWhcNMTUwOTEyMDE1NDIwWjBFMQswCQYDVQQGEwJVUzETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxG3ouM7U+fXbJt69X1H6d4UNg/uRr06pFuU9RkfIwNC+yaXyptqB3ynXKsL7BFt4DCd0fflRvJAx3feJIDp16wN9GDVHcufWMYPhh2j5HcTW/j9JoIJzGhJyvO00YKBt+hHy83iN1SdChKv5y0iSyiPP5GnqFw+ayyHoM6hSO0PqBou1Xb0ZSIE+DHosBnvVna5w2AiPY4xrJl9yZHZ4Q7DfMiYTgstjETio4bX+6oLiBnYktn7DjdEslqhffVme4PuBxNojI+uCeg/sn4QVLd/iogMJfDWNuLD8326Mi/FE9cCRvFlvAiMSaebMI3zPaySsxTK7Zgj5TpEbmbHI9wIDAQABo4GnMIGkMB0GA1UdDgQWBBSVGgvoW4MhMuzBGce29PY8vSzHFzB1BgNVHSMEbjBsgBSVGgvoW4MhMuzBGce29PY8vSzHF6FJpEcwRTELMAkGA1UEBhMCVVMxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAKg4VeVcIDz1MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAJu1rqs+anD74dbdwgd3CnqnQsQDJiEXmBhG2leaGt3ve9b/9gKaJg2pyb2NyppDe1uLqh6nNXDuzg1oNZrPz5pJL/eCXPl7FhxhMUi04TtLf8LeNTCIWYZiFuO4pmhohHcv8kRvYR1+6SkLTC8j/TZerm7qvesSiTQFNapa1eNdVQ8nFwVkEtWl+JzKEM1BlRcn42sjJkijeFp7DpI7pU+PnYeiaXpRv5pJo8ogM1iFxN+SnfEs0EuQ7fhKIG9aHKi7bKZ7L6SyX7MDIGLeulEU6lf5D9BfXNmcMambiS0pXhL2QXajt96UBq8FT2KNXY8XNtR4y6MyyCzhaiZZcc8= + + + + + https://app.onelogin.com/saml/metadata/371755 + + ploer@subspacesw.com + + + + + + + {audience} + + + + + urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport + + + +`; const base64xml = Buffer.from(xml).toString("base64"); const container = { SAMLResponse: base64xml }; @@ -2921,6 +1772,7 @@ describe("passport-saml /", function () { entryPoint: "https://app.onelogin.com/trust/saml2/http-post/sso/371755", audience: "http://sp.example.com", acceptedClockSkewMs: -1, + cert: signingCert, }; const samlObj = new SAML(samlConfig); await assert.rejects(samlObj.validatePostResponseAsync(container), { @@ -2929,12 +1781,36 @@ describe("passport-saml /", function () { }); it("onelogin xml document with audience matching AudienceRestriction should pass", async () => { - const xml = - 'https://app.onelogin.com/saml/metadata/371755' + - 'https://app.onelogin.com/saml/metadata/371755DCnPTQYBb1hKspbe6fg1U3q8xn4=e0+aFomA0+JAY0f9tKqzIuqIVSSw7LiFUsneEDKPBWdiTz1sMdgr/2y1e9+rjaS2mRmCi/vSQLY3zTYz0hp6nJNU19+TWoXo9kHQyWT4KkeQL4Xs/gZ/AoKC20iHVKtpPps0IQ0Ml/qRoouSitt6Sf/WDz2LV/pWcH2hx5tv3xSw36hK2NQc7qw7r1mEXnvcjXReYo8rrVf7XHGGxNoRIEICUIi110uvsWemSXf0Z0dyb0FVYOWuSsQMDlzNpheADBifFO4UTfSEhFZvn8kVCGZUIwrbOhZ2d/+YEtgyuTg+qtslgfy4dwd4TvEcfuRzQTazeefprSFyiQckAXOjcw==' + - TEST_CERT + - 'ploer@subspacesw.comhttp://sp.example.comurn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport' + - ""; + const signingCert = fs.readFileSync(__dirname + "/static/cert.pem", "utf-8"); + const xml = ` + https://app.onelogin.com/saml/metadata/371755 + + + SWMNMbWptW3RzRXBEHv4kvu3rbU=LiyXwB09CavCEDR3SkjRzuoUn2Mruk8pBfWyN1K7zefjbtahezIljOYIIumZAsC8hrVndeG7Pk2rE6pL04vcRcASmPlmyytb0yYwyjmhEhTR6nmfBMS0a9mwvQE5/4+TecVW4yWUcMd7m12EhWz8+RcmHRnKWSCsVDxlF0zPUHgJc8n6Yr489mNcRFGKpaWtcH9vEvf689jxNgdqXjS5SvSBJClZd6ir0KFH799etbY5TORx3p0zR+okq7ZP4A9XVcnHWZw4e3KBJ04xQ31fDcr2Cgi3qkLEaaj5HkKAE4PWZ/5G75VHFM3xAZ9rsVL5mwQAJRXFgNbSYCWOUZ4NCg== +MIIDtTCCAp2gAwIBAgIJAKg4VeVcIDz1MA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMTUwODEzMDE1NDIwWhcNMTUwOTEyMDE1NDIwWjBFMQswCQYDVQQGEwJVUzETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxG3ouM7U+fXbJt69X1H6d4UNg/uRr06pFuU9RkfIwNC+yaXyptqB3ynXKsL7BFt4DCd0fflRvJAx3feJIDp16wN9GDVHcufWMYPhh2j5HcTW/j9JoIJzGhJyvO00YKBt+hHy83iN1SdChKv5y0iSyiPP5GnqFw+ayyHoM6hSO0PqBou1Xb0ZSIE+DHosBnvVna5w2AiPY4xrJl9yZHZ4Q7DfMiYTgstjETio4bX+6oLiBnYktn7DjdEslqhffVme4PuBxNojI+uCeg/sn4QVLd/iogMJfDWNuLD8326Mi/FE9cCRvFlvAiMSaebMI3zPaySsxTK7Zgj5TpEbmbHI9wIDAQABo4GnMIGkMB0GA1UdDgQWBBSVGgvoW4MhMuzBGce29PY8vSzHFzB1BgNVHSMEbjBsgBSVGgvoW4MhMuzBGce29PY8vSzHF6FJpEcwRTELMAkGA1UEBhMCVVMxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAKg4VeVcIDz1MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAJu1rqs+anD74dbdwgd3CnqnQsQDJiEXmBhG2leaGt3ve9b/9gKaJg2pyb2NyppDe1uLqh6nNXDuzg1oNZrPz5pJL/eCXPl7FhxhMUi04TtLf8LeNTCIWYZiFuO4pmhohHcv8kRvYR1+6SkLTC8j/TZerm7qvesSiTQFNapa1eNdVQ8nFwVkEtWl+JzKEM1BlRcn42sjJkijeFp7DpI7pU+PnYeiaXpRv5pJo8ogM1iFxN+SnfEs0EuQ7fhKIG9aHKi7bKZ7L6SyX7MDIGLeulEU6lf5D9BfXNmcMambiS0pXhL2QXajt96UBq8FT2KNXY8XNtR4y6MyyCzhaiZZcc8= + + + + + https://app.onelogin.com/saml/metadata/371755 + + ploer@subspacesw.com + + + + + + + http://sp.example.com + + + + + urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport + + + +`; const base64xml = Buffer.from(xml).toString("base64"); const container = { SAMLResponse: base64xml }; @@ -2942,6 +1818,7 @@ describe("passport-saml /", function () { entryPoint: "https://app.onelogin.com/trust/saml2/http-post/sso/371755", audience: "http://sp.example.com", acceptedClockSkewMs: -1, + cert: signingCert, }; const samlObj = new SAML(samlConfig); @@ -3074,6 +1951,7 @@ describe("passport-saml /", function () { "jF3SNJh0SmHoT62vc+cJqPxMDP6E7Q1nZxsEyaAkKr2H4dSM4SlRm0VB+bS+jXsz\n" + "PCiRGSm8eupuxfix05LMMreo4mC7e3Ir4JhdCsXxAMZIvbNyXcvUMA==\n" + "-----END CERTIFICATE-----\n", + cert: FAKE_CERT, }); const request = 'onelogin_samlurn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport'; @@ -3111,6 +1989,7 @@ describe("passport-saml /", function () { "jF3SNJh0SmHoT62vc+cJqPxMDP6E7Q1nZxsEyaAkKr2H4dSM4SlRm0VB+bS+jXsz\n" + "PCiRGSm8eupuxfix05LMMreo4mC7e3Ir4JhdCsXxAMZIvbNyXcvUMA==\n" + "-----END CERTIFICATE-----\n", + cert: FAKE_CERT, }); const request = 'onelogin_samlurn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport'; @@ -3123,6 +2002,7 @@ describe("passport-saml /", function () { describe("validateRedirect()", function () { describe("idp slo", function () { let samlObj: SAML; + let fakeClock: sinon.SinonFakeTimers; beforeEach(function () { samlObj = new SAML({ cert: fs.readFileSync(__dirname + "/static/acme_tools_com.cert", "ascii"), @@ -3132,10 +2012,10 @@ describe("validateRedirect()", function () { {}, JSON.parse(fs.readFileSync(__dirname + "/static/idp_slo_redirect.json", "utf8")) ); - this.clock = sinon.useFakeTimers(Date.parse("2018-04-11T14:08:00Z")); + fakeClock = sinon.useFakeTimers(Date.parse("2018-04-11T14:08:00Z")); }); afterEach(function () { - this.clock.restore(); + fakeClock.restore(); }); it("errors if bad xml", async function () { const body = { @@ -3154,7 +2034,9 @@ describe("validateRedirect()", function () { ); }); it("errors if request has expired", async function () { - this.clock.restore(); + fakeClock.restore(); + fakeClock = sinon.useFakeTimers(Date.parse("2100-04-11T14:08:00Z")); + await assert.rejects( samlObj.validateRedirectAsync(this.request, this.request.originalQuery), { message: "SAML assertion expired" } @@ -3164,7 +2046,7 @@ describe("validateRedirect()", function () { this.request.Signature = "foo"; await assert.rejects( samlObj.validateRedirectAsync(this.request, this.request.originalQuery), - { message: "Invalid signature" } + { message: "Invalid query signature" } ); }); it("returns profile for valid signature including session index", async function () { @@ -3183,6 +2065,7 @@ describe("validateRedirect()", function () { }); describe("sp slo", function () { let samlObj: SAML; + beforeEach(function () { samlObj = new SAML({ cert: fs.readFileSync(__dirname + "/static/acme_tools_com.cert", "ascii"), @@ -3193,10 +2076,8 @@ describe("validateRedirect()", function () { {}, JSON.parse(fs.readFileSync(__dirname + "/static/sp_slo_redirect.json", "utf8")) ); - this.clock = sinon.useFakeTimers(Date.parse("2018-04-11T14:08:00Z")); }); afterEach(async function () { - this.clock.restore(); await samlObj.cacheProvider.removeAsync("_79db1e7ad12ca1d63e5b"); }); it("errors if bad xml", async function () { @@ -3235,7 +2116,7 @@ describe("validateRedirect()", function () { this.request.Signature = "foo"; await assert.rejects( samlObj.validateRedirectAsync(this.request, this.request.originalQuery), - { message: "Invalid signature" } + { message: "Invalid query signature" } ); }); diff --git a/test/types.ts b/test/types.ts new file mode 100644 index 00000000..65bfd848 --- /dev/null +++ b/test/types.ts @@ -0,0 +1,26 @@ +import { SamlConfig } from "../src/passport-saml/types"; + +// a certificate which is re-used by several tests +export const TEST_CERT = + "MIIEFzCCAv+gAwIBAgIUFJsUjPM7AmWvNtEvULSHlTTMiLQwDQYJKoZIhvcNAQEFBQAwWDELMAkGA1UEBhMCVVMxETAPBgNVBAoMCFN1YnNwYWNlMRUwEwYDVQQLDAxPbmVMb2dpbiBJZFAxHzAdBgNVBAMMFk9uZUxvZ2luIEFjY291bnQgNDIzNDkwHhcNMTQwNTEzMTgwNjEyWhcNMTkwNTE0MTgwNjEyWjBYMQswCQYDVQQGEwJVUzERMA8GA1UECgwIU3Vic3BhY2UxFTATBgNVBAsMDE9uZUxvZ2luIElkUDEfMB0GA1UEAwwWT25lTG9naW4gQWNjb3VudCA0MjM0OTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKrAzJdY9FzFLt5blArJfPzgi87EnFGlTfcV5T1TUDwLBlDkY/0ZGKnMOpf3D7ie2C4pPFOImOogcM5kpDDL7qxTXZ1ewXVyjBdMu29NG2C6NzWeQTUMUji01EcHkC8o+Pts8ANiNOYcjxEeyhEyzJKgEizblYzMMKzdrOET6QuqWo3C83K+5+5dsjDn1ooKGRwj3HvgsYcFrQl9NojgQFjoobwsiE/7A+OJhLpBcy/nSVgnoJaMfrO+JsnukZPztbntLvOl56+Vra0N8n5NAYhaSayPiv/ayhjVgjfXd1tjMVTOiDknUOwizZuJ1Y3QH94vUtBgp0WBpBSs/xMyTs8CAwEAAaOB2DCB1TAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBRQO4WpM5fWwxib49WTuJkfYDbxODCBlQYDVR0jBIGNMIGKgBRQO4WpM5fWwxib49WTuJkfYDbxOKFcpFowWDELMAkGA1UEBhMCVVMxETAPBgNVBAoMCFN1YnNwYWNlMRUwEwYDVQQLDAxPbmVMb2dpbiBJZFAxHzAdBgNVBAMMFk9uZUxvZ2luIEFjY291bnQgNDIzNDmCFBSbFIzzOwJlrzbRL1C0h5U0zIi0MA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQUFAAOCAQEACdDAAoaZFCEY5pmfwbKuKrXtO5iE8lWtiCPjCZEUuT6bXRNcqrdnuV/EAfX9WQoXjalPi0eM78zKmbvRGSTUHwWw49RHjFfeJUKvHNeNnFgTXDjEPNhMvh69kHm453lFRmB+kk6yjtXRZaQEwS8Uuo2Ot+krgNbl6oTBZJ0AHH1MtZECDloms1Km7zsK8wAi5i8TVIKkVr5b2VlhrLgFMvzZ5ViAxIMGB6w47yY4QGQB/5Q8ya9hBs9vkn+wubA+yr4j14JXZ7blVKDSTYva65Ea+PqHyrp+Wnmnbw2ObS7iWexiTy1jD3G0R2avDBFjM8Fj5DbfufsE1b0U10RTtg=="; + +export const FAKE_CERT = "fake cert"; +export interface CapturedCheck { + samlRequest?: any; + result?: any; + name: string; + expectedStatusCode: number; + samlResponse?: any; + config: SamlConfig; + expectedNameIDStartsWith?: string; + mockDate: string; +} + +export interface SamlCheck { + samlRequest?: any; + result?: any; + name: string; + samlResponse?: any; + config: SamlConfig; + expectedNameIDStartsWith?: string; +}