samlesa 2.12.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +19 -0
- package/.github/FUNDING.yml +1 -0
- package/.idea/compiler.xml +6 -0
- package/.idea/deployment.xml +14 -0
- package/.idea/inspectionProfiles/Project_Default.xml +6 -0
- package/.idea/jsLibraryMappings.xml +6 -0
- package/.idea/modules.xml +8 -0
- package/.idea/samlify.iml +12 -0
- package/.idea/vcs.xml +6 -0
- package/.pre-commit.sh +15 -0
- package/.snyk +8 -0
- package/.travis.yml +29 -0
- package/LICENSE +22 -0
- package/Makefile +25 -0
- package/README.md +84 -0
- package/build/.idea/workspace.xml +58 -0
- package/build/index.js +65 -0
- package/build/index.js.map +1 -0
- package/build/src/api.js +24 -0
- package/build/src/api.js.map +1 -0
- package/build/src/binding-post.js +369 -0
- package/build/src/binding-post.js.map +1 -0
- package/build/src/binding-redirect.js +333 -0
- package/build/src/binding-redirect.js.map +1 -0
- package/build/src/binding-simplesign.js +233 -0
- package/build/src/binding-simplesign.js.map +1 -0
- package/build/src/entity-idp.js +131 -0
- package/build/src/entity-idp.js.map +1 -0
- package/build/src/entity-sp.js +97 -0
- package/build/src/entity-sp.js.map +1 -0
- package/build/src/entity.js +236 -0
- package/build/src/entity.js.map +1 -0
- package/build/src/extractor.js +370 -0
- package/build/src/extractor.js.map +1 -0
- package/build/src/flow.js +320 -0
- package/build/src/flow.js.map +1 -0
- package/build/src/libsaml.js +642 -0
- package/build/src/libsaml.js.map +1 -0
- package/build/src/metadata-idp.js +128 -0
- package/build/src/metadata-idp.js.map +1 -0
- package/build/src/metadata-sp.js +232 -0
- package/build/src/metadata-sp.js.map +1 -0
- package/build/src/metadata.js +177 -0
- package/build/src/metadata.js.map +1 -0
- package/build/src/types.js +12 -0
- package/build/src/types.js.map +1 -0
- package/build/src/urn.js +213 -0
- package/build/src/urn.js.map +1 -0
- package/build/src/utility.js +249 -0
- package/build/src/utility.js.map +1 -0
- package/build/src/validator.js +27 -0
- package/build/src/validator.js.map +1 -0
- package/index.d.ts +10 -0
- package/index.js +19 -0
- package/index.js.map +1 -0
- package/index.ts +28 -0
- package/package.json +74 -0
- package/qodana.yaml +29 -0
- package/src/.idea/modules.xml +8 -0
- package/src/.idea/src.iml +12 -0
- package/src/.idea/vcs.xml +6 -0
- package/src/api.ts +36 -0
- package/src/binding-post.ts +338 -0
- package/src/binding-redirect.ts +331 -0
- package/src/binding-simplesign.ts +231 -0
- package/src/entity-idp.ts +145 -0
- package/src/entity-sp.ts +114 -0
- package/src/entity.ts +243 -0
- package/src/extractor.ts +392 -0
- package/src/flow.ts +467 -0
- package/src/libsaml.ts +786 -0
- package/src/metadata-idp.ts +146 -0
- package/src/metadata-sp.ts +268 -0
- package/src/metadata.ts +166 -0
- package/src/types.ts +153 -0
- package/src/urn.ts +211 -0
- package/src/utility.ts +248 -0
- package/src/validator.ts +44 -0
- package/tsconfig.json +38 -0
- package/tslint.json +35 -0
- package/types/index.d.ts +10 -0
- package/types/src/api.d.ts +13 -0
- package/types/src/binding-post.d.ts +46 -0
- package/types/src/binding-redirect.d.ts +52 -0
- package/types/src/binding-simplesign.d.ts +39 -0
- package/types/src/entity-idp.d.ts +42 -0
- package/types/src/entity-sp.d.ts +36 -0
- package/types/src/entity.d.ts +99 -0
- package/types/src/extractor.d.ts +25 -0
- package/types/src/flow.d.ts +6 -0
- package/types/src/libsaml.d.ts +210 -0
- package/types/src/metadata-idp.d.ts +24 -0
- package/types/src/metadata-sp.d.ts +36 -0
- package/types/src/metadata.d.ts +57 -0
- package/types/src/types.d.ts +127 -0
- package/types/src/urn.d.ts +194 -0
- package/types/src/utility.d.ts +134 -0
- package/types/src/validator.d.ts +3 -0
- package/types.d.ts +2 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file metadata-idp.ts
|
|
3
|
+
* @author tngan
|
|
4
|
+
* @desc Metadata of identity provider
|
|
5
|
+
*/
|
|
6
|
+
import Metadata, { MetadataInterface } from './metadata.js';
|
|
7
|
+
import { MetadataIdpOptions, MetadataIdpConstructor } from './types.js';
|
|
8
|
+
import { namespace } from './urn.js';
|
|
9
|
+
import libsaml from './libsaml.js';
|
|
10
|
+
import { castArrayOpt, isNonEmptyArray, isString } from './utility.js';
|
|
11
|
+
import xml from 'xml';
|
|
12
|
+
|
|
13
|
+
export interface IdpMetadataInterface extends MetadataInterface {
|
|
14
|
+
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/*
|
|
18
|
+
* @desc interface function
|
|
19
|
+
*/
|
|
20
|
+
export default function(meta: MetadataIdpConstructor) {
|
|
21
|
+
return new IdpMetadata(meta);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class IdpMetadata extends Metadata {
|
|
25
|
+
|
|
26
|
+
constructor(meta: MetadataIdpConstructor) {
|
|
27
|
+
|
|
28
|
+
const isFile = isString(meta) || meta instanceof Buffer;
|
|
29
|
+
|
|
30
|
+
if (!isFile) {
|
|
31
|
+
|
|
32
|
+
const {
|
|
33
|
+
entityID,
|
|
34
|
+
signingCert,
|
|
35
|
+
encryptCert,
|
|
36
|
+
wantAuthnRequestsSigned = false,
|
|
37
|
+
nameIDFormat = [],
|
|
38
|
+
singleSignOnService = [],
|
|
39
|
+
singleLogoutService = [],
|
|
40
|
+
} = meta as MetadataIdpOptions;
|
|
41
|
+
|
|
42
|
+
const IDPSSODescriptor: any[] = [{
|
|
43
|
+
_attr: {
|
|
44
|
+
WantAuthnRequestsSigned: String(wantAuthnRequestsSigned),
|
|
45
|
+
protocolSupportEnumeration: namespace.names.protocol,
|
|
46
|
+
},
|
|
47
|
+
}];
|
|
48
|
+
|
|
49
|
+
for(const cert of castArrayOpt(signingCert)) {
|
|
50
|
+
IDPSSODescriptor.push(libsaml.createKeySection('signing', cert));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
for(const cert of castArrayOpt(encryptCert)) {
|
|
54
|
+
IDPSSODescriptor.push(libsaml.createKeySection('encryption', cert));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (isNonEmptyArray(nameIDFormat)) {
|
|
58
|
+
nameIDFormat.forEach(f => IDPSSODescriptor.push({ NameIDFormat: f }));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (isNonEmptyArray(singleSignOnService)) {
|
|
62
|
+
singleSignOnService.forEach((a, indexCount) => {
|
|
63
|
+
const attr: any = {
|
|
64
|
+
Binding: a.Binding,
|
|
65
|
+
Location: a.Location,
|
|
66
|
+
};
|
|
67
|
+
if (a.isDefault) {
|
|
68
|
+
attr.isDefault = true;
|
|
69
|
+
}
|
|
70
|
+
IDPSSODescriptor.push({ SingleSignOnService: [{ _attr: attr }] });
|
|
71
|
+
});
|
|
72
|
+
} else {
|
|
73
|
+
throw new Error('ERR_IDP_METADATA_MISSING_SINGLE_SIGN_ON_SERVICE');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (isNonEmptyArray(singleLogoutService)) {
|
|
77
|
+
singleLogoutService.forEach((a, indexCount) => {
|
|
78
|
+
const attr: any = {};
|
|
79
|
+
if (a.isDefault) {
|
|
80
|
+
attr.isDefault = true;
|
|
81
|
+
}
|
|
82
|
+
attr.Binding = a.Binding;
|
|
83
|
+
attr.Location = a.Location;
|
|
84
|
+
IDPSSODescriptor.push({ SingleLogoutService: [{ _attr: attr }] });
|
|
85
|
+
});
|
|
86
|
+
} else {
|
|
87
|
+
console.warn('Construct identity provider - missing endpoint of SingleLogoutService');
|
|
88
|
+
}
|
|
89
|
+
// Create a new metadata by setting
|
|
90
|
+
meta = xml([{
|
|
91
|
+
EntityDescriptor: [{
|
|
92
|
+
_attr: {
|
|
93
|
+
'xmlns': namespace.names.metadata,
|
|
94
|
+
'xmlns:assertion': namespace.names.assertion,
|
|
95
|
+
'xmlns:ds': 'http://www.w3.org/2000/09/xmldsig#',
|
|
96
|
+
entityID,
|
|
97
|
+
},
|
|
98
|
+
}, { IDPSSODescriptor }],
|
|
99
|
+
}]);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
super(meta as string | Buffer, [
|
|
103
|
+
{
|
|
104
|
+
key: 'wantAuthnRequestsSigned',
|
|
105
|
+
localPath: ['EntityDescriptor', 'IDPSSODescriptor'],
|
|
106
|
+
attributes: ['WantAuthnRequestsSigned'],
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
key: 'singleSignOnService',
|
|
110
|
+
localPath: ['EntityDescriptor', 'IDPSSODescriptor', 'SingleSignOnService'],
|
|
111
|
+
index: ['Binding'],
|
|
112
|
+
attributePath: [],
|
|
113
|
+
attributes: ['Location']
|
|
114
|
+
},
|
|
115
|
+
]);
|
|
116
|
+
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* @desc Get the preference whether it wants a signed request
|
|
121
|
+
* @return {boolean} WantAuthnRequestsSigned
|
|
122
|
+
*/
|
|
123
|
+
isWantAuthnRequestsSigned(): boolean {
|
|
124
|
+
const was = this.meta.wantAuthnRequestsSigned;
|
|
125
|
+
if (was === undefined) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
return String(was) === 'true';
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* @desc Get the entity endpoint for single sign on service
|
|
133
|
+
* @param {string} binding protocol binding (e.g. redirect, post)
|
|
134
|
+
* @return {string/object} location
|
|
135
|
+
*/
|
|
136
|
+
getSingleSignOnService(binding: string): string | object {
|
|
137
|
+
if (isString(binding)) {
|
|
138
|
+
const bindName = namespace.binding[binding];
|
|
139
|
+
const service = this.meta.singleSignOnService[bindName];
|
|
140
|
+
if (service) {
|
|
141
|
+
return service;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return this.meta.singleSignOnService;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file metadata-sp.ts
|
|
3
|
+
* @author tngan
|
|
4
|
+
* @desc Metadata of service provider
|
|
5
|
+
*/
|
|
6
|
+
import Metadata, { MetadataInterface } from './metadata.js';
|
|
7
|
+
import { MetadataSpConstructor, MetadataSpOptions } from './types.js';
|
|
8
|
+
import { namespace, elementsOrder as order } from './urn.js';
|
|
9
|
+
import libsaml from './libsaml.js';
|
|
10
|
+
import { castArrayOpt, isNonEmptyArray, isString } from './utility.js';
|
|
11
|
+
import xml from 'xml';
|
|
12
|
+
import {AttrService,ServiceName,RequestedAttribute,AttributeConsumingService} from './types.js'
|
|
13
|
+
export interface SpMetadataInterface extends MetadataInterface {
|
|
14
|
+
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// https://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf (P.16, 18)
|
|
18
|
+
interface MetaElement {
|
|
19
|
+
KeyDescriptor?: any[];
|
|
20
|
+
NameIDFormat?: any[];
|
|
21
|
+
SingleLogoutService?: any[];
|
|
22
|
+
AssertionConsumerService?: any[];
|
|
23
|
+
AttributeConsumingService?: any[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/*
|
|
27
|
+
* @desc interface function
|
|
28
|
+
*/
|
|
29
|
+
export default function(meta: MetadataSpConstructor) {
|
|
30
|
+
return new SpMetadata(meta);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @desc SP Metadata is for creating Service Provider, provides a set of API to manage the actions in SP.
|
|
35
|
+
*/
|
|
36
|
+
export class SpMetadata extends Metadata {
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param {object/string} meta (either xml string or configuration in object)
|
|
40
|
+
* @return {object} prototypes including public functions
|
|
41
|
+
*/
|
|
42
|
+
constructor(meta: MetadataSpConstructor) {
|
|
43
|
+
|
|
44
|
+
const isFile = isString(meta) || meta instanceof Buffer;
|
|
45
|
+
|
|
46
|
+
// use object configuration instead of importing metadata file directly
|
|
47
|
+
if (!isFile) {
|
|
48
|
+
|
|
49
|
+
const {
|
|
50
|
+
elementsOrder = order.default,
|
|
51
|
+
entityID,
|
|
52
|
+
signingCert,
|
|
53
|
+
encryptCert,
|
|
54
|
+
authnRequestsSigned = false,
|
|
55
|
+
wantAssertionsSigned = false,
|
|
56
|
+
wantMessageSigned = false,
|
|
57
|
+
signatureConfig,
|
|
58
|
+
nameIDFormat = [],
|
|
59
|
+
singleLogoutService = [],
|
|
60
|
+
assertionConsumerService = [],
|
|
61
|
+
attributeConsumingService = []
|
|
62
|
+
} = meta as MetadataSpOptions;
|
|
63
|
+
|
|
64
|
+
const descriptors: MetaElement = {
|
|
65
|
+
KeyDescriptor: [],
|
|
66
|
+
NameIDFormat: [],
|
|
67
|
+
SingleLogoutService: [],
|
|
68
|
+
AssertionConsumerService: [],
|
|
69
|
+
AttributeConsumingService: [],
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const SPSSODescriptor: any[] = [{
|
|
73
|
+
_attr: {
|
|
74
|
+
AuthnRequestsSigned: String(authnRequestsSigned),
|
|
75
|
+
WantAssertionsSigned: String(wantAssertionsSigned),
|
|
76
|
+
protocolSupportEnumeration: namespace.names.protocol,
|
|
77
|
+
},
|
|
78
|
+
}];
|
|
79
|
+
|
|
80
|
+
if (wantMessageSigned && signatureConfig === undefined) {
|
|
81
|
+
console.warn('Construct service provider - missing signatureConfig');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
for(const cert of castArrayOpt(signingCert)) {
|
|
85
|
+
descriptors.KeyDescriptor!.push(libsaml.createKeySection('signing', cert).KeyDescriptor);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
for(const cert of castArrayOpt(encryptCert)) {
|
|
89
|
+
descriptors.KeyDescriptor!.push(libsaml.createKeySection('encryption', cert).KeyDescriptor);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (isNonEmptyArray(nameIDFormat)) {
|
|
93
|
+
nameIDFormat.forEach(f => descriptors.NameIDFormat!.push(f));
|
|
94
|
+
} else {
|
|
95
|
+
// default value
|
|
96
|
+
descriptors.NameIDFormat!.push(namespace.format.emailAddress);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (isNonEmptyArray(singleLogoutService)) {
|
|
100
|
+
singleLogoutService.forEach(a => {
|
|
101
|
+
const attr: any = {
|
|
102
|
+
Binding: a.Binding,
|
|
103
|
+
Location: a.Location,
|
|
104
|
+
};
|
|
105
|
+
if (a.isDefault) {
|
|
106
|
+
attr.isDefault = true;
|
|
107
|
+
}
|
|
108
|
+
descriptors.SingleLogoutService!.push([{ _attr: attr }]);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (isNonEmptyArray(assertionConsumerService)) {
|
|
113
|
+
let indexCount = 0;
|
|
114
|
+
assertionConsumerService.forEach(a => {
|
|
115
|
+
const attr: any = {
|
|
116
|
+
index: String(indexCount++),
|
|
117
|
+
Binding: a.Binding,
|
|
118
|
+
Location: a.Location,
|
|
119
|
+
};
|
|
120
|
+
if (a.isDefault) {
|
|
121
|
+
attr.isDefault = true;
|
|
122
|
+
}
|
|
123
|
+
descriptors.AssertionConsumerService!.push([{ _attr: attr }]);
|
|
124
|
+
});
|
|
125
|
+
} else {
|
|
126
|
+
console.warn('Missing endpoint of AssertionConsumerService');
|
|
127
|
+
}
|
|
128
|
+
// 修改原有处理逻辑
|
|
129
|
+
if (isNonEmptyArray(attributeConsumingService)) {
|
|
130
|
+
attributeConsumingService.forEach((service,index)=> {
|
|
131
|
+
// 1. 构建AttributeConsumingService主元素
|
|
132
|
+
let indexCount = 0;
|
|
133
|
+
let attrConsumingService: any[] = [{
|
|
134
|
+
_attr: {
|
|
135
|
+
index: String(index + 1),
|
|
136
|
+
}
|
|
137
|
+
}];
|
|
138
|
+
if (service.isDefault) {
|
|
139
|
+
attrConsumingService[0]._attr.isDefault = true;
|
|
140
|
+
}
|
|
141
|
+
// 2. 添加ServiceName子元素
|
|
142
|
+
if (isNonEmptyArray( service.serviceName)){
|
|
143
|
+
service.serviceName.forEach(({ value, lang }) => {
|
|
144
|
+
attrConsumingService.push({
|
|
145
|
+
ServiceName: [
|
|
146
|
+
{
|
|
147
|
+
_attr: lang ? { 'xml:lang': lang } : {},
|
|
148
|
+
},
|
|
149
|
+
value
|
|
150
|
+
]
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (isNonEmptyArray( service.serviceDescription)){
|
|
156
|
+
service.serviceDescription.forEach(({ value, lang }) => {
|
|
157
|
+
attrConsumingService.push({
|
|
158
|
+
ServiceDescription: [
|
|
159
|
+
{
|
|
160
|
+
_attr: lang ? { 'xml:lang': lang } : {},
|
|
161
|
+
},
|
|
162
|
+
value
|
|
163
|
+
]
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
// 3. 添加RequestedAttribute子元素
|
|
168
|
+
if (isNonEmptyArray(service.requestedAttributes)) {
|
|
169
|
+
service.requestedAttributes.forEach(attr => {
|
|
170
|
+
const requestedAttr: any = {
|
|
171
|
+
_attr: {
|
|
172
|
+
...(attr.isRequired && { isRequired: String(attr.isRequired) }),
|
|
173
|
+
Name: attr.name,
|
|
174
|
+
...(attr.friendlyName && { FriendlyName: attr.friendlyName }),
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
/* // 处理属性值白名单
|
|
178
|
+
if (isNonEmptyArray(attr.attributeValue)) {
|
|
179
|
+
requestedAttr[namespace.tags.attributeValue] = attr.attributeValue.map(val => ({
|
|
180
|
+
_: val
|
|
181
|
+
}));
|
|
182
|
+
}*/
|
|
183
|
+
attrConsumingService.push({
|
|
184
|
+
RequestedAttribute: [requestedAttr]
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// 4. 将完整元素加入描述符
|
|
190
|
+
descriptors.AttributeConsumingService!.push(attrConsumingService);
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// handle element order
|
|
195
|
+
const existedElements = elementsOrder.filter(name => isNonEmptyArray(descriptors[name]));
|
|
196
|
+
existedElements.forEach(name => {
|
|
197
|
+
descriptors[name].forEach(e => SPSSODescriptor.push({ [name]: e }));
|
|
198
|
+
});
|
|
199
|
+
// Re-assign the meta reference as a XML string|Buffer for use with the parent constructor
|
|
200
|
+
meta = xml([{
|
|
201
|
+
EntityDescriptor: [{
|
|
202
|
+
_attr: {
|
|
203
|
+
entityID,
|
|
204
|
+
'xmlns': namespace.names.metadata,
|
|
205
|
+
'xmlns:assertion': namespace.names.assertion,
|
|
206
|
+
'xmlns:ds': 'http://www.w3.org/2000/09/xmldsig#',
|
|
207
|
+
},
|
|
208
|
+
}, { SPSSODescriptor }],
|
|
209
|
+
}]);
|
|
210
|
+
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Use the re-assigned meta object reference here
|
|
214
|
+
super(meta as string | Buffer, [
|
|
215
|
+
{
|
|
216
|
+
key: 'spSSODescriptor',
|
|
217
|
+
localPath: ['EntityDescriptor', 'SPSSODescriptor'],
|
|
218
|
+
attributes: ['WantAssertionsSigned', 'AuthnRequestsSigned'],
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
key: 'assertionConsumerService',
|
|
222
|
+
localPath: ['EntityDescriptor', 'SPSSODescriptor', 'AssertionConsumerService'],
|
|
223
|
+
attributes: ['Binding', 'Location', 'isDefault', 'index'],
|
|
224
|
+
}
|
|
225
|
+
]);
|
|
226
|
+
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* @desc Get the preference whether it wants a signed assertion response
|
|
231
|
+
* @return {boolean} Wantassertionssigned
|
|
232
|
+
*/
|
|
233
|
+
public isWantAssertionsSigned(): boolean {
|
|
234
|
+
return this.meta.spSSODescriptor.wantAssertionsSigned === 'true';
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* @desc Get the preference whether it signs request
|
|
238
|
+
* @return {boolean} Authnrequestssigned
|
|
239
|
+
*/
|
|
240
|
+
public isAuthnRequestSigned(): boolean {
|
|
241
|
+
return this.meta.spSSODescriptor.authnRequestsSigned === 'true';
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* @desc Get the entity endpoint for assertion consumer service
|
|
245
|
+
* @param {string} binding protocol binding (e.g. redirect, post)
|
|
246
|
+
* @return {string/[string]} URL of endpoint(s)
|
|
247
|
+
*/
|
|
248
|
+
public getAssertionConsumerService(binding: string): string | string[] {
|
|
249
|
+
if (isString(binding)) {
|
|
250
|
+
let location;
|
|
251
|
+
const bindName = namespace.binding[binding];
|
|
252
|
+
if (isNonEmptyArray(this.meta.assertionConsumerService)) {
|
|
253
|
+
this.meta.assertionConsumerService.forEach(obj => {
|
|
254
|
+
if (obj.binding === bindName) {
|
|
255
|
+
location = obj.location;
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
} else {
|
|
260
|
+
if (this.meta.assertionConsumerService.binding === bindName) {
|
|
261
|
+
location = this.meta.assertionConsumerService.location;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return location;
|
|
265
|
+
}
|
|
266
|
+
return this.meta.assertionConsumerService;
|
|
267
|
+
}
|
|
268
|
+
}
|
package/src/metadata.ts
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file metadata.ts
|
|
3
|
+
* @author tngan
|
|
4
|
+
* @desc An abstraction for metadata of identity provider and service provider
|
|
5
|
+
*/
|
|
6
|
+
import * as fs from 'fs';
|
|
7
|
+
import { namespace } from './urn.js';
|
|
8
|
+
import { extract } from './extractor.js';
|
|
9
|
+
import { isString } from './utility.js';
|
|
10
|
+
|
|
11
|
+
export interface MetadataInterface {
|
|
12
|
+
xmlString: string;
|
|
13
|
+
getMetadata: () => string;
|
|
14
|
+
exportMetadata: (exportFile: string) => void;
|
|
15
|
+
getEntityID: () => string;
|
|
16
|
+
getX509Certificate: (certType: string) => string | string[];
|
|
17
|
+
getNameIDFormat: () => any[];
|
|
18
|
+
getSingleLogoutService: (binding: string | undefined) => string | object;
|
|
19
|
+
getSupportBindings: (services: string[]) => string[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export default class Metadata implements MetadataInterface {
|
|
23
|
+
|
|
24
|
+
xmlString: string;
|
|
25
|
+
meta: any;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param {string | Buffer} xml
|
|
29
|
+
* @param {object} extraParse for custom metadata extractor
|
|
30
|
+
*/
|
|
31
|
+
constructor(xml: string | Buffer, extraParse: any = []) {
|
|
32
|
+
this.xmlString = xml.toString();
|
|
33
|
+
this.meta = extract(this.xmlString, extraParse.concat([
|
|
34
|
+
{
|
|
35
|
+
key: 'entityDescriptor',
|
|
36
|
+
localPath: ['EntityDescriptor'],
|
|
37
|
+
attributes: [],
|
|
38
|
+
context: true
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
key: 'entityID',
|
|
42
|
+
localPath: ['EntityDescriptor'],
|
|
43
|
+
attributes: ['entityID']
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
// shared certificate for both encryption and signing
|
|
47
|
+
key: 'sharedCertificate',
|
|
48
|
+
localPath: ['EntityDescriptor', '~SSODescriptor', 'KeyDescriptor', 'KeyInfo', 'X509Data', 'X509Certificate'],
|
|
49
|
+
attributes: []
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
// explicit certificate declaration for encryption and signing
|
|
53
|
+
key: 'certificate',
|
|
54
|
+
localPath: ['EntityDescriptor', '~SSODescriptor', 'KeyDescriptor'],
|
|
55
|
+
index: ['use'],
|
|
56
|
+
attributePath: ['KeyInfo', 'X509Data', 'X509Certificate'],
|
|
57
|
+
attributes: []
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
key: 'singleLogoutService',
|
|
61
|
+
localPath: ['EntityDescriptor', '~SSODescriptor', 'SingleLogoutService'],
|
|
62
|
+
attributes: ['Binding', 'Location']
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
key: 'nameIDFormat',
|
|
66
|
+
localPath: ['EntityDescriptor', '~SSODescriptor', 'NameIDFormat'],
|
|
67
|
+
attributes: [],
|
|
68
|
+
}
|
|
69
|
+
]));
|
|
70
|
+
|
|
71
|
+
// get shared certificate
|
|
72
|
+
const sharedCertificate = this.meta.sharedCertificate;
|
|
73
|
+
if (typeof sharedCertificate === 'string') {
|
|
74
|
+
this.meta.certificate = {
|
|
75
|
+
signing: sharedCertificate,
|
|
76
|
+
encryption: sharedCertificate
|
|
77
|
+
};
|
|
78
|
+
delete this.meta.sharedCertificate;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (
|
|
82
|
+
Array.isArray(this.meta.entityDescriptor) &&
|
|
83
|
+
this.meta.entityDescriptor.length > 1
|
|
84
|
+
) {
|
|
85
|
+
throw new Error('ERR_MULTIPLE_METADATA_ENTITYDESCRIPTOR');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* @desc Get the metadata in xml format
|
|
92
|
+
* @return {string} metadata in xml format
|
|
93
|
+
*/
|
|
94
|
+
public getMetadata(): string {
|
|
95
|
+
return this.xmlString;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* @desc Export the metadata to specific file
|
|
100
|
+
* @param {string} exportFile is the output file path
|
|
101
|
+
*/
|
|
102
|
+
public exportMetadata(exportFile: string): void {
|
|
103
|
+
fs.writeFileSync(exportFile, this.xmlString);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* @desc Get the entityID in metadata
|
|
108
|
+
* @return {string} entityID
|
|
109
|
+
*/
|
|
110
|
+
public getEntityID(): string {
|
|
111
|
+
return this.meta.entityID;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* @desc Get the x509 certificate declared in entity metadata
|
|
116
|
+
* @param {string} use declares the type of certificate
|
|
117
|
+
* @return {string} certificate in string format
|
|
118
|
+
*/
|
|
119
|
+
public getX509Certificate(use: string) {
|
|
120
|
+
return this.meta.certificate[use] || null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* @desc Get the support NameID format declared in entity metadata
|
|
125
|
+
* @return {array} support NameID format
|
|
126
|
+
*/
|
|
127
|
+
public getNameIDFormat(): any {
|
|
128
|
+
return this.meta.nameIDFormat;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* @desc Get the entity endpoint for single logout service
|
|
133
|
+
* @param {string} binding e.g. redirect, post
|
|
134
|
+
* @return {string/object} location
|
|
135
|
+
*/
|
|
136
|
+
public getSingleLogoutService(binding: string | undefined): string | object {
|
|
137
|
+
if (binding && isString(binding)) {
|
|
138
|
+
const bindType = namespace.binding[binding];
|
|
139
|
+
let singleLogoutService = this.meta.singleLogoutService;
|
|
140
|
+
if (!(singleLogoutService instanceof Array)) {
|
|
141
|
+
singleLogoutService = [singleLogoutService];
|
|
142
|
+
}
|
|
143
|
+
const service = singleLogoutService.find(obj => obj.binding === bindType);
|
|
144
|
+
if (service) {
|
|
145
|
+
return service.location;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return this.meta.singleLogoutService;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* @desc Get the support bindings
|
|
153
|
+
* @param {[string]} services
|
|
154
|
+
* @return {[string]} support bindings
|
|
155
|
+
*/
|
|
156
|
+
public getSupportBindings(services: string[]): string[] {
|
|
157
|
+
let supportBindings = [];
|
|
158
|
+
if (services) {
|
|
159
|
+
supportBindings = services.reduce((acc: any, service) => {
|
|
160
|
+
const supportBinding = Object.keys(service)[0];
|
|
161
|
+
return acc.push(supportBinding);
|
|
162
|
+
}, []);
|
|
163
|
+
}
|
|
164
|
+
return supportBindings;
|
|
165
|
+
}
|
|
166
|
+
}
|