Skip to content

Commit

Permalink
Merge 059463b into 6ec7855
Browse files Browse the repository at this point in the history
  • Loading branch information
laverya committed Apr 4, 2019
2 parents 6ec7855 + 059463b commit c92d9c7
Show file tree
Hide file tree
Showing 15 changed files with 58 additions and 50 deletions.
10 changes: 5 additions & 5 deletions src/binding-post.ts
Expand Up @@ -18,15 +18,15 @@ const binding = wording.binding;
* @param {object} entity object includes both idp and sp
* @param {function} customTagReplacement used when developers have their own login response template
*/
function base64LoginRequest(referenceTagXPath: string, entity: any, customTagReplacement: (template: string) => BindingContext): BindingContext {
function base64LoginRequest(referenceTagXPath: string, entity: any, customTagReplacement?: (template: string) => BindingContext): BindingContext {
const metadata = { idp: entity.idp.entityMeta, sp: entity.sp.entityMeta };
const spSetting = entity.sp.entitySetting;
let id: string = '';

if (metadata && metadata.idp && metadata.sp) {
const base = metadata.idp.getSingleSignOnService(binding.post);
let rawSamlRequest: string;
if (spSetting.loginRequestTemplate) {
if (spSetting.loginRequestTemplate && customTagReplacement) {
const info = customTagReplacement(spSetting.loginRequestTemplate.context);
id = get(info, 'id', null);
rawSamlRequest = get(info, 'context', null);
Expand Down Expand Up @@ -78,7 +78,7 @@ function base64LoginRequest(referenceTagXPath: string, entity: any, customTagRep
* @param {function} customTagReplacement used when developers have their own login response template
* @param {boolean} encryptThenSign whether or not to encrypt then sign first (if signing). Defaults to sign-then-encrypt
*/
async function base64LoginResponse(requestInfo: any = {}, entity: any, user: any = {}, customTagReplacement: (template: string) => BindingContext, encryptThenSign: boolean = false): Promise<BindingContext> {
async function base64LoginResponse(requestInfo: any = {}, entity: any, user: any = {}, customTagReplacement?: (template: string) => BindingContext, encryptThenSign: boolean = false): Promise<BindingContext> {
const idpSetting = entity.idp.entitySetting;
const spSetting = entity.sp.entitySetting;
const id = idpSetting.generateID();
Expand Down Expand Up @@ -117,7 +117,7 @@ async function base64LoginResponse(requestInfo: any = {}, entity: any, user: any
AuthnStatement: '',
AttributeStatement: '',
};
if (idpSetting.loginResponseTemplate) {
if (idpSetting.loginResponseTemplate && customTagReplacement) {
const template = customTagReplacement(idpSetting.loginResponseTemplate.context);
rawSamlResponse = get(template, 'context', null);
} else {
Expand Down Expand Up @@ -215,7 +215,7 @@ function base64LogoutRequest(user, referenceTagXPath, entity, customTagReplaceme
let id: string = '';
if (metadata && metadata.init && metadata.target) {
let rawSamlRequest: string;
if (initSetting.logoutRequestTemplate) {
if (initSetting.logoutRequestTemplate && customTagReplacement) {
const template = customTagReplacement(initSetting.logoutRequestTemplate.context);
id = get(template, 'id', null);
rawSamlRequest = get(template, 'context', null);
Expand Down
8 changes: 4 additions & 4 deletions src/binding-redirect.ts
Expand Up @@ -62,7 +62,7 @@ function buildRedirectURL(opts: BuildRedirectConfig) {
if (isSigned) {
const sigAlg = pvPair(urlParams.sigAlg, encodeURIComponent(entitySetting.requestSignatureAlgorithm));
const octetString = samlRequest + relayState + sigAlg;
return baseUrl + pvPair(queryParam, octetString, noParams) + pvPair(urlParams.signature, encodeURIComponent(libsaml.constructMessageSignature(queryParam + '=' + octetString, entitySetting.privateKey, entitySetting.privateKeyPass, null, entitySetting.requestSignatureAlgorithm)));
return baseUrl + pvPair(queryParam, octetString, noParams) + pvPair(urlParams.signature, encodeURIComponent(libsaml.constructMessageSignature(queryParam + '=' + octetString, entitySetting.privateKey, entitySetting.privateKeyPass, undefined, entitySetting.requestSignatureAlgorithm)));
}
return baseUrl + pvPair(queryParam, samlRequest + relayState, noParams);
}
Expand All @@ -81,7 +81,7 @@ function loginRequestRedirectURL(entity: { idp: Idp, sp: Sp }, customTagReplacem
if (metadata && metadata.idp && metadata.sp) {
const base = metadata.idp.getSingleSignOnService(binding.redirect);
let rawSamlRequest: string;
if (spSetting.loginRequestTemplate) {
if (spSetting.loginRequestTemplate && customTagReplacement) {
const info = customTagReplacement(spSetting.loginRequestTemplate);
id = get(info, 'id', null);
rawSamlRequest = get(info, 'context', null);
Expand Down Expand Up @@ -136,7 +136,7 @@ function logoutRequestRedirectURL(user, entity, relayState?: string, customTagRe
NameID: user.logoutNameID,
SessionIndex: user.sessionIndex,
};
if (initSetting.logoutRequestTemplate) {
if (initSetting.logoutRequestTemplate && customTagReplacement) {
const info = customTagReplacement(initSetting.logoutRequestTemplate, requiredTags);
id = get(info, 'id', null);
rawSamlRequest = get(info, 'context', null);
Expand Down Expand Up @@ -173,7 +173,7 @@ function logoutResponseRedirectURL(requestInfo: any, entity: any, relayState?: s
if (metadata && metadata.init && metadata.target) {
const base = metadata.target.getSingleLogoutService(binding.redirect);
let rawSamlResponse: string;
if (initSetting.logoutResponseTemplate) {
if (initSetting.logoutResponseTemplate && customTagReplacement) {
const template = customTagReplacement(initSetting.logoutResponseTemplate);
id = get(template, 'id', null);
rawSamlResponse = get(template, 'context', null);
Expand Down
5 changes: 3 additions & 2 deletions src/entity-idp.ts
Expand Up @@ -15,6 +15,7 @@ import { namespace } from './urn';
import postBinding from './binding-post';
import { flow, FlowResult } from './flow';
import { isString } from './utility';
import { BindingContext } from './entity';

/**
* Identity prvider can be configured using either metadata importing or idpSetting
Expand Down Expand Up @@ -46,7 +47,7 @@ export class IdentityProvider extends Entity {
};
entitySetting.loginResponseTemplate = {
...entitySetting.loginResponseTemplate,
context: libsaml.replaceTagsByValue(entitySetting.loginResponseTemplate.context, replacement),
context: libsaml.replaceTagsByValue(entitySetting.loginResponseTemplate!.context, replacement),
};
} else {
console.warn('Invalid login response template');
Expand All @@ -69,7 +70,7 @@ export class IdentityProvider extends Entity {
requestInfo: { [key: string]: any },
binding: string,
user: { [key: string]: any },
customTagReplacement?: (...args: any[]) => any,
customTagReplacement?: (template: string) => BindingContext,
encryptThenSign?: boolean,
) {
const protocol = namespace.binding[binding];
Expand Down
2 changes: 1 addition & 1 deletion src/entity-sp.ts
Expand Up @@ -55,7 +55,7 @@ export class ServiceProvider extends Entity {
public createLoginRequest(
idp: IdentityProvider,
binding = 'redirect',
customTagReplacement?: (...args: any[]) => any,
customTagReplacement?: (template: string) => BindingContext,
): BindingContext | PostBindingContext {
const nsBinding = namespace.binding;
const protocol = nsBinding[binding];
Expand Down
12 changes: 6 additions & 6 deletions src/extractor.ts
@@ -1,6 +1,6 @@
import { DOMParser } from 'xmldom';
import { select } from 'xpath';
import { uniq, last, zipObject } from './utility';
import { select, SelectedValue } from 'xpath';
import { uniq, last, zipObject, notEmpty } from './utility';
import camelCase from 'camelcase';
const dom = DOMParser;

Expand Down Expand Up @@ -219,7 +219,7 @@ export function extract(context: string, fields) {

return {
...result,
[key]: uniq(select(multiXPaths, targetDoc).map((n: Node) => n.nodeValue))
[key]: uniq(select(multiXPaths, targetDoc).map((n: Node) => n.nodeValue).filter(notEmpty))
};
}
// eo special case: multiple path
Expand Down Expand Up @@ -285,7 +285,7 @@ export function extract(context: string, fields) {
*/
if (isEntire) {
const node = select(baseXPath, targetDoc);
let value = null;
let value: string | string[] | null = null;
if (node.length === 1) {
value = node[0].toString();
}
Expand Down Expand Up @@ -347,14 +347,14 @@ export function extract(context: string, fields) {
}
*/
if (attributes.length === 0) {
let attributeValue = null;
let attributeValue: SelectedValue[] | Array<(string | null)> | null = null;
const node = select(baseXPath, targetDoc);
if (node.length === 1) {
const fullPath = `string(${baseXPath}${attributeXPath})`;
attributeValue = select(fullPath, targetDoc);
}
if (node.length > 1) {
attributeValue = node.map((n: Node) => n.firstChild.nodeValue);
attributeValue = node.map((n: Node) => n.firstChild!.nodeValue);
}
return {
...result,
Expand Down
4 changes: 2 additions & 2 deletions src/flow.ts
Expand Up @@ -80,7 +80,7 @@ async function redirectFlow(options) {

const extractorFields = getDefaultExtractorFields(parserType);

const parseResult: { samlContent: string, extract: any, sigAlg: string } = {
const parseResult: { samlContent: string, extract: any, sigAlg: (string | null) } = {
samlContent: xmlString,
sigAlg: null,
extract: extract(xmlString, extractorFields),
Expand Down Expand Up @@ -135,7 +135,7 @@ async function postFlow(options): Promise<FlowResult> {

const decryptRequired = from.entitySetting.isAssertionEncrypted;

let extractorFields = [];
let extractorFields: ExtractorFields = [];

// validate the xml first
await libsaml.isValidXml(samlContent);
Expand Down
22 changes: 12 additions & 10 deletions src/libsaml.ts
Expand Up @@ -7,13 +7,13 @@
import { DOMParser } from 'xmldom';
import utility, { flattenDeep, isString } from './utility';
import { algorithms, wording, namespace } from './urn';
import { select } from 'xpath';
import { select, SelectedValue } from 'xpath';
import { MetadataInterface } from './metadata';
import * as nrsa from 'node-rsa';
import { SignedXml, FileKeyInfo } from 'xml-crypto';
import * as xmlenc from '@authenio/xml-encryption';
import { extract } from './extractor';
import { getValidatorModule } from './schema-validator';
import { getValidatorModule, SchemaValidator } from './schema-validator';
import camelCase from 'camelcase';

const signatureAlgorithms = algorithms.signature;
Expand Down Expand Up @@ -158,9 +158,11 @@ const libSaml = () => {
* @return {string/null} signing algorithm short-hand for the module node-rsa
*/
function getSigningScheme(sigAlg?: string): string | null {
const algAlias = nrsaAliasMapping[sigAlg];
if (!(algAlias === undefined)) {
return algAlias;
if (sigAlg) {
const algAlias = nrsaAliasMapping[sigAlg];
if (!(algAlias === undefined)) {
return algAlias;
}
}
return nrsaAliasMapping[signatureAlgorithms.RSA_SHA1]; // default value
}
Expand Down Expand Up @@ -312,8 +314,8 @@ const libSaml = () => {
const wrappingElementsXPath = "/*[contains(local-name(), 'Response')]/*[local-name(.)='Assertion']/*[local-name(.)='Subject']/*[local-name(.)='SubjectConfirmation']/*[local-name(.)='SubjectConfirmationData']//*[local-name(.)='Assertion' or local-name(.)='Signature']";

// select the signature node
let selection = [];
let assertionNode = null;
let selection: any = [];
let assertionNode: string | null = null;
const messageSignatureNode = select(messageSignatureXpath, doc);
const assertionSignatureNode = select(assertionSignatureXpath, doc);
const wrappingElementNode = select(wrappingElementsXPath, doc);
Expand Down Expand Up @@ -510,7 +512,7 @@ const libSaml = () => {
* @param {string} xml response in xml string format
* @return {Promise} a promise to resolve the finalized xml
*/
encryptAssertion(sourceEntity, targetEntity, xml: string) {
encryptAssertion(sourceEntity, targetEntity, xml?: string) {
// Implement encryption after signature if it has
return new Promise<string>((resolve, reject) => {

Expand Down Expand Up @@ -599,7 +601,7 @@ const libSaml = () => {
*/
async isValidXml(input: string) {
try {
await mod.validate(input);
await mod!.validate(input);
return Promise.resolve();
} catch (e) {
throw e;
Expand All @@ -609,7 +611,7 @@ const libSaml = () => {
};

// load the validator module before the function runtime
let mod = null;
let mod: SchemaValidator | null = null;
(async () => mod = await getValidatorModule())();

export default libSaml();
10 changes: 5 additions & 5 deletions src/metadata-sp.ts
Expand Up @@ -81,19 +81,19 @@ export class SpMetadata extends Metadata {
}

if (signingCert) {
descriptors.KeyDescriptor.push(libsaml.createKeySection('signing', signingCert).KeyDescriptor);
descriptors.KeyDescriptor!.push(libsaml.createKeySection('signing', signingCert).KeyDescriptor);
} else {
//console.warn('Construct service provider - missing signing certificate');
}

if (encryptCert) {
descriptors.KeyDescriptor.push(libsaml.createKeySection('encryption', encryptCert).KeyDescriptor);
descriptors.KeyDescriptor!.push(libsaml.createKeySection('encryption', encryptCert).KeyDescriptor);
} else {
//console.warn('Construct service provider - missing encrypt certificate');
}

if (isNonEmptyArray(nameIDFormat)) {
nameIDFormat.forEach(f => descriptors.NameIDFormat.push(f));
nameIDFormat.forEach(f => descriptors.NameIDFormat!.push(f));
}

if (isNonEmptyArray(singleLogoutService)) {
Expand All @@ -107,7 +107,7 @@ export class SpMetadata extends Metadata {
if (a.isDefault) {
attr.isDefault = true;
}
descriptors.SingleLogoutService.push([{ _attr: attr }]);
descriptors.SingleLogoutService!.push([{ _attr: attr }]);
});
}

Expand All @@ -122,7 +122,7 @@ export class SpMetadata extends Metadata {
if (a.isDefault) {
attr.isDefault = true;
}
descriptors.AssertionConsumerService.push([{ _attr: attr }]);
descriptors.AssertionConsumerService!.push([{ _attr: attr }]);
});
} else {
// console.warn('Missing endpoint of AssertionConsumerService');
Expand Down
4 changes: 2 additions & 2 deletions src/metadata.ts
Expand Up @@ -28,7 +28,7 @@ export default class Metadata implements MetadataInterface {
* @param {string | Buffer} metadata xml
* @param {object} extraParse for custom metadata extractor
*/
constructor(xml: string | Buffer, extraParse = []) {
constructor(xml: string | Buffer, extraParse: any = []) {
this.xmlString = xml.toString();
this.meta = extract(this.xmlString, extraParse.concat([
{
Expand Down Expand Up @@ -134,7 +134,7 @@ export default class Metadata implements MetadataInterface {
* @return {string/object} location
*/
public getSingleLogoutService(binding: string | undefined): string | object {
if (isString(binding)) {
if (binding && isString(binding)) {
const bindType = namespace.binding[binding];
let singleLogoutService = this.meta.singleLogoutService;
if (!(singleLogoutService instanceof Array)) {
Expand Down
4 changes: 2 additions & 2 deletions src/schema-validator.ts
Expand Up @@ -7,7 +7,7 @@ enum SchemaValidators {
XMLLINT = 'node-xmllint'
}

interface SchemaValidator {
export interface SchemaValidator {
validate: (xml: string) => Promise<string>;
}

Expand All @@ -26,7 +26,7 @@ const getValidatorModule: GetValidatorModuleSpec = async () => {

const selectedValidator: string = moduleResolver(SchemaValidators.JAVAC)
|| moduleResolver(SchemaValidators.LIBXML)
|| moduleResolver(SchemaValidators.XMLLINT);
|| moduleResolver(SchemaValidators.XMLLINT) || "";

const xsd = 'saml-schema-protocol-2.0.xsd';

Expand Down
6 changes: 5 additions & 1 deletion src/utility.ts
Expand Up @@ -168,7 +168,7 @@ function getPublicKeyPemFromCertificate(x509Certificate: string) {
* @return {string} string in pem format
* If passphrase is used to protect the .pem content (recommend)
*/
export function readPrivateKey(keyString: string | Buffer, passphrase: string, isOutputString?: boolean) {
export function readPrivateKey(keyString: string | Buffer, passphrase: string | undefined, isOutputString?: boolean) {
return isString(passphrase) ? this.convertToString(pki.privateKeyToPem(pki.decryptRsaPrivateKey(String(keyString), passphrase)), isOutputString) : keyString;
}
/**
Expand All @@ -184,6 +184,10 @@ export function isNonEmptyArray(a) {
return Array.isArray(a) && a.length > 0;
}

export function notEmpty<TValue>(value: TValue | null | undefined): value is TValue {
return value !== null && value !== undefined;
}

const utility = {
isString,
base64Encode,
Expand Down
8 changes: 4 additions & 4 deletions src/validator.ts
Expand Up @@ -5,8 +5,8 @@ function verifyTime(utcNotBefore?: string, utcNotOnOrAfter?: string): boolean {
return true; // throw exception todo
}

let notBeforeLocal = null;
let notOnOrAfterLocal = null;
let notBeforeLocal: Date | null = null;
let notOnOrAfterLocal: Date | null = null;

if (utcNotBefore && !utcNotOnOrAfter) {
notBeforeLocal = new Date(utcNotBefore);
Expand All @@ -17,8 +17,8 @@ function verifyTime(utcNotBefore?: string, utcNotOnOrAfter?: string): boolean {
return now < notOnOrAfterLocal;
}

notBeforeLocal = new Date(utcNotBefore);
notOnOrAfterLocal = new Date(utcNotOnOrAfter);
notBeforeLocal = new Date(utcNotBefore!);
notOnOrAfterLocal = new Date(utcNotOnOrAfter!);
return +notBeforeLocal <= +now && now < notOnOrAfterLocal;
}

Expand Down
8 changes: 4 additions & 4 deletions test/flow.ts
Expand Up @@ -478,7 +478,7 @@ test('send login response with [custom template] encrypted signed assertion + si
test('idp sends a redirect logout request without signature and sp parses it', async t => {
const { id, context } = idp.createLogoutRequest(sp, 'redirect', { logoutNameID: 'user@esaml2.com' });
const query = url.parse(context).query;
t.is(query.includes('SAMLRequest='), true);
t.is(query!.includes('SAMLRequest='), true);
t.is(typeof id, 'string');
t.is(typeof context, 'string');
const originalURL = url.parse(context, true);
Expand All @@ -497,9 +497,9 @@ test('idp sends a redirect logout request without signature and sp parses it', a
test('idp sends a redirect logout request with signature and sp parses it', async t => {
const { id, context } = idp.createLogoutRequest(spWantLogoutReqSign, 'redirect', { logoutNameID: 'user@esaml2.com' });
const query = url.parse(context).query;
t.is(query.includes('SAMLRequest='), true);
t.is(query.includes('SigAlg='), true);
t.is(query.includes('Signature='), true);
t.is(query!.includes('SAMLRequest='), true);
t.is(query!.includes('SigAlg='), true);
t.is(query!.includes('Signature='), true);
t.is(typeof id, 'string');
t.is(typeof context, 'string');
const originalURL = url.parse(context, true);
Expand Down

0 comments on commit c92d9c7

Please sign in to comment.