From 176378995413ae81f86dbb2d7f721fa8105c5649 Mon Sep 17 00:00:00 2001 From: AleDore Date: Mon, 26 Apr 2021 10:45:58 +0200 Subject: [PATCH 1/3] Provide support for professional Spid --- src/utils/__tests__/saml.test.ts | 46 ++++++++++++++++++++++++++++++-- src/utils/middleware.ts | 27 +++++++++++++++++++ src/utils/saml.ts | 13 +++++++++ 3 files changed, 84 insertions(+), 2 deletions(-) diff --git a/src/utils/__tests__/saml.test.ts b/src/utils/__tests__/saml.test.ts index 5926171c..77b4abe5 100644 --- a/src/utils/__tests__/saml.test.ts +++ b/src/utils/__tests__/saml.test.ts @@ -2,6 +2,7 @@ import { right } from "fp-ts/lib/Either"; import { isSome, tryCatch } from "fp-ts/lib/Option"; import { fromEither } from "fp-ts/lib/TaskEither"; import { SamlConfig } from "passport-saml"; +import { Builder } from "xml2js"; import { DOMParser } from "xmldom"; import { EventTracker } from "../../index"; import { @@ -12,7 +13,11 @@ import { samlResponseCIE } from "../__mocks__/saml"; import { StrictResponseValidationOptions } from "../middleware"; -import { getPreValidateResponse, getXmlFromSamlResponse } from "../saml"; +import { + getAuthorizeRequestTamperer, + getPreValidateResponse, + getXmlFromSamlResponse +} from "../saml"; import * as saml from "../saml"; const samlConfig: SamlConfig = ({ @@ -54,6 +59,43 @@ describe("getXmlFromSamlResponse", () => { }); }); +describe("getAuthorizeRequestTamperer", () => { + it("should add professional spid extension", async () => { + await getAuthorizeRequestTamperer( + // spid-testenv does not accept an xml header with utf8 encoding + new Builder({ xmldec: { encoding: undefined, version: "1.0" } }), + { + professionalSpidExtension: { + professionalSpidEnabled: true, + purpose: "P" + } + // tslint:disable-next-line: no-any + } as any, + samlConfig + )(samlRequest) + .fold( + err => fail(err), + _ => expect(_).toContain("P") + ) + .run(); + }); + + it("should not add professional spid extension", async () => { + await getAuthorizeRequestTamperer( + // spid-testenv does not accept an xml header with utf8 encoding + new Builder({ xmldec: { encoding: undefined, version: "1.0" } }), + // tslint:disable-next-line: no-any + {} as any, + samlConfig + )(samlRequest) + .fold( + err => fail(err), + _ => expect(_).not.toContain("P") + ) + .run(); + }); +}); + // tslint:disable-next-line: no-big-function describe("preValidateResponse", () => { const mockCallback = jest.fn(); @@ -80,7 +122,7 @@ describe("preValidateResponse", () => { error ? expect(callback).toBeCalledWith(error) : expect(callback).toBeCalledWith(null, true, expect.any(String)); - resolve(); + resolve(void 0); }, 100); }); diff --git a/src/utils/middleware.ts b/src/utils/middleware.ts index b4b5ad48..51eb23bf 100644 --- a/src/utils/middleware.ts +++ b/src/utils/middleware.ts @@ -4,6 +4,7 @@ import * as express from "express"; import { array } from "fp-ts/lib/Array"; import { Task, task } from "fp-ts/lib/Task"; +import * as t from "io-ts"; import { NonEmptyString } from "italia-ts-commons/lib/strings"; import { Profile, SamlConfig, VerifiedCallback } from "passport-saml"; import { RedisClient } from "redis"; @@ -18,6 +19,31 @@ import { IDPEntityDescriptor } from "../types/IDPEntityDescriptor"; import { fetchIdpsMetadata } from "./metadata"; import { logSamlCertExpiration, SamlAttributeT } from "./saml"; +export enum ProfessionalPurpose { + P = "P", + PX = "PX", + LP = "LP", + PG = "PG", + PF = "PF" +} + +const ProfessionalSpidExtension = t.union([ + t.interface({ + professionalSpidEnabled: t.literal(false) + }), + t.interface({ + professionalSpidEnabled: t.literal(true), + purpose: t.union([ + t.literal(ProfessionalPurpose.P), + t.literal(ProfessionalPurpose.PX), + t.literal(ProfessionalPurpose.LP), + t.literal(ProfessionalPurpose.PG), + t.literal(ProfessionalPurpose.PF) + ]) + }) +]); + +type ProfessionalSpidExtension = t.TypeOf; interface IServiceProviderOrganization { URL: string; displayName: string; @@ -34,6 +60,7 @@ export interface IServiceProviderConfig { IDPMetadataUrl: string; organization: IServiceProviderOrganization; publicCert: string; + professionalSpidExtension?: ProfessionalSpidExtension; strictResponseValidation?: StrictResponseValidationOptions; } diff --git a/src/utils/saml.ts b/src/utils/saml.ts index 551b465c..20004c83 100644 --- a/src/utils/saml.ts +++ b/src/utils/saml.ts @@ -68,6 +68,8 @@ export const SAML_NAMESPACE = { XMLDSIG: "http://www.w3.org/2000/09/xmldsig#" }; +const SAML_EXTENSION = "https://spid.gov.it/saml‐extensions"; + const ISSUER_FORMAT = "urn:oasis:names:tc:SAML:2.0:nameid-format:entity"; const decodeBase64 = (s: string) => Buffer.from(s, "base64").toString("utf8"); @@ -496,6 +498,17 @@ export const getAuthorizeRequestTamperer = ( authnRequest["saml:Issuer"][0].$.NameQualifier = samlConfig.issuer; // tslint:disable-next-line: no-object-mutation authnRequest["saml:Issuer"][0].$.Format = ISSUER_FORMAT; + if (_.professionalSpidExtension?.professionalSpidEnabled === true) { + // tslint:disable-next-line: no-object-mutation + authnRequest["samlp:Extensions"] = { + $: { + "xmlns:spid": SAML_EXTENSION + }, + "spid:Purpose": { + _: _.professionalSpidExtension.purpose + } + }; + } return o; }, toError) ) From 1f3a132a0fa0d905fe4fbf984f10bfad80e79cb9 Mon Sep 17 00:00:00 2001 From: AleDore Date: Mon, 26 Apr 2021 11:28:23 +0200 Subject: [PATCH 2/3] Refactor over review --- src/utils/__tests__/saml.test.ts | 2 +- src/utils/middleware.ts | 22 ++++++++-------------- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/src/utils/__tests__/saml.test.ts b/src/utils/__tests__/saml.test.ts index 77b4abe5..0432b976 100644 --- a/src/utils/__tests__/saml.test.ts +++ b/src/utils/__tests__/saml.test.ts @@ -90,7 +90,7 @@ describe("getAuthorizeRequestTamperer", () => { )(samlRequest) .fold( err => fail(err), - _ => expect(_).not.toContain("P") + _ => expect(_).not.toContain(" Date: Mon, 26 Apr 2021 14:43:49 +0200 Subject: [PATCH 3/3] better saml extension naming --- src/utils/saml.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/saml.ts b/src/utils/saml.ts index 20004c83..43a79892 100644 --- a/src/utils/saml.ts +++ b/src/utils/saml.ts @@ -68,7 +68,7 @@ export const SAML_NAMESPACE = { XMLDSIG: "http://www.w3.org/2000/09/xmldsig#" }; -const SAML_EXTENSION = "https://spid.gov.it/saml‐extensions"; +const SPID_SAML_EXTENSION = "https://spid.gov.it/saml‐extensions"; const ISSUER_FORMAT = "urn:oasis:names:tc:SAML:2.0:nameid-format:entity"; @@ -502,7 +502,7 @@ export const getAuthorizeRequestTamperer = ( // tslint:disable-next-line: no-object-mutation authnRequest["samlp:Extensions"] = { $: { - "xmlns:spid": SAML_EXTENSION + "xmlns:spid": SPID_SAML_EXTENSION }, "spid:Purpose": { _: _.professionalSpidExtension.purpose