UNPKG

@pagopa/io-spid-commons

Version:

Common code for integrating SPID authentication

113 lines 6.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.parseStartupIdpsMetadata = exports.fetchIdpsMetadata = exports.fetchMetadataXML = exports.mapIpdMetadataL = exports.mapIpdMetadata = exports.parseIdpMetadata = void 0; /** * Methods to fetch and parse Identity service providers metadata. */ const reporters_1 = require("@pagopa/ts-commons/lib/reporters"); const E = require("fp-ts/lib/Either"); const function_1 = require("fp-ts/lib/function"); const R = require("fp-ts/lib/Record"); const string_1 = require("fp-ts/lib/string"); const TE = require("fp-ts/lib/TaskEither"); const node_fetch_1 = require("node-fetch"); const config_1 = require("../config"); const IDPEntityDescriptor_1 = require("../types/IDPEntityDescriptor"); const logger_1 = require("./logger"); const samlUtils_1 = require("./samlUtils"); const EntityDescriptorTAG = "EntityDescriptor"; const X509CertificateTAG = "X509Certificate"; const SingleSignOnServiceTAG = "SingleSignOnService"; const SingleLogoutServiceTAG = "SingleLogoutService"; const METADATA_NAMESPACES = { METADATA: "urn:oasis:names:tc:SAML:2.0:metadata", XMLDSIG: "http://www.w3.org/2000/09/xmldsig#", }; /** * Parse a string that represents an XML file containing * the ipd Metadata and converts it into an array of IDPEntityDescriptor * * Required namespace definitions into the XML are * xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" and xmlns:ds="http://www.w3.org/2000/09/xmldsig#" * * An example file is provided in /test_idps/spid-entities-idps.xml of this project. */ const parseIdpMetadata = (idpMetadataPage) => (0, function_1.pipe)((0, samlUtils_1.safeXMLParseFromString)(idpMetadataPage), E.fromOption(() => new Error("Empty XML file content")), E.chain(E.fromPredicate((domParser) => domParser && !domParser.getElementsByTagName("parsererror").item(0), () => new Error("XML parser error"))), E.chain((domParser) => { const entityDescriptors = domParser.getElementsByTagNameNS(METADATA_NAMESPACES.METADATA, EntityDescriptorTAG); return E.right(Array.from(entityDescriptors).reduce((idps, element) => { var _a, _b; const certs = Array.from(element.getElementsByTagNameNS(METADATA_NAMESPACES.XMLDSIG, X509CertificateTAG)).map((_) => _.textContent ? _.textContent.replace(/[\n\s]/g, "") : ""); return (0, function_1.pipe)(IDPEntityDescriptor_1.IDPEntityDescriptor.decode({ cert: certs, entityID: element.getAttribute("entityID"), entryPoint: (_a = Array.from(element.getElementsByTagNameNS(METADATA_NAMESPACES.METADATA, SingleSignOnServiceTAG)) .filter((_) => _.getAttribute("Binding") === "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect")[0]) === null || _a === void 0 ? void 0 : _a.getAttribute("Location"), logoutUrl: ((_b = Array.from(element.getElementsByTagNameNS(METADATA_NAMESPACES.METADATA, SingleLogoutServiceTAG)) .filter((_) => _.getAttribute("Binding") === "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect")[0]) === null || _b === void 0 ? void 0 : _b.getAttribute("Location")) || "", }), E.fold((errs) => { logger_1.logger.warn("Invalid md:EntityDescriptor. %s", (0, reporters_1.errorsToReadableMessages)(errs).join(" / ")); return idps; }, (elementInfo) => [...idps, elementInfo])); }, [])); })); exports.parseIdpMetadata = parseIdpMetadata; /** * Map provided idpMetadata into an object with idp key whitelisted in ipdIds. * Mapping is based on entityID property */ const mapIpdMetadata = (idpMetadata, idpIds) => idpMetadata.reduce((prev, idp) => { const idpKey = idpIds[idp.entityID]; if (idpKey) { return Object.assign(Object.assign({}, prev), { [idpKey]: idp }); } logger_1.logger.warn(`Unsupported SPID idp from metadata repository [${idp.entityID}]`); return prev; }, {}); exports.mapIpdMetadata = mapIpdMetadata; /** * Lazy version of mapIpdMetadata() */ const mapIpdMetadataL = (idpIds) => (idpMetadata) => (0, exports.mapIpdMetadata)(idpMetadata, idpIds); exports.mapIpdMetadataL = mapIpdMetadataL; /** * Fetch an XML from a remote URL */ const fetchMetadataXML = (idpMetadataUrl) => (0, function_1.pipe)(TE.tryCatch(() => { logger_1.logger.info("Fetching SPID metadata from [%s]...", idpMetadataUrl); return (0, node_fetch_1.default)(idpMetadataUrl); }, E.toError), TE.chain(TE.fromPredicate((p) => p.status >= 200 && p.status < 300, () => { logger_1.logger.warn("Error fetching remote metadata for %s", idpMetadataUrl); return new Error("Error fetching remote metadata"); })), TE.chain((p) => TE.tryCatch(() => p.text(), E.toError))); exports.fetchMetadataXML = fetchMetadataXML; /** * Load idp Metadata from a remote url, parse infos and return a mapped and whitelisted idp options * for spidStrategy object. */ const fetchIdpsMetadata = (idpMetadataUrl, idpIds) => (0, function_1.pipe)((0, exports.fetchMetadataXML)(idpMetadataUrl), TE.chain((idpMetadataXML) => { logger_1.logger.info("Parsing SPID metadata for %s", idpMetadataUrl); return TE.fromEither((0, exports.parseIdpMetadata)(idpMetadataXML)); }), TE.chain(TE.fromPredicate((idpMetadata) => idpMetadata.length > 0, () => { logger_1.logger.error("No SPID metadata found for %s", idpMetadataUrl); return new Error("No SPID metadata found"); })), TE.map((idpMetadata) => { if (!idpMetadata.length) { logger_1.logger.warn("Missing SPID metadata on %s", idpMetadataUrl); } logger_1.logger.info("Configuring IdPs for %s", idpMetadataUrl); return (0, exports.mapIpdMetadata)(idpMetadata, idpIds); })); exports.fetchIdpsMetadata = fetchIdpsMetadata; /** * This method expects in input a Record where key are idp identifier * and values are an XML string (idp metadata). * Provided metadata are parsed and converted into IDP Entity Descriptor objects. */ const parseStartupIdpsMetadata = (idpsMetadata) => (0, function_1.pipe)(idpsMetadata, R.reduce(string_1.Ord)([], (prev, metadataXML) => [ ...prev, ...(0, function_1.pipe)((0, exports.parseIdpMetadata)(metadataXML), E.getOrElseW(() => [])), ]), (0, exports.mapIpdMetadataL)(Object.assign(Object.assign({}, config_1.SPID_IDP_IDENTIFIERS), config_1.CIE_IDP_IDENTIFIERS))); exports.parseStartupIdpsMetadata = parseStartupIdpsMetadata; //# sourceMappingURL=metadata.js.map