Skip to content

Commit

Permalink
Assert booleans when constructing options object (#85)
Browse files Browse the repository at this point in the history
  • Loading branch information
cjbarth committed Jul 22, 2022
1 parent b9b6ef9 commit 4355f8f
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 18 deletions.
16 changes: 13 additions & 3 deletions src/saml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import {
XMLValue,
} from "./types";
import { AuthenticateOptions, AuthorizeOptions } from "./passport-saml-types";
import { assertRequired } from "./utility";
import { assertBooleanIfPresent, assertRequired } from "./utility";
import {
buildXml2JsObject,
buildXmlBuilderObject,
Expand Down Expand Up @@ -122,6 +122,16 @@ class SAML {
assertRequired(ctorOptions.issuer, "issuer is required");
assertRequired(ctorOptions.cert, "cert is required");

// Prevent a JS user from passing in "false", which is truthy, and doing the wrong thing
assertBooleanIfPresent(ctorOptions.passive);
assertBooleanIfPresent(ctorOptions.disableRequestedAuthnContext);
assertBooleanIfPresent(ctorOptions.forceAuthn);
assertBooleanIfPresent(ctorOptions.skipRequestCompression);
assertBooleanIfPresent(ctorOptions.disableRequestAcsUrl);
assertBooleanIfPresent(ctorOptions.allowCreate);
assertBooleanIfPresent(ctorOptions.wantAssertionsSigned);
assertBooleanIfPresent(ctorOptions.signMetadata);

const options: SamlOptions = {
...ctorOptions,
passive: ctorOptions.passive ?? false,
Expand Down Expand Up @@ -160,7 +170,7 @@ class SAML {
signatureAlgorithm: ctorOptions.signatureAlgorithm ?? "sha1", // sha1, sha256, or sha512
authnRequestBinding: ctorOptions.authnRequestBinding ?? "HTTP-Redirect",
generateUniqueId: ctorOptions.generateUniqueId ?? generateUniqueId,

signMetadata: ctorOptions.signMetadata ?? false,
racComparison: ctorOptions.racComparison ?? "exact",
};

Expand Down Expand Up @@ -249,7 +259,7 @@ class SAML {

if (isPassive) request["samlp:AuthnRequest"]["@IsPassive"] = true;

if (this.options.forceAuthn) {
if (this.options.forceAuthn === true) {
request["samlp:AuthnRequest"]["@ForceAuthn"] = true;
}

Expand Down
2 changes: 1 addition & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ export interface SamlOptions extends Partial<SamlSigningOptions>, MandatorySamlO
wantAssertionsSigned: boolean;
maxAssertionAgeMs: number;
generateUniqueId: () => string;
signMetadata?: boolean;
signMetadata: boolean;

// InResponseTo Validation
validateInResponseTo: ValidateInResponseTo;
Expand Down
9 changes: 9 additions & 0 deletions src/utility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ export function assertRequired<T>(value: T | null | undefined, error?: string):
}
}

export function assertBooleanIfPresent<T>(
value: T | null | undefined,
error?: string
): asserts value {
if (value != null && typeof value != "boolean") {
throw new TypeError(error ?? "value is set but not boolean");
}
}

export function signXmlResponse(samlMessage: string, options: SamlSigningOptions): string {
const responseXpath =
'//*[local-name(.)="Response" and namespace-uri(.)="urn:oasis:names:tc:SAML:2.0:protocol"]';
Expand Down
73 changes: 59 additions & 14 deletions test/samlRequest.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,7 @@ describe("SAML request", function () {
const encodedSamlRequest = samlRequestMatchValues?.[1];

let buffer = Buffer.from(encodedSamlRequest, "base64");
if (!config.skipRequestCompression) {
buffer = zlib.inflateRawSync(buffer);
}
buffer = zlib.inflateRawSync(buffer);

return parseStringPromise(buffer.toString());
})
Expand Down Expand Up @@ -160,9 +158,7 @@ describe("SAML request", function () {
const encodedSamlRequest = samlRequestMatchValues?.[1];

let buffer = Buffer.from(encodedSamlRequest, "base64");
if (!config.skipRequestCompression) {
buffer = zlib.inflateRawSync(buffer);
}
buffer = zlib.inflateRawSync(buffer);

return parseStringPromise(buffer.toString());
})
Expand Down Expand Up @@ -229,9 +225,7 @@ describe("SAML request", function () {
const encodedSamlRequest = samlRequestMatchValues?.[1];

let buffer = Buffer.from(encodedSamlRequest, "base64");
if (!config.skipRequestCompression) {
buffer = zlib.inflateRawSync(buffer);
}
buffer = zlib.inflateRawSync(buffer);

return parseStringPromise(buffer.toString());
})
Expand All @@ -247,7 +241,7 @@ describe("SAML request", function () {
entryPoint: "https://wwwexampleIdp.com/saml",
cert: FAKE_CERT,
issuer: "onelogin_saml",
forceAuthn: false,
forceAuthn: true,
passive: true,
};

Expand All @@ -259,6 +253,7 @@ describe("SAML request", function () {
ProtocolBinding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
AssertionConsumerServiceURL: "http://localhost/saml/consume",
Destination: "https://wwwexampleIdp.com/saml",
ForceAuthn: "true",
IsPassive: "true",
},
"saml:Issuer": [
Expand Down Expand Up @@ -288,7 +283,7 @@ describe("SAML request", function () {
};

const oSAML = new SAML(config);
oSAML
return oSAML
.getAuthorizeFormAsync("http://localhost/saml/consume")
.then((formBody) => {
expect(formBody).to.match(/<!DOCTYPE html>[^]*<input.*name="SAMLRequest"[^]*<\/html>/);
Expand All @@ -297,9 +292,59 @@ describe("SAML request", function () {
const encodedSamlRequest = samlRequestMatchValues?.[1];

let buffer = Buffer.from(encodedSamlRequest, "base64");
if (!config.skipRequestCompression) {
buffer = zlib.inflateRawSync(buffer);
}
buffer = zlib.inflateRawSync(buffer);

return parseStringPromise(buffer.toString());
})
.then((doc) => {
delete doc["samlp:AuthnRequest"]["$"]["ID"];
delete doc["samlp:AuthnRequest"]["$"]["IssueInstant"];
expect(doc).to.deep.equal(result);
});
});

it("Config with disableRequestedAuthnContext, skipRequestCompression, disableRequestAcsUrl", async function () {
const config: SamlConfig = {
entryPoint: "https://wwwexampleIdp.com/saml",
cert: FAKE_CERT,
issuer: "onelogin_saml",
disableRequestedAuthnContext: true,
skipRequestCompression: true,
disableRequestAcsUrl: true,
};

const 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" } },
],
"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",
},
},
],
},
};

const oSAML = new SAML(config);
return oSAML
.getAuthorizeFormAsync("http://localhost/saml/consume")
.then((formBody) => {
expect(formBody).to.match(/<!DOCTYPE html>[^]*<input.*name="SAMLRequest"[^]*<\/html>/);
const samlRequestMatchValues = formBody.match(/<input.*name="SAMLRequest" value="([^"]*)"/);
assertRequired(samlRequestMatchValues?.[1]);
const encodedSamlRequest = samlRequestMatchValues?.[1];
const buffer = Buffer.from(encodedSamlRequest, "base64");

return parseStringPromise(buffer.toString());
})
Expand Down

0 comments on commit 4355f8f

Please sign in to comment.