@pagopa/io-spid-commons
Version:
Common code for integrating SPID authentication
113 lines • 6.4 kB
JavaScript
;
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