mocksaml/utils/response.ts
Deepak Prabhakara 331c3cf318
Switch to saml20 (#21)
* Use boxyhq/saml20

* use sign from saml20

* cleaned up GetKeyInfo

* cleaned up getPublicKeyPemFromCertificate

* cleaned up node-forge

* use hasValidSignature from saml20

* cleanup and update saml20 to the beta version

* throw an error if signature is not valid

* updated saml20
2022-04-26 18:02:12 +01:00

140 lines
4.4 KiB
TypeScript

import crypto from 'crypto';
import xmlbuilder from 'xmlbuilder';
import { User } from '../types';
import saml from '@boxyhq/saml20';
const responseXPath =
'/*[local-name(.)="Response" and namespace-uri(.)="urn:oasis:names:tc:SAML:2.0:protocol"]';
const createResponseXML = async (params: {
idpIdentityId: string;
audience: string;
acsUrl: string;
samlReqId: string;
user: User;
}): Promise<string> => {
const { idpIdentityId, audience, acsUrl, user, samlReqId } = params;
const authDate = new Date();
const authTimestamp = authDate.toISOString();
authDate.setMinutes(authDate.getMinutes() - 5);
const notBefore = authDate.toISOString();
authDate.setMinutes(authDate.getMinutes() + 10);
const notAfter = authDate.toISOString();
const inResponseTo = samlReqId;
// const responseId = crypto.randomBytes(10).toString('hex');
const attributeStatement = {
'@xmlns:xs': 'http://www.w3.org/2001/XMLSchema',
'@xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
'saml:Attribute': [
{
'@Name': 'id',
'@NameFormat': 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic',
'saml:AttributeValue': {
'@xmlns:xs': 'http://www.w3.org/2001/XMLSchema',
'@xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
'@xsi:type': 'xs:string',
'#text': user.id,
},
},
{
'@Name': 'email',
'@NameFormat': 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic',
'saml:AttributeValue': {
'@xmlns:xs': 'http://www.w3.org/2001/XMLSchema',
'@xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
'@xsi:type': 'xs:string',
'#text': user.email,
},
},
{
'@Name': 'firstName',
'@NameFormat': 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic',
'saml:AttributeValue': {
'@xmlns:xs': 'http://www.w3.org/2001/XMLSchema',
'@xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
'@xsi:type': 'xs:string',
'#text': user.firstName,
},
},
{
'@Name': 'lastName',
'@NameFormat': 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic',
'saml:AttributeValue': {
'@xmlns:xs': 'http://www.w3.org/2001/XMLSchema',
'@xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
'@xsi:type': 'xs:string',
'#text': user.lastName,
},
},
],
};
const nodes = {
'samlp:Response': {
'@xmlns:samlp': 'urn:oasis:names:tc:SAML:2.0:protocol',
'@Version': '2.0',
'@ID': crypto.randomBytes(10).toString('hex'),
'@Destination': acsUrl,
'@InResponseTo': inResponseTo,
'@IssueInstant': authTimestamp,
'saml:Issuer': {
'@xmlns:saml': 'urn:oasis:names:tc:SAML:2.0:assertion',
'@Format': 'urn:oasis:names:tc:SAML:2.0:assertion',
'#text': idpIdentityId,
},
'samlp:Status': {
'samlp:StatusCode': {
'@Value': 'urn:oasis:names:tc:SAML:2.0:status:Success',
},
},
'saml:Assertion': {
'@xmlns:saml': 'urn:oasis:names:tc:SAML:2.0:assertion',
'@Version': '2.0',
'@ID': crypto.randomBytes(10).toString('hex'),
'@IssueInstant': authTimestamp,
'saml:Issuer': {
'#text': idpIdentityId,
},
'saml:Subject': {
'saml:NameID': {
'@Format': 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified',
'#text': user.email,
},
},
'saml:Conditions': {
'@NotBefore': notBefore,
'@NotOnOrAfter': notAfter,
'saml:AudienceRestriction': {
'saml:Audience': {
'#text': audience,
},
},
},
'saml:AuthnStatement': {
'@AuthnInstant': authTimestamp,
'@SessionIndex': '_YIlFoNFzLMDYxdwf-T_BuimfkGa5qhKg',
'saml:AuthnContext': {
'saml:AuthnContextClassRef': {
'#text': 'urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified',
},
},
},
'saml:AttributeStatement': attributeStatement,
},
},
};
return xmlbuilder.create(nodes, { encoding: 'UTF-8' }).end();
};
const signResponseXML = async (xml: string, signingKey: any, publicKey: any): Promise<string> => {
return await saml.sign(xml, signingKey, publicKey, responseXPath);
};
export { createResponseXML, signResponseXML };