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
package/src/extractor.ts
ADDED
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
import { select, SelectedValue } from 'xpath';
|
|
2
|
+
import { uniq, last, zipObject, notEmpty } from './utility.js';
|
|
3
|
+
import { getContext } from './api.js';
|
|
4
|
+
import camelCase from 'camelcase';
|
|
5
|
+
|
|
6
|
+
interface ExtractorField {
|
|
7
|
+
key: string;
|
|
8
|
+
localPath: string[] | string[][];
|
|
9
|
+
attributes: string[];
|
|
10
|
+
index?: string[];
|
|
11
|
+
attributePath?: string[];
|
|
12
|
+
context?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type ExtractorFields = ExtractorField[];
|
|
16
|
+
|
|
17
|
+
function buildAbsoluteXPath(paths) {
|
|
18
|
+
return paths.reduce((currentPath, name) => {
|
|
19
|
+
let appendedPath = currentPath;
|
|
20
|
+
const isWildcard = name.startsWith('~');
|
|
21
|
+
if (isWildcard) {
|
|
22
|
+
const pathName = name.replace('~', '');
|
|
23
|
+
appendedPath = currentPath + `/*[contains(local-name(), '${pathName}')]`;
|
|
24
|
+
}
|
|
25
|
+
if (!isWildcard) {
|
|
26
|
+
appendedPath = currentPath + `/*[local-name(.)='${name}']`;
|
|
27
|
+
}
|
|
28
|
+
return appendedPath;
|
|
29
|
+
}, '');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function buildAttributeXPath(attributes) {
|
|
33
|
+
if (attributes.length === 0) {
|
|
34
|
+
return '/text()';
|
|
35
|
+
}
|
|
36
|
+
if (attributes.length === 1) {
|
|
37
|
+
return `/@${attributes[0]}`;
|
|
38
|
+
}
|
|
39
|
+
const filters = attributes.map(attribute => `name()='${attribute}'`).join(' or ');
|
|
40
|
+
return `/@*[${filters}]`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const loginRequestFields: ExtractorFields = [
|
|
44
|
+
{
|
|
45
|
+
key: 'request',
|
|
46
|
+
localPath: ['AuthnRequest'],
|
|
47
|
+
attributes: ['ID', 'IssueInstant', 'Destination', 'AssertionConsumerServiceURL']
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
key: 'issuer',
|
|
51
|
+
localPath: ['AuthnRequest', 'Issuer'],
|
|
52
|
+
attributes: []
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
key: 'nameIDPolicy',
|
|
56
|
+
localPath: ['AuthnRequest', 'NameIDPolicy'],
|
|
57
|
+
attributes: ['Format', 'AllowCreate']
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
key: 'authnContextClassRef',
|
|
61
|
+
localPath: ['AuthnRequest', 'AuthnContextClassRef'],
|
|
62
|
+
attributes: []
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
key: 'signature',
|
|
66
|
+
localPath: ['AuthnRequest', 'Signature'],
|
|
67
|
+
attributes: [],
|
|
68
|
+
context: true
|
|
69
|
+
}
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
// support two-tiers status code
|
|
73
|
+
export const loginResponseStatusFields = [
|
|
74
|
+
{
|
|
75
|
+
key: 'top',
|
|
76
|
+
localPath: ['Response', 'Status', 'StatusCode'],
|
|
77
|
+
attributes: ['Value'],
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
key: 'second',
|
|
81
|
+
localPath: ['Response', 'Status', 'StatusCode', 'StatusCode'],
|
|
82
|
+
attributes: ['Value'],
|
|
83
|
+
}
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
// support two-tiers status code
|
|
87
|
+
export const logoutResponseStatusFields = [
|
|
88
|
+
{
|
|
89
|
+
key: 'top',
|
|
90
|
+
localPath: ['LogoutResponse', 'Status', 'StatusCode'],
|
|
91
|
+
attributes: ['Value']
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
key: 'second',
|
|
95
|
+
localPath: ['LogoutResponse', 'Status', 'StatusCode', 'StatusCode'],
|
|
96
|
+
attributes: ['Value'],
|
|
97
|
+
}
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
export const loginResponseFields: ((assertion: any) => ExtractorFields) = assertion => [
|
|
101
|
+
{
|
|
102
|
+
key: 'conditions',
|
|
103
|
+
localPath: ['Assertion', 'Conditions'],
|
|
104
|
+
attributes: ['NotBefore', 'NotOnOrAfter'],
|
|
105
|
+
shortcut: assertion
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
key: 'response',
|
|
109
|
+
localPath: ['Response'],
|
|
110
|
+
attributes: ['ID', 'IssueInstant', 'Destination', 'InResponseTo'],
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
key: 'audience',
|
|
114
|
+
localPath: ['Assertion', 'Conditions', 'AudienceRestriction', 'Audience'],
|
|
115
|
+
attributes: [],
|
|
116
|
+
shortcut: assertion
|
|
117
|
+
},
|
|
118
|
+
// {
|
|
119
|
+
// key: 'issuer',
|
|
120
|
+
// localPath: ['Response', 'Issuer'],
|
|
121
|
+
// attributes: []
|
|
122
|
+
// },
|
|
123
|
+
{
|
|
124
|
+
key: 'issuer',
|
|
125
|
+
localPath: ['Assertion', 'Issuer'],
|
|
126
|
+
attributes: [],
|
|
127
|
+
shortcut: assertion
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
key: 'nameID',
|
|
131
|
+
localPath: ['Assertion', 'Subject', 'NameID'],
|
|
132
|
+
attributes: [],
|
|
133
|
+
shortcut: assertion
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
key: 'sessionIndex',
|
|
137
|
+
localPath: ['Assertion', 'AuthnStatement'],
|
|
138
|
+
attributes: ['AuthnInstant', 'SessionNotOnOrAfter', 'SessionIndex'],
|
|
139
|
+
shortcut: assertion
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
key: 'attributes',
|
|
143
|
+
localPath: ['Assertion', 'AttributeStatement', 'Attribute'],
|
|
144
|
+
index: ['Name'],
|
|
145
|
+
attributePath: ['AttributeValue'],
|
|
146
|
+
attributes: [],
|
|
147
|
+
shortcut: assertion
|
|
148
|
+
}
|
|
149
|
+
];
|
|
150
|
+
|
|
151
|
+
export const logoutRequestFields: ExtractorFields = [
|
|
152
|
+
{
|
|
153
|
+
key: 'request',
|
|
154
|
+
localPath: ['LogoutRequest'],
|
|
155
|
+
attributes: ['ID', 'IssueInstant', 'Destination']
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
key: 'issuer',
|
|
159
|
+
localPath: ['LogoutRequest', 'Issuer'],
|
|
160
|
+
attributes: []
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
key: 'nameID',
|
|
164
|
+
localPath: ['LogoutRequest', 'NameID'],
|
|
165
|
+
attributes: []
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
key: 'sessionIndex',
|
|
169
|
+
localPath: ['LogoutRequest', 'SessionIndex'],
|
|
170
|
+
attributes: []
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
key: 'signature',
|
|
174
|
+
localPath: ['LogoutRequest', 'Signature'],
|
|
175
|
+
attributes: [],
|
|
176
|
+
context: true
|
|
177
|
+
}
|
|
178
|
+
];
|
|
179
|
+
|
|
180
|
+
export const logoutResponseFields: ExtractorFields = [
|
|
181
|
+
{
|
|
182
|
+
key: 'response',
|
|
183
|
+
localPath: ['LogoutResponse'],
|
|
184
|
+
attributes: ['ID', 'Destination', 'InResponseTo']
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
key: 'issuer',
|
|
188
|
+
localPath: ['LogoutResponse', 'Issuer'],
|
|
189
|
+
attributes: []
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
key: 'signature',
|
|
193
|
+
localPath: ['LogoutResponse', 'Signature'],
|
|
194
|
+
attributes: [],
|
|
195
|
+
context: true
|
|
196
|
+
}
|
|
197
|
+
];
|
|
198
|
+
|
|
199
|
+
export function extract(context: string, fields) {
|
|
200
|
+
const { dom } = getContext();
|
|
201
|
+
const rootDoc = dom.parseFromString(context);
|
|
202
|
+
|
|
203
|
+
return fields.reduce((result: any, field) => {
|
|
204
|
+
// get essential fields
|
|
205
|
+
const key = field.key;
|
|
206
|
+
const localPath = field.localPath;
|
|
207
|
+
const attributes = field.attributes;
|
|
208
|
+
const isEntire = field.context;
|
|
209
|
+
const shortcut = field.shortcut;
|
|
210
|
+
// get optional fields
|
|
211
|
+
const index = field.index;
|
|
212
|
+
const attributePath = field.attributePath;
|
|
213
|
+
|
|
214
|
+
// set allowing overriding if there is a shortcut injected
|
|
215
|
+
let targetDoc = rootDoc;
|
|
216
|
+
|
|
217
|
+
// if shortcut is used, then replace the doc
|
|
218
|
+
// it's a design for overriding the doc used during runtime
|
|
219
|
+
if (shortcut) {
|
|
220
|
+
targetDoc = dom.parseFromString(shortcut);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// special case: multiple path
|
|
224
|
+
/*
|
|
225
|
+
{
|
|
226
|
+
key: 'issuer',
|
|
227
|
+
localPath: [
|
|
228
|
+
['Response', 'Issuer'],
|
|
229
|
+
['Response', 'Assertion', 'Issuer']
|
|
230
|
+
],
|
|
231
|
+
attributes: []
|
|
232
|
+
}
|
|
233
|
+
*/
|
|
234
|
+
if (localPath.every(path => Array.isArray(path))) {
|
|
235
|
+
const multiXPaths = localPath
|
|
236
|
+
.map(path => {
|
|
237
|
+
// not support attribute yet, so ignore it
|
|
238
|
+
return `${buildAbsoluteXPath(path)}/text()`;
|
|
239
|
+
})
|
|
240
|
+
.join(' | ');
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
...result,
|
|
244
|
+
[key]: uniq(select(multiXPaths, targetDoc).map((n: Node) => n.nodeValue).filter(notEmpty))
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
// eo special case: multiple path
|
|
248
|
+
|
|
249
|
+
const baseXPath = buildAbsoluteXPath(localPath);
|
|
250
|
+
const attributeXPath = buildAttributeXPath(attributes);
|
|
251
|
+
|
|
252
|
+
// special case: get attributes where some are in child, some are in parent
|
|
253
|
+
/*
|
|
254
|
+
{
|
|
255
|
+
key: 'attributes',
|
|
256
|
+
localPath: ['Response', 'Assertion', 'AttributeStatement', 'Attribute'],
|
|
257
|
+
index: ['Name'],
|
|
258
|
+
attributePath: ['AttributeValue'],
|
|
259
|
+
attributes: []
|
|
260
|
+
}
|
|
261
|
+
*/
|
|
262
|
+
if (index && attributePath) {
|
|
263
|
+
// find the index in localpath
|
|
264
|
+
const indexPath = buildAttributeXPath(index);
|
|
265
|
+
const fullLocalXPath = `${baseXPath}${indexPath}`;
|
|
266
|
+
const parentNodes = select(baseXPath, targetDoc);
|
|
267
|
+
// [uid, mail, edupersonaffiliation], ready for aggregate
|
|
268
|
+
const parentAttributes = select(fullLocalXPath, targetDoc).map((n: Attr) => n.value);
|
|
269
|
+
// [attribute, attributevalue]
|
|
270
|
+
const childXPath = buildAbsoluteXPath([last(localPath)].concat(attributePath));
|
|
271
|
+
const childAttributeXPath = buildAttributeXPath(attributes);
|
|
272
|
+
const fullChildXPath = `${childXPath}${childAttributeXPath}`;
|
|
273
|
+
// [ 'test', 'test@example.com', [ 'users', 'examplerole1' ] ]
|
|
274
|
+
const childAttributes = parentNodes.map(node => {
|
|
275
|
+
const nodeDoc = dom.parseFromString(node.toString());
|
|
276
|
+
if (attributes.length === 0) {
|
|
277
|
+
const childValues = select(fullChildXPath, nodeDoc).map((n: Node) => n.nodeValue);
|
|
278
|
+
if (childValues.length === 1) {
|
|
279
|
+
return childValues[0];
|
|
280
|
+
}
|
|
281
|
+
return childValues;
|
|
282
|
+
}
|
|
283
|
+
if (attributes.length > 0) {
|
|
284
|
+
const childValues = select(fullChildXPath, nodeDoc).map((n: Attr) => n.value);
|
|
285
|
+
if (childValues.length === 1) {
|
|
286
|
+
return childValues[0];
|
|
287
|
+
}
|
|
288
|
+
return childValues;
|
|
289
|
+
}
|
|
290
|
+
return null;
|
|
291
|
+
});
|
|
292
|
+
// aggregation
|
|
293
|
+
const obj = zipObject(parentAttributes, childAttributes, false);
|
|
294
|
+
return {
|
|
295
|
+
...result,
|
|
296
|
+
[key]: obj
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
}
|
|
300
|
+
// case: fetch entire content, only allow one existence
|
|
301
|
+
/*
|
|
302
|
+
{
|
|
303
|
+
key: 'signature',
|
|
304
|
+
localPath: ['AuthnRequest', 'Signature'],
|
|
305
|
+
attributes: [],
|
|
306
|
+
context: true
|
|
307
|
+
}
|
|
308
|
+
*/
|
|
309
|
+
if (isEntire) {
|
|
310
|
+
const node = select(baseXPath, targetDoc);
|
|
311
|
+
let value: string | string[] | null = null;
|
|
312
|
+
if (node.length === 1) {
|
|
313
|
+
value = node[0].toString();
|
|
314
|
+
}
|
|
315
|
+
if (node.length > 1) {
|
|
316
|
+
value = node.map(n => n.toString());
|
|
317
|
+
}
|
|
318
|
+
return {
|
|
319
|
+
...result,
|
|
320
|
+
[key]: value
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// case: multiple attribute
|
|
325
|
+
/*
|
|
326
|
+
{
|
|
327
|
+
key: 'nameIDPolicy',
|
|
328
|
+
localPath: ['AuthnRequest', 'NameIDPolicy'],
|
|
329
|
+
attributes: ['Format', 'AllowCreate']
|
|
330
|
+
}
|
|
331
|
+
*/
|
|
332
|
+
if (attributes.length > 1) {
|
|
333
|
+
const baseNode = select(baseXPath, targetDoc).map(n => n.toString());
|
|
334
|
+
const childXPath = `${buildAbsoluteXPath([last(localPath)])}${attributeXPath}`;
|
|
335
|
+
const attributeValues = baseNode.map((node: string) => {
|
|
336
|
+
const nodeDoc = dom.parseFromString(node);
|
|
337
|
+
const values = select(childXPath, nodeDoc).reduce((r: any, n: Attr) => {
|
|
338
|
+
r[camelCase(n.name, {locale: 'en-us'})] = n.value;
|
|
339
|
+
return r;
|
|
340
|
+
}, {});
|
|
341
|
+
return values;
|
|
342
|
+
});
|
|
343
|
+
return {
|
|
344
|
+
...result,
|
|
345
|
+
[key]: attributeValues.length === 1 ? attributeValues[0] : attributeValues
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
// case: single attribute
|
|
349
|
+
/*
|
|
350
|
+
{
|
|
351
|
+
key: 'statusCode',
|
|
352
|
+
localPath: ['Response', 'Status', 'StatusCode'],
|
|
353
|
+
attributes: ['Value'],
|
|
354
|
+
}
|
|
355
|
+
*/
|
|
356
|
+
if (attributes.length === 1) {
|
|
357
|
+
const fullPath = `${baseXPath}${attributeXPath}`;
|
|
358
|
+
const attributeValues = select(fullPath, targetDoc).map((n: Attr) => n.value);
|
|
359
|
+
return {
|
|
360
|
+
...result,
|
|
361
|
+
[key]: attributeValues[0]
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
// case: zero attribute
|
|
365
|
+
/*
|
|
366
|
+
{
|
|
367
|
+
key: 'issuer',
|
|
368
|
+
localPath: ['AuthnRequest', 'Issuer'],
|
|
369
|
+
attributes: []
|
|
370
|
+
}
|
|
371
|
+
*/
|
|
372
|
+
if (attributes.length === 0) {
|
|
373
|
+
let attributeValue: SelectedValue[] | (string | null)[] | null = null;
|
|
374
|
+
const node = select(baseXPath, targetDoc);
|
|
375
|
+
if (node.length === 1) {
|
|
376
|
+
const fullPath = `string(${baseXPath}${attributeXPath})`;
|
|
377
|
+
attributeValue = select(fullPath, targetDoc);
|
|
378
|
+
}
|
|
379
|
+
if (node.length > 1) {
|
|
380
|
+
attributeValue = node.filter((n: Node) => n.firstChild)
|
|
381
|
+
.map((n: Node) => n.firstChild!.nodeValue);
|
|
382
|
+
}
|
|
383
|
+
return {
|
|
384
|
+
...result,
|
|
385
|
+
[key]: attributeValue
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return result;
|
|
390
|
+
}, {});
|
|
391
|
+
|
|
392
|
+
}
|