From 43cc309cb168c01514fecc7a3239f99a38be9ece Mon Sep 17 00:00:00 2001 From: "Ismail H. Ayaz" Date: Fri, 27 Jan 2023 19:21:50 +0300 Subject: [PATCH] feat: better metadatas --- index.ts | 3 +- src/metadata-idp.ts | 85 ++++++++++++++++++++++++++++++++++++++------- src/metadata-sp.ts | 7 ++++ src/types.ts | 28 +++++++++------ 4 files changed, 100 insertions(+), 23 deletions(-) diff --git a/index.ts b/index.ts index edda52b2..4969eec8 100644 --- a/index.ts +++ b/index.ts @@ -2,6 +2,7 @@ import IdentityProvider, { IdentityProvider as IdentityProviderInstance } from './src/entity-idp'; import ServiceProvider, { ServiceProvider as ServiceProviderInstance } from './src/entity-sp'; +export { default as Metadata } from './src/metadata'; export { default as IdPMetadata } from './src/metadata-idp'; export { default as SPMetadata } from './src/metadata-sp'; export { default as Utility } from './src/utility'; @@ -24,4 +25,4 @@ export { ServiceProviderInstance, // set context setSchemaValidator -}; \ No newline at end of file +}; diff --git a/src/metadata-idp.ts b/src/metadata-idp.ts index 280e1180..443e7380 100644 --- a/src/metadata-idp.ts +++ b/src/metadata-idp.ts @@ -5,7 +5,7 @@ */ import Metadata, { MetadataInterface } from './metadata'; import { MetadataIdpOptions, MetadataIdpConstructor } from './types'; -import { namespace } from './urn'; +import {elementsOrder as order, namespace} from './urn'; import libsaml from './libsaml'; import { castArrayOpt, isNonEmptyArray, isString } from './utility'; import xml from 'xml'; @@ -37,25 +37,42 @@ export class IdpMetadata extends Metadata { nameIDFormat = [], singleSignOnService = [], singleLogoutService = [], + organization, + technicalContact, + supportContact, + elementsOrder = order.default, + customAttributes= [] } = meta as MetadataIdpOptions; - const IDPSSODescriptor: any[] = [{ + const descriptors = { + KeyDescriptor: [], + SingleLogoutService: [], + NameIDFormat: [], + SingleSignOnService: [], + AssertionConsumerService: [], + AttributeConsumingService: [], + CustomAttributes: [] + } as Record; + + const IDPSSODescriptor: any[] = [ { _attr: { WantAuthnRequestsSigned: String(wantAuthnRequestsSigned), protocolSupportEnumeration: namespace.names.protocol, }, - }]; + } ]; + for(const cert of castArrayOpt(signingCert)) { - IDPSSODescriptor.push(libsaml.createKeySection('signing', cert)); + descriptors.KeyDescriptor!.push(libsaml.createKeySection('signing', cert).KeyDescriptor); } for(const cert of castArrayOpt(encryptCert)) { - IDPSSODescriptor.push(libsaml.createKeySection('encryption', cert)); + descriptors.KeyDescriptor!.push(libsaml.createKeySection('encryption', cert).KeyDescriptor); } + if (isNonEmptyArray(nameIDFormat)) { - nameIDFormat.forEach(f => IDPSSODescriptor.push({ NameIDFormat: f })); + nameIDFormat.forEach(f => descriptors.NameIDFormat!.push(f)); } if (isNonEmptyArray(singleSignOnService)) { @@ -67,7 +84,7 @@ export class IdpMetadata extends Metadata { if (a.isDefault) { attr.isDefault = true; } - IDPSSODescriptor.push({ SingleSignOnService: [{ _attr: attr }] }); + descriptors.SingleSignOnService!.push([ { _attr: attr } ]); }); } else { throw new Error('ERR_IDP_METADATA_MISSING_SINGLE_SIGN_ON_SERVICE'); @@ -81,22 +98,66 @@ export class IdpMetadata extends Metadata { } attr.Binding = a.Binding; attr.Location = a.Location; - IDPSSODescriptor.push({ SingleLogoutService: [{ _attr: attr }] }); + descriptors.SingleLogoutService!.push([ { _attr: attr } ]); }); } else { console.warn('Construct identity provider - missing endpoint of SingleLogoutService'); } + + + // handle element order + const existedElements = elementsOrder.filter(name => isNonEmptyArray(descriptors[name])); + existedElements.forEach(name => { + descriptors[name].forEach(e => IDPSSODescriptor.push({ [name]: e })); + }); + + if (isNonEmptyArray(customAttributes)){ + customAttributes.forEach(attr => { + IDPSSODescriptor.push({ [attr.name || 'Attribute']: [ { _attr: attr._attr || {} }, attr.value ] }); + }); + } + + const OrgDescriptor = organization ? { + Organization: [ + { _attr: {} }, + organization.name && { OrganizationName: [ { _attr: { 'xml:lang': 'en-US' } }, organization.name ] } , + organization.displayName && { OrganizationDisplayName: [ { _attr: { 'xml:lang': 'en-US' } },organization.displayName ] } , + organization.url && { OrganizationURL: [ { _attr: { 'xml:lang': 'en-US' } },organization.url ] } + ].filter(v => !!v) + } : {}; + + const TechnicalContactDescriptor = technicalContact ? { + ContactPerson: [ + { + _attr: { contactType: 'technical' } + }, + technicalContact.name && { GivenName: technicalContact.name }, + technicalContact.email && { EmailAddress: technicalContact.email } + ].filter(v => !!v) + } : {}; + + const SupportContactDescriptor = supportContact ? { + ContactPerson: [ + { + _attr: { contactType: 'support' } + }, + supportContact.name && { GivenName: supportContact.name }, + supportContact.email && { EmailAddress: supportContact.email } + ].filter(v => !!v) + } : {}; // Create a new metadata by setting - meta = xml([{ - EntityDescriptor: [{ + meta = xml([ { + EntityDescriptor: [ { _attr: { 'xmlns': namespace.names.metadata, 'xmlns:assertion': namespace.names.assertion, 'xmlns:ds': 'http://www.w3.org/2000/09/xmldsig#', entityID, }, - }, { IDPSSODescriptor }], - }]); + }, { + IDPSSODescriptor, + }, OrgDescriptor, TechnicalContactDescriptor, SupportContactDescriptor ], + } ]); } super(meta as string | Buffer, [ diff --git a/src/metadata-sp.ts b/src/metadata-sp.ts index a6fc20e8..2a1c7345 100644 --- a/src/metadata-sp.ts +++ b/src/metadata-sp.ts @@ -57,6 +57,7 @@ export class SpMetadata extends Metadata { signatureConfig, nameIDFormat = [], singleLogoutService = [], + customAttributes = [], assertionConsumerService = [], } = meta as MetadataSpOptions; @@ -131,6 +132,12 @@ export class SpMetadata extends Metadata { descriptors[name].forEach(e => SPSSODescriptor.push({ [name]: e })); }); + if (isNonEmptyArray(customAttributes)){ + customAttributes.forEach(attr => { + SPSSODescriptor.push({ [attr.name || 'Attribute']: [ { _attr: attr._attr || {} }, attr.value ] }); + }); + } + // Re-assign the meta reference as a XML string|Buffer for use with the parent constructor meta = xml([{ EntityDescriptor: [{ diff --git a/src/types.ts b/src/types.ts index af8d3414..4d84e929 100644 --- a/src/types.ts +++ b/src/types.ts @@ -14,34 +14,42 @@ type SSOService = { Location: string; }; -export interface MetadataIdpOptions { +interface MetadataOptions { entityID?: string; signingCert?: string | Buffer | (string | Buffer)[]; encryptCert?: string | Buffer | (string | Buffer)[]; - wantAuthnRequestsSigned?: boolean; nameIDFormat?: string[]; singleSignOnService?: SSOService[]; singleLogoutService?: SSOService[]; + elementsOrder?: string[]; + customAttributes?: { + _attr?: Record, + name?: string, + value?: string + }[] +} +export interface MetadataIdpOptions extends MetadataOptions { + wantAuthnRequestsSigned?: boolean; requestSignatureAlgorithm?: string; + organization?: { + name?: string, + displayName?: string, + url?: string + }, + technicalContact?: { name: string, email: string }, + supportContact?: { name: string, email: string }, } export type MetadataIdpConstructor = | MetadataIdpOptions | MetadataFile; -export interface MetadataSpOptions { - entityID?: string; - signingCert?: string | Buffer | (string | Buffer)[]; - encryptCert?: string | Buffer | (string | Buffer)[]; +export interface MetadataSpOptions extends MetadataOptions { authnRequestsSigned?: boolean; wantAssertionsSigned?: boolean; wantMessageSigned?: boolean; signatureConfig?: { [key: string]: any }; - nameIDFormat?: string[]; - singleSignOnService?: SSOService[]; - singleLogoutService?: SSOService[]; assertionConsumerService?: SSOService[]; - elementsOrder?: string[]; } export type MetadataSpConstructor =