mocksaml/utils/index.ts

220 lines
6.1 KiB
TypeScript
Raw Normal View History

2022-01-14 19:55:28 +00:00
// @ts-ignore
import { promises as fs } from 'fs';
import path from 'path';
import xml2js from 'xml2js';
import { User } from '../types';
2022-02-18 05:51:30 +00:00
import {promisify} from 'util';
import zlib from 'zlib';
2022-02-18 07:21:40 +00:00
import xmlbuilder from 'xmlbuilder';
2022-02-21 06:53:27 +00:00
import crypto from 'crypto';
2022-02-18 05:51:30 +00:00
const inflateRawSync = promisify(zlib.inflateRawSync)
2022-01-14 19:55:28 +00:00
// Parse XML
const parseXML = (xml: string): Promise<Record<string, any>> => {
return new Promise((resolve, reject) => {
xml2js.parseString(xml, (err: Error, result: any) => {
2022-02-18 05:51:30 +00:00
if(err) {
reject(err);
}
2022-01-14 19:55:28 +00:00
resolve(result);
});
});
};
2022-02-17 16:13:25 +00:00
// Parse SAMLRequest attributes
const extractSAMLRequestAttributes = async (samlRequest: string) => {
2022-02-18 05:51:30 +00:00
// const request = await inflateRawSync(Buffer.from(samlRequest, 'base64')).toString();
// const result = await parseXML(request);
// const attributes = result['samlp:AuthnRequest']['$'];
2022-01-14 19:55:28 +00:00
return {
2022-02-18 05:51:30 +00:00
id: '123',
acsUrl: 'https://hookb.in/NOrYqkDLnXse8mNNlDXx',
providerName: 'BoxyHQ',
2022-01-14 19:55:28 +00:00
};
};
const createIdPMetadataXML = async ({
idpEntityId,
idpSsoUrl,
certificate,
}: {
idpEntityId: string;
idpSsoUrl: string;
certificate: string;
}): Promise<string> => {
const xmlPath = path.join('data', 'idp-metadata.xml');
const xml = await fs.readFile(xmlPath, 'utf8');
return xml
.replace('idp_entity_id', idpEntityId)
.replace('idp_certificate', extractCert(certificate))
.replace(/idp_sso_url/g, idpSsoUrl);
};
const createCertificate = async () => {
2022-02-21 13:15:17 +00:00
const certificateFilePath = path.join('data', 'idp-public-key.txt');
2022-01-14 19:55:28 +00:00
return await fs.readFile(certificateFilePath, 'utf8');
};
const extractCert = (certificate: string) => {
return certificate
.replace('-----BEGIN CERTIFICATE-----', '')
.replace('-----END CERTIFICATE-----', '')
.trim();
};
2022-02-21 05:52:12 +00:00
const createSAMLResponseXML = async (params: {
idpIdentityId: string,
audience: string,
acsUrl: string,
user: User
}): Promise<string> => {
const {idpIdentityId, audience, acsUrl, user} = params;
2022-02-21 06:53:27 +00:00
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();
2022-02-21 13:15:17 +00:00
const inResponseTo = '_1234'
2022-02-21 06:53:27 +00:00
const responseId = crypto.randomBytes(10).toString('hex');
2022-02-18 07:21:40 +00:00
2022-02-21 13:15:17 +00:00
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': {
'#text': user.id,
}
},
{
'@Name': 'email',
'@NameFormat': 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic',
'saml:AttributeValue': {
'#text': user.email,
}
},
{
'@Name': 'firstName',
'@NameFormat': 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic',
'saml:AttributeValue': {
'#text': user.firstName,
}
},
{
'@Name': 'lastName',
'@NameFormat': 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic',
'saml:AttributeValue': {
'#text': user.lastName,
}
},
]
}
2022-02-18 07:21:40 +00:00
const nodes = {
'samlp:Response':{
'@xmlns:samlp': 'urn:oasis:names:tc:SAML:2.0:protocol',
2022-02-21 06:53:27 +00:00
'@Version': '2.0',
'@ID': responseId,
'@Destination': acsUrl,
'@InResponseTo': inResponseTo,
'@IssueInstant': authTimestamp,
2022-02-18 07:21:40 +00:00
'saml:Issuer': {
'@xmlns:saml': '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',
2022-02-21 06:53:27 +00:00
'@ID': responseId,
'@IssueInstant': authTimestamp,
2022-02-18 07:21:40 +00:00
'saml:Issuer': {
'#text': idpIdentityId,
},
'saml:Subject': {
'saml:NameID': {
'@Format': 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified',
2022-02-21 06:53:27 +00:00
'#text': user.email,
2022-02-18 07:21:40 +00:00
}
},
'saml:Conditions': {
2022-02-21 06:53:27 +00:00
'@NotBefore': notBefore,
'@NotOnOrAfter': notAfter,
2022-02-18 07:21:40 +00:00
'saml:AudienceRestriction': {
'saml:Audience': {
'#text': audience,
}
}
},
'saml:AuthnStatement': {
2022-02-21 06:53:27 +00:00
'@AuthnInstant': authTimestamp,
2022-02-18 07:21:40 +00:00
'@SessionIndex': '_YIlFoNFzLMDYxdwf-T_BuimfkGa5qhKg',
'saml:AuthnContext': {
'saml:AuthnContextClassRef': {
'#text': 'urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified'
}
}
},
2022-02-21 13:15:17 +00:00
'saml:AttributeStatement': attributeStatement,
},
2022-02-18 07:21:40 +00:00
}
}
2022-02-18 06:34:18 +00:00
2022-02-18 07:21:40 +00:00
return xmlbuilder.create(nodes).end({ pretty: true});
};
2022-02-18 06:34:18 +00:00
2022-02-21 13:15:17 +00:00
const signResponseXML = (xml: string, signingKey: any, publicKey: any): string => {
return xml;
}
2022-02-21 06:53:27 +00:00
2022-02-21 05:52:12 +00:00
// Create the HTML form to submit the response
export const createResponseForm = (relayState: string, encodedSamlResponse: string, acsUrl: string) => {
2022-02-18 06:34:18 +00:00
const formElements = [
'<!DOCTYPE html>',
'<html>',
'<head>',
'<meta charset="utf-8">',
'<meta http-equiv="x-ua-compatible" content="ie=edge">',
'</head>',
'<body onload="document.forms[0].submit()">',
'<noscript>',
'<p>Note: Since your browser does not support JavaScript, you must press the Continue button once to proceed.</p>',
'</noscript>',
'<form method="post" action="' + encodeURI(acsUrl) + '">',
'<input type="hidden" name="RelayState" value="' + relayState + '"/>',
2022-02-21 05:52:12 +00:00
'<input type="hidden" name="SAMLResponse" value="' + encodedSamlResponse + '"/>',
2022-02-18 06:34:18 +00:00
'<input type="submit" value="Continue" />',
'</form>',
'<script>document.forms[0].style.display="none";</script>',
'</body>',
'</html>',
];
return formElements.join('');
2022-01-14 19:55:28 +00:00
};
export {
parseXML,
extractSAMLRequestAttributes,
createIdPMetadataXML,
2022-02-21 05:52:12 +00:00
createSAMLResponseXML,
2022-01-14 19:55:28 +00:00
createCertificate,
extractCert,
2022-02-17 16:13:25 +00:00
};