diff --git a/src/utils/__tests__/saml.test.ts b/src/utils/__tests__/saml.test.ts index 5926171c..0432b976 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(" { 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..aa40f9cb 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,25 @@ import { IDPEntityDescriptor } from "../types/IDPEntityDescriptor"; import { fetchIdpsMetadata } from "./metadata"; import { logSamlCertExpiration, SamlAttributeT } from "./saml"; +export const ProfessionalPurpose = t.keyof({ + LP: null, + P: null, + PF: null, + PG: null, + PX: null +}); + +const ProfessionalSpidExtension = t.union([ + t.interface({ + professionalSpidEnabled: t.literal(false) + }), + t.interface({ + professionalSpidEnabled: t.literal(true), + purpose: ProfessionalPurpose + }) +]); + +type ProfessionalSpidExtension = t.TypeOf; interface IServiceProviderOrganization { URL: string; displayName: string; @@ -34,6 +54,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..43a79892 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 SPID_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": SPID_SAML_EXTENSION + }, + "spid:Purpose": { + _: _.professionalSpidExtension.purpose + } + }; + } return o; }, toError) )