82 lines
2.3 KiB
JavaScript
82 lines
2.3 KiB
JavaScript
// SAML service-provider (relying-party) registry. Backed by _idp_saml_sps. The
|
|
// SSO endpoint only issues an assertion to a registered SP and only to one of
|
|
// its allow-listed ACS URLs -- this is what prevents a forged AuthnRequest from
|
|
// having a signed assertion delivered to an attacker-chosen Destination. The
|
|
// signing_cert is a PUBLIC X.509 cert (no sealing, unlike client secrets); when
|
|
// present and want_authn_requests_signed is set, the SSO endpoint verifies the
|
|
// AuthnRequest signature against it.
|
|
|
|
const db = require("@saltcorn/data/db");
|
|
|
|
const { TABLE_SAML_SPS } = require("../constants");
|
|
|
|
|
|
// True if the requested ACS exactly matches one of the SP's allow-listed URLs.
|
|
// Exact-string match is the secure default (no trailing-slash/scheme fuzzing).
|
|
const acsAllowed = (row, acs) => {
|
|
if (!row || !acs) {
|
|
return false;
|
|
}
|
|
let list = [];
|
|
try {
|
|
list = JSON.parse(row.acs_urls);
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
return Array.isArray(list) && list.includes(acs);
|
|
};
|
|
|
|
|
|
const acsUrls = (row) => {
|
|
try {
|
|
const list = JSON.parse(row.acs_urls);
|
|
return Array.isArray(list) ? list : [];
|
|
} catch (e) {
|
|
return [];
|
|
}
|
|
};
|
|
|
|
|
|
const createSp = async (opts) => {
|
|
await db.insert(TABLE_SAML_SPS, {
|
|
entity_id: opts.entityId,
|
|
label: opts.label || null,
|
|
acs_urls: JSON.stringify(opts.acsUrls || []),
|
|
signing_cert: opts.signingCert || null,
|
|
want_authn_requests_signed: opts.wantSigned ? 1 : 0,
|
|
created_at: new Date().toISOString()
|
|
}, { noid: true });
|
|
return { entity_id: opts.entityId };
|
|
};
|
|
|
|
|
|
const deleteSp = async (entityId) => {
|
|
await db.deleteWhere(TABLE_SAML_SPS, { entity_id: entityId });
|
|
};
|
|
|
|
|
|
const getSp = async (entityId) => {
|
|
return await db.selectMaybeOne(TABLE_SAML_SPS, { entity_id: entityId });
|
|
};
|
|
|
|
|
|
const listSps = async () => {
|
|
return await db.select(TABLE_SAML_SPS, {}, { orderBy: "entity_id" });
|
|
};
|
|
|
|
|
|
// Coerce the stored INTEGER 0/1 (sqlite) or boolean (pg driver) to a real bool.
|
|
const wantsSignedRequests = (row) => {
|
|
return !!(row && row.want_authn_requests_signed);
|
|
};
|
|
|
|
|
|
module.exports = {
|
|
acsAllowed,
|
|
acsUrls,
|
|
createSp,
|
|
deleteSp,
|
|
getSp,
|
|
listSps,
|
|
wantsSignedRequests
|
|
};
|