samlify 2.11.0 → 2.13.0

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.
Files changed (83) hide show
  1. package/README.md +1 -1
  2. package/build/src/api.js +52 -3
  3. package/build/src/api.js.map +1 -1
  4. package/build/src/binding-post.js +236 -182
  5. package/build/src/binding-post.js.map +1 -1
  6. package/build/src/binding-redirect.js +303 -215
  7. package/build/src/binding-redirect.js.map +1 -1
  8. package/build/src/binding-simplesign.js +285 -137
  9. package/build/src/binding-simplesign.js.map +1 -1
  10. package/build/src/entity-idp.js +130 -47
  11. package/build/src/entity-idp.js.map +1 -1
  12. package/build/src/entity-sp.js +81 -39
  13. package/build/src/entity-sp.js.map +1 -1
  14. package/build/src/entity.js +100 -62
  15. package/build/src/entity.js.map +1 -1
  16. package/build/src/extractor.js +119 -155
  17. package/build/src/extractor.js.map +1 -1
  18. package/build/src/flow.js +100 -96
  19. package/build/src/flow.js.map +1 -1
  20. package/build/src/libsaml.js +318 -261
  21. package/build/src/libsaml.js.map +1 -1
  22. package/build/src/metadata-idp.js +60 -30
  23. package/build/src/metadata-idp.js.map +1 -1
  24. package/build/src/metadata-sp.js +51 -41
  25. package/build/src/metadata-sp.js.map +1 -1
  26. package/build/src/metadata.js +47 -43
  27. package/build/src/metadata.js.map +1 -1
  28. package/build/src/options.js +73 -0
  29. package/build/src/options.js.map +1 -0
  30. package/build/src/urn.js +28 -1
  31. package/build/src/urn.js.map +1 -1
  32. package/build/src/utility.js +165 -83
  33. package/build/src/utility.js.map +1 -1
  34. package/build/src/validator.js +27 -10
  35. package/build/src/validator.js.map +1 -1
  36. package/package.json +17 -7
  37. package/types/src/api.d.ts +33 -3
  38. package/types/src/binding-post.d.ts +67 -34
  39. package/types/src/binding-redirect.d.ts +58 -31
  40. package/types/src/binding-simplesign.d.ts +77 -21
  41. package/types/src/entity-idp.d.ts +40 -31
  42. package/types/src/entity-sp.d.ts +37 -27
  43. package/types/src/entity.d.ts +71 -77
  44. package/types/src/extractor.d.ts +31 -22
  45. package/types/src/flow.d.ts +24 -2
  46. package/types/src/libsaml.d.ts +172 -118
  47. package/types/src/metadata-idp.d.ts +27 -11
  48. package/types/src/metadata-sp.d.ts +29 -19
  49. package/types/src/metadata.d.ts +59 -34
  50. package/types/src/options.d.ts +37 -0
  51. package/types/src/types.d.ts +250 -24
  52. package/types/src/urn.d.ts +7 -0
  53. package/types/src/utility.d.ts +144 -89
  54. package/types/src/validator.d.ts +21 -0
  55. package/.circleci/config.yml +0 -98
  56. package/.editorconfig +0 -19
  57. package/.github/FUNDING.yml +0 -1
  58. package/.github/workflows/deploy-docs.yml +0 -56
  59. package/.pre-commit.sh +0 -15
  60. package/.snyk +0 -4
  61. package/Makefile +0 -25
  62. package/index.ts +0 -28
  63. package/src/api.ts +0 -36
  64. package/src/binding-post.ts +0 -336
  65. package/src/binding-redirect.ts +0 -335
  66. package/src/binding-simplesign.ts +0 -231
  67. package/src/entity-idp.ts +0 -145
  68. package/src/entity-sp.ts +0 -114
  69. package/src/entity.ts +0 -243
  70. package/src/extractor.ts +0 -399
  71. package/src/flow.ts +0 -469
  72. package/src/libsaml.ts +0 -777
  73. package/src/metadata-idp.ts +0 -146
  74. package/src/metadata-sp.ts +0 -203
  75. package/src/metadata.ts +0 -166
  76. package/src/types.ts +0 -127
  77. package/src/urn.ts +0 -210
  78. package/src/utility.ts +0 -231
  79. package/src/validator.ts +0 -44
  80. package/tsconfig.json +0 -41
  81. package/tslint.json +0 -35
  82. package/types.d.ts +0 -2
  83. package/vitest.config.ts +0 -12
package/src/entity.ts DELETED
@@ -1,243 +0,0 @@
1
- /**
2
- * @file entity.ts
3
- * @author tngan
4
- * @desc An abstraction for identity provider and service provider.
5
- */
6
- import { randomUUID } from 'crypto';
7
- import { isString, isNonEmptyArray } from './utility';
8
- import { namespace, wording, algorithms, messageConfigurations } from './urn';
9
- import IdpMetadata, { IdpMetadata as IdpMetadataConstructor } from './metadata-idp';
10
- import SpMetadata, { SpMetadata as SpMetadataConstructor } from './metadata-sp';
11
- import redirectBinding from './binding-redirect';
12
- import postBinding from './binding-post';
13
- import { MetadataIdpConstructor, MetadataSpConstructor, EntitySetting } from './types';
14
- import { flow, FlowResult } from './flow';
15
-
16
- const dataEncryptionAlgorithm = algorithms.encryption.data;
17
- const keyEncryptionAlgorithm = algorithms.encryption.key;
18
- const signatureAlgorithms = algorithms.signature;
19
- const messageSigningOrders = messageConfigurations.signingOrder;
20
-
21
- const defaultEntitySetting = {
22
- wantLogoutResponseSigned: false,
23
- messageSigningOrder: messageSigningOrders.SIGN_THEN_ENCRYPT,
24
- wantLogoutRequestSigned: false,
25
- allowCreate: false,
26
- isAssertionEncrypted: false,
27
- requestSignatureAlgorithm: signatureAlgorithms.RSA_SHA256,
28
- dataEncryptionAlgorithm: dataEncryptionAlgorithm.AES_256,
29
- keyEncryptionAlgorithm: keyEncryptionAlgorithm.RSA_OAEP_MGF1P,
30
- generateID: (): string => ('_' + randomUUID()),
31
- relayState: '',
32
- };
33
-
34
- export interface ESamlHttpRequest {
35
- query?: any;
36
- body?: any;
37
- octetString?: string;
38
- }
39
-
40
- export interface BindingContext {
41
- context: string;
42
- id: string;
43
- }
44
-
45
- export interface PostBindingContext extends BindingContext {
46
- relayState?: string;
47
- entityEndpoint: string;
48
- type: string;
49
- }
50
-
51
- export interface SimpleSignBindingContext extends PostBindingContext {
52
- sigAlg?: string;
53
- signature?: string;
54
- keyInfo?: string;
55
- }
56
-
57
- export interface SimpleSignComputedContext extends BindingContext {
58
- sigAlg?: string;
59
- signature?: string;
60
- }
61
-
62
- export interface ParseResult {
63
- samlContent: string;
64
- extract: any;
65
- sigAlg: string;
66
- }
67
-
68
- export type EntityConstructor = (MetadataIdpConstructor | MetadataSpConstructor)
69
- & { metadata?: string | Buffer };
70
-
71
- export default class Entity {
72
- entitySetting: EntitySetting;
73
- entityType: string;
74
- entityMeta: IdpMetadataConstructor | SpMetadataConstructor;
75
-
76
- /**
77
- * @param entitySetting
78
- * @param entityMeta is the entity metadata, deprecated after 2.0
79
- */
80
- constructor(entitySetting: EntityConstructor, entityType: 'idp' | 'sp') {
81
- this.entitySetting = Object.assign({}, defaultEntitySetting, entitySetting);
82
- const metadata = entitySetting.metadata || entitySetting;
83
- switch (entityType) {
84
- case 'idp':
85
- this.entityMeta = IdpMetadata(metadata);
86
- // setting with metadata has higher precedence
87
- this.entitySetting.wantAuthnRequestsSigned = this.entityMeta.isWantAuthnRequestsSigned();
88
- this.entitySetting.nameIDFormat = this.entityMeta.getNameIDFormat() || this.entitySetting.nameIDFormat;
89
- break;
90
- case 'sp':
91
- this.entityMeta = SpMetadata(metadata);
92
- // setting with metadata has higher precedence
93
- this.entitySetting.authnRequestsSigned = this.entityMeta.isAuthnRequestSigned();
94
- this.entitySetting.wantAssertionsSigned = this.entityMeta.isWantAssertionsSigned();
95
- this.entitySetting.nameIDFormat = this.entityMeta.getNameIDFormat() || this.entitySetting.nameIDFormat;
96
- break;
97
- default:
98
- throw new Error('ERR_UNDEFINED_ENTITY_TYPE');
99
- }
100
- }
101
-
102
- /**
103
- * @desc Returns the setting of entity
104
- * @return {object}
105
- */
106
- getEntitySetting() {
107
- return this.entitySetting;
108
- }
109
- /**
110
- * @desc Returns the xml string of entity metadata
111
- * @return {string}
112
- */
113
- getMetadata(): string {
114
- return this.entityMeta.getMetadata();
115
- }
116
-
117
- /**
118
- * @desc Exports the entity metadata into specified folder
119
- * @param {string} exportFile indicates the file name
120
- */
121
- exportMetadata(exportFile: string) {
122
- return this.entityMeta.exportMetadata(exportFile);
123
- }
124
-
125
- /** * @desc Verify fields with the one specified in metadata
126
- * @param {string/[string]} field is a string or an array of string indicating the field value in SAML message
127
- * @param {string} metaField is a string indicating the same field specified in metadata
128
- * @return {boolean} True/False
129
- */
130
- verifyFields(field: string | string[], metaField: string): boolean {
131
- if (isString(field)) {
132
- return field === metaField;
133
- }
134
- if (isNonEmptyArray(field)) {
135
- let res = true;
136
- (field as string[]).forEach(f => {
137
- if (f !== metaField) {
138
- res = false;
139
- return;
140
- }
141
- });
142
- return res;
143
- }
144
- return false;
145
- }
146
- /** @desc Generates the logout request for developers to design their own method
147
- * @param {ServiceProvider} sp object of service provider
148
- * @param {string} binding protocol binding
149
- * @param {object} user current logged user (e.g. user)
150
- * @param {string} relayState the URL to which to redirect the user when logout is complete
151
- * @param {function} customTagReplacement used when developers have their own login response template
152
- */
153
- createLogoutRequest(targetEntity, binding, user, relayState = '', customTagReplacement?): BindingContext | PostBindingContext {
154
- if (binding === wording.binding.redirect) {
155
- return redirectBinding.logoutRequestRedirectURL(user, {
156
- init: this,
157
- target: targetEntity,
158
- }, relayState, customTagReplacement);
159
- }
160
- if (binding === wording.binding.post) {
161
- const entityEndpoint = targetEntity.entityMeta.getSingleLogoutService(binding);
162
- const context = postBinding.base64LogoutRequest(user, "/*[local-name(.)='LogoutRequest']", { init: this, target: targetEntity }, customTagReplacement);
163
- return {
164
- ...context,
165
- relayState,
166
- entityEndpoint,
167
- type: 'SAMLRequest',
168
- };
169
- }
170
- // Will support artifact in the next release
171
- throw new Error('ERR_UNDEFINED_BINDING');
172
- }
173
-
174
- /**
175
- * @desc Generates the logout response for developers to design their own method
176
- * @param {IdentityProvider} idp object of identity provider
177
- * @param {object} requestInfo corresponding request, used to obtain the id
178
- * @param {string} relayState the URL to which to redirect the user when logout is complete.
179
- * @param {string} binding protocol binding
180
- * @param {function} customTagReplacement used when developers have their own login response template
181
- */
182
- createLogoutResponse(target, requestInfo, binding, relayState = '', customTagReplacement?): BindingContext | PostBindingContext {
183
- const protocol = namespace.binding[binding];
184
- if (protocol === namespace.binding.redirect) {
185
- return redirectBinding.logoutResponseRedirectURL(requestInfo, {
186
- init: this,
187
- target,
188
- }, relayState, customTagReplacement);
189
- }
190
- if (protocol === namespace.binding.post) {
191
- const context = postBinding.base64LogoutResponse(requestInfo, {
192
- init: this,
193
- target,
194
- }, customTagReplacement);
195
- return {
196
- ...context,
197
- relayState,
198
- entityEndpoint: target.entityMeta.getSingleLogoutService(binding),
199
- type: 'SAMLResponse',
200
- };
201
- }
202
- throw new Error('ERR_CREATE_LOGOUT_RESPONSE_UNDEFINED_BINDING');
203
- }
204
-
205
- /**
206
- * @desc Validation of the parsed the URL parameters
207
- * @param {IdentityProvider} idp object of identity provider
208
- * @param {string} binding protocol binding
209
- * @param {request} req request
210
- * @return {Promise}
211
- */
212
- parseLogoutRequest(from, binding, request: ESamlHttpRequest) {
213
- const self = this;
214
- return flow({
215
- from: from,
216
- self: self,
217
- type: 'logout',
218
- parserType: 'LogoutRequest',
219
- checkSignature: this.entitySetting.wantLogoutRequestSigned,
220
- binding: binding,
221
- request: request,
222
- });
223
- }
224
- /**
225
- * @desc Validation of the parsed the URL parameters
226
- * @param {object} config config for the parser
227
- * @param {string} binding protocol binding
228
- * @param {request} req request
229
- * @return {Promise}
230
- */
231
- parseLogoutResponse(from, binding, request: ESamlHttpRequest) {
232
- const self = this;
233
- return flow({
234
- from: from,
235
- self: self,
236
- type: 'logout',
237
- parserType: 'LogoutResponse',
238
- checkSignature: self.entitySetting.wantLogoutResponseSigned,
239
- binding: binding,
240
- request: request
241
- });
242
- }
243
- }
package/src/extractor.ts DELETED
@@ -1,399 +0,0 @@
1
- import { select, SelectedValue, SelectReturnType } from 'xpath';
2
- import { uniq, last, zipObject, notEmpty } from './utility';
3
-
4
- function toNodeArray(result: SelectReturnType): Node[] {
5
- if (Array.isArray(result)) return result;
6
- if (result != null && typeof result === 'object' && 'nodeType' in (result as object)) return [result as Node];
7
- return [];
8
- }
9
- import { getContext } from './api';
10
- import camelCase from 'camelcase';
11
-
12
- interface ExtractorField {
13
- key: string;
14
- localPath: string[] | string[][];
15
- attributes: string[];
16
- index?: string[];
17
- attributePath?: string[];
18
- context?: boolean;
19
- }
20
-
21
- export type ExtractorFields = ExtractorField[];
22
-
23
- function buildAbsoluteXPath(paths) {
24
- return paths.reduce((currentPath, name) => {
25
- let appendedPath = currentPath;
26
- const isWildcard = name.startsWith('~');
27
- if (isWildcard) {
28
- const pathName = name.replace('~', '');
29
- appendedPath = currentPath + `/*[contains(local-name(), '${pathName}')]`;
30
- }
31
- if (!isWildcard) {
32
- appendedPath = currentPath + `/*[local-name(.)='${name}']`;
33
- }
34
- return appendedPath;
35
- }, '');
36
- }
37
-
38
- function buildAttributeXPath(attributes) {
39
- if (attributes.length === 0) {
40
- return '/text()';
41
- }
42
- if (attributes.length === 1) {
43
- return `/@${attributes[0]}`;
44
- }
45
- const filters = attributes.map(attribute => `name()='${attribute}'`).join(' or ');
46
- return `/@*[${filters}]`;
47
- }
48
-
49
- export const loginRequestFields: ExtractorFields = [
50
- {
51
- key: 'request',
52
- localPath: ['AuthnRequest'],
53
- attributes: ['ID', 'IssueInstant', 'Destination', 'AssertionConsumerServiceURL']
54
- },
55
- {
56
- key: 'issuer',
57
- localPath: ['AuthnRequest', 'Issuer'],
58
- attributes: []
59
- },
60
- {
61
- key: 'nameIDPolicy',
62
- localPath: ['AuthnRequest', 'NameIDPolicy'],
63
- attributes: ['Format', 'AllowCreate']
64
- },
65
- {
66
- key: 'authnContextClassRef',
67
- localPath: ['AuthnRequest', 'AuthnContextClassRef'],
68
- attributes: []
69
- },
70
- {
71
- key: 'signature',
72
- localPath: ['AuthnRequest', 'Signature'],
73
- attributes: [],
74
- context: true
75
- }
76
- ];
77
-
78
- // support two-tiers status code
79
- export const loginResponseStatusFields = [
80
- {
81
- key: 'top',
82
- localPath: ['Response', 'Status', 'StatusCode'],
83
- attributes: ['Value'],
84
- },
85
- {
86
- key: 'second',
87
- localPath: ['Response', 'Status', 'StatusCode', 'StatusCode'],
88
- attributes: ['Value'],
89
- }
90
- ];
91
-
92
- // support two-tiers status code
93
- export const logoutResponseStatusFields = [
94
- {
95
- key: 'top',
96
- localPath: ['LogoutResponse', 'Status', 'StatusCode'],
97
- attributes: ['Value']
98
- },
99
- {
100
- key: 'second',
101
- localPath: ['LogoutResponse', 'Status', 'StatusCode', 'StatusCode'],
102
- attributes: ['Value'],
103
- }
104
- ];
105
-
106
- export const loginResponseFields: ((assertion: any) => ExtractorFields) = assertion => [
107
- {
108
- key: 'conditions',
109
- localPath: ['Assertion', 'Conditions'],
110
- attributes: ['NotBefore', 'NotOnOrAfter'],
111
- shortcut: assertion
112
- },
113
- {
114
- key: 'response',
115
- localPath: ['Response'],
116
- attributes: ['ID', 'IssueInstant', 'Destination', 'InResponseTo'],
117
- },
118
- {
119
- key: 'audience',
120
- localPath: ['Assertion', 'Conditions', 'AudienceRestriction', 'Audience'],
121
- attributes: [],
122
- shortcut: assertion
123
- },
124
- // {
125
- // key: 'issuer',
126
- // localPath: ['Response', 'Issuer'],
127
- // attributes: []
128
- // },
129
- {
130
- key: 'issuer',
131
- localPath: ['Assertion', 'Issuer'],
132
- attributes: [],
133
- shortcut: assertion
134
- },
135
- {
136
- key: 'nameID',
137
- localPath: ['Assertion', 'Subject', 'NameID'],
138
- attributes: [],
139
- shortcut: assertion
140
- },
141
- {
142
- key: 'sessionIndex',
143
- localPath: ['Assertion', 'AuthnStatement'],
144
- attributes: ['AuthnInstant', 'SessionNotOnOrAfter', 'SessionIndex'],
145
- shortcut: assertion
146
- },
147
- {
148
- key: 'attributes',
149
- localPath: ['Assertion', 'AttributeStatement', 'Attribute'],
150
- index: ['Name'],
151
- attributePath: ['AttributeValue'],
152
- attributes: [],
153
- shortcut: assertion
154
- }
155
- ];
156
-
157
- export const logoutRequestFields: ExtractorFields = [
158
- {
159
- key: 'request',
160
- localPath: ['LogoutRequest'],
161
- attributes: ['ID', 'IssueInstant', 'Destination']
162
- },
163
- {
164
- key: 'issuer',
165
- localPath: ['LogoutRequest', 'Issuer'],
166
- attributes: []
167
- },
168
- {
169
- key: 'nameID',
170
- localPath: ['LogoutRequest', 'NameID'],
171
- attributes: []
172
- },
173
- {
174
- key: 'sessionIndex',
175
- localPath: ['LogoutRequest', 'SessionIndex'],
176
- attributes: []
177
- },
178
- {
179
- key: 'signature',
180
- localPath: ['LogoutRequest', 'Signature'],
181
- attributes: [],
182
- context: true
183
- }
184
- ];
185
-
186
- export const logoutResponseFields: ExtractorFields = [
187
- {
188
- key: 'response',
189
- localPath: ['LogoutResponse'],
190
- attributes: ['ID', 'Destination', 'InResponseTo']
191
- },
192
- {
193
- key: 'issuer',
194
- localPath: ['LogoutResponse', 'Issuer'],
195
- attributes: []
196
- },
197
- {
198
- key: 'signature',
199
- localPath: ['LogoutResponse', 'Signature'],
200
- attributes: [],
201
- context: true
202
- }
203
- ];
204
-
205
- export function extract(context: string, fields) {
206
- const { dom } = getContext();
207
- const rootDoc = dom.parseFromString(context);
208
-
209
- return fields.reduce((result: any, field) => {
210
- // get essential fields
211
- const key = field.key;
212
- const localPath = field.localPath;
213
- const attributes = field.attributes;
214
- const isEntire = field.context;
215
- const shortcut = field.shortcut;
216
- // get optional fields
217
- const index = field.index;
218
- const attributePath = field.attributePath;
219
-
220
- // set allowing overriding if there is a shortcut injected
221
- let targetDoc = rootDoc;
222
-
223
- // if shortcut is used, then replace the doc
224
- // it's a design for overriding the doc used during runtime
225
- if (shortcut) {
226
- targetDoc = dom.parseFromString(shortcut);
227
- }
228
-
229
- // special case: multiple path
230
- /*
231
- {
232
- key: 'issuer',
233
- localPath: [
234
- ['Response', 'Issuer'],
235
- ['Response', 'Assertion', 'Issuer']
236
- ],
237
- attributes: []
238
- }
239
- */
240
- if (localPath.every(path => Array.isArray(path))) {
241
- const multiXPaths = localPath
242
- .map(path => {
243
- // not support attribute yet, so ignore it
244
- return `${buildAbsoluteXPath(path)}/text()`;
245
- })
246
- .join(' | ');
247
-
248
- return {
249
- ...result,
250
- [key]: uniq(toNodeArray(select(multiXPaths, targetDoc)).map((n: Node) => n.nodeValue).filter(notEmpty))
251
- };
252
- }
253
- // eo special case: multiple path
254
-
255
- const baseXPath = buildAbsoluteXPath(localPath);
256
- const attributeXPath = buildAttributeXPath(attributes);
257
-
258
- // special case: get attributes where some are in child, some are in parent
259
- /*
260
- {
261
- key: 'attributes',
262
- localPath: ['Response', 'Assertion', 'AttributeStatement', 'Attribute'],
263
- index: ['Name'],
264
- attributePath: ['AttributeValue'],
265
- attributes: []
266
- }
267
- */
268
- if (index && attributePath) {
269
- // find the index in localpath
270
- const indexPath = buildAttributeXPath(index);
271
- const fullLocalXPath = `${baseXPath}${indexPath}`;
272
- const parentNodes = toNodeArray(select(baseXPath, targetDoc));
273
- // [uid, mail, edupersonaffiliation], ready for aggregate
274
- const parentAttributes = toNodeArray(select(fullLocalXPath, targetDoc)).map((n: Attr) => n.value);
275
- // [attribute, attributevalue]
276
- const childXPath = buildAbsoluteXPath([last(localPath)].concat(attributePath));
277
- const childAttributeXPath = buildAttributeXPath(attributes);
278
- const fullChildXPath = `${childXPath}${childAttributeXPath}`;
279
- // [ 'test', 'test@example.com', [ 'users', 'examplerole1' ] ]
280
- const childAttributes = parentNodes.map(node => {
281
- const nodeDoc = dom.parseFromString(node.toString());
282
- if (attributes.length === 0) {
283
- const childValues = toNodeArray(select(fullChildXPath, nodeDoc)).map((n: Node) => n.nodeValue);
284
- if (childValues.length === 1) {
285
- return childValues[0];
286
- }
287
- return childValues;
288
- }
289
- if (attributes.length > 0) {
290
- const childValues = toNodeArray(select(fullChildXPath, nodeDoc)).map((n: Attr) => n.value);
291
- if (childValues.length === 1) {
292
- return childValues[0];
293
- }
294
- return childValues;
295
- }
296
- return null;
297
- });
298
- // aggregation
299
- const obj = zipObject(parentAttributes, childAttributes, false);
300
- return {
301
- ...result,
302
- [key]: obj
303
- };
304
-
305
- }
306
- // case: fetch entire content, only allow one existence
307
- /*
308
- {
309
- key: 'signature',
310
- localPath: ['AuthnRequest', 'Signature'],
311
- attributes: [],
312
- context: true
313
- }
314
- */
315
- if (isEntire) {
316
- const nodes = toNodeArray(select(baseXPath, targetDoc));
317
- let value: string | string[] | null = null;
318
- if (nodes.length === 1) {
319
- value = nodes[0].toString();
320
- }
321
- if (nodes.length > 1) {
322
- value = nodes.map(n => n.toString());
323
- }
324
- return {
325
- ...result,
326
- [key]: value
327
- };
328
- }
329
-
330
- // case: multiple attribute
331
- /*
332
- {
333
- key: 'nameIDPolicy',
334
- localPath: ['AuthnRequest', 'NameIDPolicy'],
335
- attributes: ['Format', 'AllowCreate']
336
- }
337
- */
338
- if (attributes.length > 1) {
339
- const baseNode = toNodeArray(select(baseXPath, targetDoc)).map(n => n.toString());
340
- const childXPath = `${buildAbsoluteXPath([last(localPath)])}${attributeXPath}`;
341
- const attributeValues = baseNode.map((node: string) => {
342
- const nodeDoc = dom.parseFromString(node);
343
- const values = toNodeArray(select(childXPath, nodeDoc)).reduce((r: any, n: Attr) => {
344
- r[camelCase(n.name, {locale: 'en-us'})] = n.value;
345
- return r;
346
- }, {});
347
- return values;
348
- });
349
- return {
350
- ...result,
351
- [key]: attributeValues.length === 1 ? attributeValues[0] : attributeValues
352
- };
353
- }
354
- // case: single attribute
355
- /*
356
- {
357
- key: 'statusCode',
358
- localPath: ['Response', 'Status', 'StatusCode'],
359
- attributes: ['Value'],
360
- }
361
- */
362
- if (attributes.length === 1) {
363
- const fullPath = `${baseXPath}${attributeXPath}`;
364
- const attributeValues = toNodeArray(select(fullPath, targetDoc)).map((n: Attr) => n.value);
365
- return {
366
- ...result,
367
- [key]: attributeValues[0]
368
- };
369
- }
370
- // case: zero attribute
371
- /*
372
- {
373
- key: 'issuer',
374
- localPath: ['AuthnRequest', 'Issuer'],
375
- attributes: []
376
- }
377
- */
378
- if (attributes.length === 0) {
379
- let attributeValue: SelectedValue[] | (string | null)[] | string | null = null;
380
- const nodes = toNodeArray(select(baseXPath, targetDoc));
381
- if (nodes.length === 1) {
382
- const fullPath = `string(${baseXPath}${attributeXPath})`;
383
- const strResult = select(fullPath, targetDoc);
384
- attributeValue = typeof strResult === 'string' ? strResult : strResult === null ? null : Array.isArray(strResult) ? strResult : null;
385
- }
386
- if (nodes.length > 1) {
387
- attributeValue = nodes.filter((n: Node) => n.firstChild)
388
- .map((n: Node) => n.firstChild!.nodeValue);
389
- }
390
- return {
391
- ...result,
392
- [key]: attributeValue
393
- };
394
- }
395
-
396
- return result;
397
- }, {});
398
-
399
- }