@cresta-oss/samlify
Version:
High-level API for Single Sign On (SAML 2.0)
353 lines • 12.3 kB
JavaScript
;
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
Object.defineProperty(exports, "__esModule", { value: true });
var xmldom_1 = require("xmldom");
var xpath_1 = require("xpath");
var utility_1 = require("./utility");
var camelcase_1 = require("camelcase");
var dom = xmldom_1.DOMParser;
function buildAbsoluteXPath(paths) {
return paths.reduce(function (currentPath, name) {
var appendedPath = currentPath;
var isWildcard = name.startsWith('~');
if (isWildcard) {
var pathName = name.replace('~', '');
appendedPath = currentPath + ("/*[contains(local-name(), '" + pathName + "')]");
}
if (!isWildcard) {
appendedPath = currentPath + ("/*[local-name(.)='" + name + "']");
}
return appendedPath;
}, '');
}
function buildAttributeXPath(attributes) {
if (attributes.length === 0) {
return '/text()';
}
if (attributes.length === 1) {
return "/@" + attributes[0];
}
var filters = attributes.map(function (attribute) { return "name()='" + attribute + "'"; }).join(' or ');
return "/@*[" + filters + "]";
}
exports.loginRequestFields = [
{
key: 'request',
localPath: ['AuthnRequest'],
attributes: ['ID', 'IssueInstant', 'Destination', 'AssertionConsumerServiceURL']
},
{
key: 'issuer',
localPath: ['AuthnRequest', 'Issuer'],
attributes: []
},
{
key: 'nameIDPolicy',
localPath: ['AuthnRequest', 'NameIDPolicy'],
attributes: ['Format', 'AllowCreate']
},
{
key: 'authnContextClassRef',
localPath: ['AuthnRequest', 'AuthnContextClassRef'],
attributes: []
},
{
key: 'signature',
localPath: ['AuthnRequest', 'Signature'],
attributes: [],
context: true
}
];
// support two-tiers status code
exports.loginResponseStatusFields = [
{
key: 'top',
localPath: ['Response', 'Status', 'StatusCode'],
attributes: ['Value'],
},
{
key: 'second',
localPath: ['Response', 'Status', 'StatusCode', 'StatusCode'],
attributes: ['Value'],
}
];
// support two-tiers status code
exports.logoutResponseStatusFields = [
{
key: 'top',
localPath: ['LogoutResponse', 'Status', 'StatusCode'],
attributes: ['Value']
},
{
key: 'second',
localPath: ['LogoutResponse', 'Status', 'StatusCode', 'StatusCode'],
attributes: ['Value'],
}
];
exports.loginResponseFields = function (assertion) { return [
{
key: 'conditions',
localPath: ['Assertion', 'Conditions'],
attributes: ['NotBefore', 'NotOnOrAfter'],
shortcut: assertion
},
{
key: 'response',
localPath: ['Response'],
attributes: ['ID', 'IssueInstant', 'Destination', 'InResponseTo'],
},
{
key: 'audience',
localPath: ['Assertion', 'Conditions', 'AudienceRestriction', 'Audience'],
attributes: [],
shortcut: assertion
},
// {
// key: 'issuer',
// localPath: ['Response', 'Issuer'],
// attributes: []
// },
{
key: 'issuer',
localPath: ['Assertion', 'Issuer'],
attributes: [],
shortcut: assertion
},
{
key: 'nameID',
localPath: ['Assertion', 'Subject', 'NameID'],
attributes: [],
shortcut: assertion
},
{
key: 'sessionIndex',
localPath: ['Assertion', 'AuthnStatement'],
attributes: ['AuthnInstant', 'SessionNotOnOrAfter', 'SessionIndex'],
shortcut: assertion
},
{
key: 'attributes',
localPath: ['Assertion', 'AttributeStatement', 'Attribute'],
index: ['Name'],
attributePath: ['AttributeValue'],
attributes: [],
shortcut: assertion
}
]; };
exports.logoutRequestFields = [
{
key: 'request',
localPath: ['LogoutRequest'],
attributes: ['ID', 'IssueInstant', 'Destination']
},
{
key: 'issuer',
localPath: ['LogoutRequest', 'Issuer'],
attributes: []
},
{
key: 'nameID',
localPath: ['LogoutRequest', 'NameID'],
attributes: []
},
{
key: 'signature',
localPath: ['LogoutRequest', 'Signature'],
attributes: [],
context: true
}
];
exports.logoutResponseFields = [
{
key: 'response',
localPath: ['LogoutResponse'],
attributes: ['ID', 'Destination', 'InResponseTo']
},
{
key: 'issuer',
localPath: ['LogoutResponse', 'Issuer'],
attributes: []
},
{
key: 'signature',
localPath: ['LogoutResponse', 'Signature'],
attributes: [],
context: true
}
];
function extract(context, fields) {
var rootDoc = new dom().parseFromString(context);
return fields.reduce(function (result, field) {
var _a, _b, _c, _d, _e, _f;
// get essential fields
var key = field.key;
var localPath = field.localPath;
var attributes = field.attributes;
var isEntire = field.context;
var shortcut = field.shortcut;
// get optional fields
var index = field.index;
var attributePath = field.attributePath;
// set allowing overriding if there is a shortcut injected
var targetDoc = rootDoc;
// if shortcut is used, then replace the doc
// it's a design for overriding the doc used during runtime
if (shortcut) {
targetDoc = new dom().parseFromString(shortcut);
}
// special case: multiple path
/*
{
key: 'issuer',
localPath: [
['Response', 'Issuer'],
['Response', 'Assertion', 'Issuer']
],
attributes: []
}
*/
if (localPath.every(function (path) { return Array.isArray(path); })) {
var multiXPaths = localPath
.map(function (path) {
// not support attribute yet, so ignore it
return buildAbsoluteXPath(path) + "/text()";
})
.join(' | ');
return __assign(__assign({}, result), (_a = {}, _a[key] = utility_1.uniq(xpath_1.select(multiXPaths, targetDoc).map(function (n) { return n.nodeValue; }).filter(utility_1.notEmpty)), _a));
}
// eo special case: multiple path
var baseXPath = buildAbsoluteXPath(localPath);
var attributeXPath = buildAttributeXPath(attributes);
// special case: get attributes where some are in child, some are in parent
/*
{
key: 'attributes',
localPath: ['Response', 'Assertion', 'AttributeStatement', 'Attribute'],
index: ['Name'],
attributePath: ['AttributeValue'],
attributes: []
}
*/
if (index && attributePath) {
// find the index in localpath
var indexPath = buildAttributeXPath(index);
var fullLocalXPath = "" + baseXPath + indexPath;
var parentNodes = xpath_1.select(baseXPath, targetDoc);
// [uid, mail, edupersonaffiliation], ready for aggregate
var parentAttributes = xpath_1.select(fullLocalXPath, targetDoc).map(function (n) { return n.value; });
// [attribute, attributevalue]
var childXPath = buildAbsoluteXPath([utility_1.last(localPath)].concat(attributePath));
var childAttributeXPath = buildAttributeXPath(attributes);
var fullChildXPath_1 = "" + childXPath + childAttributeXPath;
// [ 'test', 'test@example.com', [ 'users', 'examplerole1' ] ]
var childAttributes = parentNodes.map(function (node) {
var nodeDoc = new dom().parseFromString(node.toString());
if (attributes.length === 0) {
var childValues = xpath_1.select(fullChildXPath_1, nodeDoc).map(function (n) { return n.nodeValue; });
if (childValues.length === 1) {
return childValues[0];
}
return childValues;
}
if (attributes.length > 0) {
var childValues = xpath_1.select(fullChildXPath_1, nodeDoc).map(function (n) { return n.value; });
if (childValues.length === 1) {
return childValues[0];
}
return childValues;
}
return null;
});
// aggregation
var obj = utility_1.zipObject(parentAttributes, childAttributes, false);
return __assign(__assign({}, result), (_b = {}, _b[key] = obj, _b));
}
// case: fetch entire content, only allow one existence
/*
{
key: 'signature',
localPath: ['AuthnRequest', 'Signature'],
attributes: [],
context: true
}
*/
if (isEntire) {
var node = xpath_1.select(baseXPath, targetDoc);
var value = null;
if (node.length === 1) {
value = node[0].toString();
}
if (node.length > 1) {
value = node.map(function (n) { return n.toString(); });
}
return __assign(__assign({}, result), (_c = {}, _c[key] = value, _c));
}
// case: multiple attribute
/*
{
key: 'nameIDPolicy',
localPath: ['AuthnRequest', 'NameIDPolicy'],
attributes: ['Format', 'AllowCreate']
}
*/
if (attributes.length > 1) {
var baseNode = xpath_1.select(baseXPath, targetDoc).map(function (n) { return n.toString(); });
var childXPath_1 = "" + buildAbsoluteXPath([utility_1.last(localPath)]) + attributeXPath;
var attributeValues = baseNode.map(function (node) {
var nodeDoc = new dom().parseFromString(node);
var values = xpath_1.select(childXPath_1, nodeDoc).reduce(function (r, n) {
r[camelcase_1.default(n.name)] = n.value;
return r;
}, {});
return values;
});
return __assign(__assign({}, result), (_d = {}, _d[key] = attributeValues.length === 1 ? attributeValues[0] : attributeValues, _d));
}
// case: single attribute
/*
{
key: 'statusCode',
localPath: ['Response', 'Status', 'StatusCode'],
attributes: ['Value'],
}
*/
if (attributes.length === 1) {
var fullPath = "" + baseXPath + attributeXPath;
var attributeValues = xpath_1.select(fullPath, targetDoc).map(function (n) { return n.value; });
return __assign(__assign({}, result), (_e = {}, _e[key] = attributeValues[0], _e));
}
// case: zero attribute
/*
{
key: 'issuer',
localPath: ['AuthnRequest', 'Issuer'],
attributes: []
}
*/
if (attributes.length === 0) {
var attributeValue = null;
var node = xpath_1.select(baseXPath, targetDoc);
if (node.length === 1) {
var fullPath = "string(" + baseXPath + attributeXPath + ")";
attributeValue = xpath_1.select(fullPath, targetDoc);
}
if (node.length > 1) {
attributeValue = node.map(function (n) { return n.firstChild.nodeValue; });
}
return __assign(__assign({}, result), (_f = {}, _f[key] = attributeValue, _f));
}
return result;
}, {});
}
exports.extract = extract;
//# sourceMappingURL=extractor.js.map