samlify 2.10.2 → 2.12.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.
- package/.circleci/config.yml +8 -38
- package/.snyk +2 -6
- package/build/index.js +17 -7
- package/build/index.js.map +1 -1
- package/build/src/api.js +15 -5
- package/build/src/api.js.map +1 -1
- package/build/src/binding-post.js +25 -15
- package/build/src/binding-post.js.map +1 -1
- package/build/src/binding-redirect.js +21 -7
- package/build/src/binding-redirect.js.map +1 -1
- package/build/src/binding-simplesign.js +24 -14
- package/build/src/binding-simplesign.js.map +1 -1
- package/build/src/entity-idp.js +4 -4
- package/build/src/entity-idp.js.map +1 -1
- package/build/src/entity-sp.js +2 -2
- package/build/src/entity-sp.js.map +1 -1
- package/build/src/entity.js +2 -25
- package/build/src/entity.js.map +1 -1
- package/build/src/extractor.js +33 -28
- package/build/src/extractor.js.map +1 -1
- package/build/src/flow.js +4 -5
- package/build/src/flow.js.map +1 -1
- package/build/src/libsaml.js +44 -24
- package/build/src/libsaml.js.map +1 -1
- package/build/src/metadata-idp.js +9 -9
- package/build/src/metadata-idp.js.map +1 -1
- package/build/src/metadata-sp.js +9 -9
- package/build/src/metadata-sp.js.map +1 -1
- package/build/src/metadata.js +17 -7
- package/build/src/metadata.js.map +1 -1
- package/build/src/urn.js +4 -4
- package/build/src/urn.js.map +1 -1
- package/build/src/utility.js +53 -26
- package/build/src/utility.js.map +1 -1
- package/build/src/validator.js +1 -2
- package/build/src/validator.js.map +1 -1
- package/package.json +17 -19
- package/samlify-2.11.0.tgz +0 -0
- package/src/api.ts +13 -1
- package/src/binding-redirect.ts +4 -0
- package/src/entity.ts +2 -2
- package/src/extractor.ts +33 -26
- package/src/libsaml.ts +26 -17
- package/src/utility.ts +42 -13
- package/types/src/binding-post.d.ts +1 -1
- package/types/src/binding-simplesign.d.ts +1 -1
- package/types/src/entity.d.ts +1 -2
- package/types/src/extractor.d.ts +1 -1
- package/types/src/libsaml.d.ts +5 -6
- package/types/src/metadata.d.ts +0 -1
- package/types/src/types.d.ts +7 -8
- package/types/src/utility.d.ts +7 -2
- package/types/src/validator.d.ts +1 -1
- package/.travis.yml +0 -29
- package/CHANGELOG.md +0 -7
- package/types/vitest.config.d.ts +0 -2
package/src/entity.ts
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
* @author tngan
|
|
4
4
|
* @desc An abstraction for identity provider and service provider.
|
|
5
5
|
*/
|
|
6
|
+
import { randomUUID } from 'crypto';
|
|
6
7
|
import { isString, isNonEmptyArray } from './utility';
|
|
7
8
|
import { namespace, wording, algorithms, messageConfigurations } from './urn';
|
|
8
|
-
import * as uuid from 'uuid';
|
|
9
9
|
import IdpMetadata, { IdpMetadata as IdpMetadataConstructor } from './metadata-idp';
|
|
10
10
|
import SpMetadata, { SpMetadata as SpMetadataConstructor } from './metadata-sp';
|
|
11
11
|
import redirectBinding from './binding-redirect';
|
|
@@ -27,7 +27,7 @@ const defaultEntitySetting = {
|
|
|
27
27
|
requestSignatureAlgorithm: signatureAlgorithms.RSA_SHA256,
|
|
28
28
|
dataEncryptionAlgorithm: dataEncryptionAlgorithm.AES_256,
|
|
29
29
|
keyEncryptionAlgorithm: keyEncryptionAlgorithm.RSA_OAEP_MGF1P,
|
|
30
|
-
generateID: (): string => ('_' +
|
|
30
|
+
generateID: (): string => ('_' + randomUUID()),
|
|
31
31
|
relayState: '',
|
|
32
32
|
};
|
|
33
33
|
|
package/src/extractor.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
import { select, SelectedValue } from 'xpath';
|
|
2
|
-
import { uniq, last, zipObject, notEmpty } from './utility';
|
|
1
|
+
import { select, SelectedValue, SelectReturnType } from 'xpath';
|
|
2
|
+
import { uniq, last, zipObject, notEmpty, escapeXPathValue } 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
|
+
}
|
|
3
9
|
import { getContext } from './api';
|
|
4
|
-
import camelCase from '
|
|
10
|
+
import { camelCase } from './utility';
|
|
5
11
|
|
|
6
12
|
interface ExtractorField {
|
|
7
13
|
key: string;
|
|
@@ -20,10 +26,10 @@ function buildAbsoluteXPath(paths) {
|
|
|
20
26
|
const isWildcard = name.startsWith('~');
|
|
21
27
|
if (isWildcard) {
|
|
22
28
|
const pathName = name.replace('~', '');
|
|
23
|
-
appendedPath = currentPath + `/*[contains(local-name(),
|
|
29
|
+
appendedPath = currentPath + `/*[contains(local-name(), ${escapeXPathValue(pathName)})]`;
|
|
24
30
|
}
|
|
25
31
|
if (!isWildcard) {
|
|
26
|
-
appendedPath = currentPath + `/*[local-name(.)
|
|
32
|
+
appendedPath = currentPath + `/*[local-name(.)=${escapeXPathValue(name)}]`;
|
|
27
33
|
}
|
|
28
34
|
return appendedPath;
|
|
29
35
|
}, '');
|
|
@@ -36,7 +42,7 @@ function buildAttributeXPath(attributes) {
|
|
|
36
42
|
if (attributes.length === 1) {
|
|
37
43
|
return `/@${attributes[0]}`;
|
|
38
44
|
}
|
|
39
|
-
const filters = attributes.map(attribute => `name()
|
|
45
|
+
const filters = attributes.map(attribute => `name()=${escapeXPathValue(attribute)}`).join(' or ');
|
|
40
46
|
return `/@*[${filters}]`;
|
|
41
47
|
}
|
|
42
48
|
|
|
@@ -241,7 +247,7 @@ export function extract(context: string, fields) {
|
|
|
241
247
|
|
|
242
248
|
return {
|
|
243
249
|
...result,
|
|
244
|
-
[key]: uniq(select(multiXPaths, targetDoc).map((n: Node) => n.nodeValue).filter(notEmpty))
|
|
250
|
+
[key]: uniq(toNodeArray(select(multiXPaths, targetDoc)).map((n: Node) => n.nodeValue).filter(notEmpty))
|
|
245
251
|
};
|
|
246
252
|
}
|
|
247
253
|
// eo special case: multiple path
|
|
@@ -263,9 +269,9 @@ export function extract(context: string, fields) {
|
|
|
263
269
|
// find the index in localpath
|
|
264
270
|
const indexPath = buildAttributeXPath(index);
|
|
265
271
|
const fullLocalXPath = `${baseXPath}${indexPath}`;
|
|
266
|
-
const parentNodes = select(baseXPath, targetDoc);
|
|
272
|
+
const parentNodes = toNodeArray(select(baseXPath, targetDoc));
|
|
267
273
|
// [uid, mail, edupersonaffiliation], ready for aggregate
|
|
268
|
-
const parentAttributes = select(fullLocalXPath, targetDoc).map((n: Attr) => n.value);
|
|
274
|
+
const parentAttributes = toNodeArray(select(fullLocalXPath, targetDoc)).map((n: Attr) => n.value);
|
|
269
275
|
// [attribute, attributevalue]
|
|
270
276
|
const childXPath = buildAbsoluteXPath([last(localPath)].concat(attributePath));
|
|
271
277
|
const childAttributeXPath = buildAttributeXPath(attributes);
|
|
@@ -274,14 +280,14 @@ export function extract(context: string, fields) {
|
|
|
274
280
|
const childAttributes = parentNodes.map(node => {
|
|
275
281
|
const nodeDoc = dom.parseFromString(node.toString());
|
|
276
282
|
if (attributes.length === 0) {
|
|
277
|
-
const childValues = select(fullChildXPath, nodeDoc).map((n: Node) => n.nodeValue);
|
|
283
|
+
const childValues = toNodeArray(select(fullChildXPath, nodeDoc)).map((n: Node) => n.nodeValue);
|
|
278
284
|
if (childValues.length === 1) {
|
|
279
285
|
return childValues[0];
|
|
280
286
|
}
|
|
281
287
|
return childValues;
|
|
282
288
|
}
|
|
283
289
|
if (attributes.length > 0) {
|
|
284
|
-
const childValues = select(fullChildXPath, nodeDoc).map((n: Attr) => n.value);
|
|
290
|
+
const childValues = toNodeArray(select(fullChildXPath, nodeDoc)).map((n: Attr) => n.value);
|
|
285
291
|
if (childValues.length === 1) {
|
|
286
292
|
return childValues[0];
|
|
287
293
|
}
|
|
@@ -307,13 +313,13 @@ export function extract(context: string, fields) {
|
|
|
307
313
|
}
|
|
308
314
|
*/
|
|
309
315
|
if (isEntire) {
|
|
310
|
-
const
|
|
316
|
+
const nodes = toNodeArray(select(baseXPath, targetDoc));
|
|
311
317
|
let value: string | string[] | null = null;
|
|
312
|
-
if (
|
|
313
|
-
value =
|
|
318
|
+
if (nodes.length === 1) {
|
|
319
|
+
value = nodes[0].toString();
|
|
314
320
|
}
|
|
315
|
-
if (
|
|
316
|
-
value =
|
|
321
|
+
if (nodes.length > 1) {
|
|
322
|
+
value = nodes.map(n => n.toString());
|
|
317
323
|
}
|
|
318
324
|
return {
|
|
319
325
|
...result,
|
|
@@ -330,12 +336,12 @@ export function extract(context: string, fields) {
|
|
|
330
336
|
}
|
|
331
337
|
*/
|
|
332
338
|
if (attributes.length > 1) {
|
|
333
|
-
const baseNode = select(baseXPath, targetDoc).map(n => n.toString());
|
|
339
|
+
const baseNode = toNodeArray(select(baseXPath, targetDoc)).map(n => n.toString());
|
|
334
340
|
const childXPath = `${buildAbsoluteXPath([last(localPath)])}${attributeXPath}`;
|
|
335
341
|
const attributeValues = baseNode.map((node: string) => {
|
|
336
342
|
const nodeDoc = dom.parseFromString(node);
|
|
337
|
-
const values = select(childXPath, nodeDoc).reduce((r: any, n: Attr) => {
|
|
338
|
-
r[camelCase(n.name
|
|
343
|
+
const values = toNodeArray(select(childXPath, nodeDoc)).reduce((r: any, n: Attr) => {
|
|
344
|
+
r[camelCase(n.name)] = n.value;
|
|
339
345
|
return r;
|
|
340
346
|
}, {});
|
|
341
347
|
return values;
|
|
@@ -355,7 +361,7 @@ export function extract(context: string, fields) {
|
|
|
355
361
|
*/
|
|
356
362
|
if (attributes.length === 1) {
|
|
357
363
|
const fullPath = `${baseXPath}${attributeXPath}`;
|
|
358
|
-
const attributeValues = select(fullPath, targetDoc).map((n: Attr) => n.value);
|
|
364
|
+
const attributeValues = toNodeArray(select(fullPath, targetDoc)).map((n: Attr) => n.value);
|
|
359
365
|
return {
|
|
360
366
|
...result,
|
|
361
367
|
[key]: attributeValues[0]
|
|
@@ -370,14 +376,15 @@ export function extract(context: string, fields) {
|
|
|
370
376
|
}
|
|
371
377
|
*/
|
|
372
378
|
if (attributes.length === 0) {
|
|
373
|
-
let attributeValue: SelectedValue[] | (string | null)[] | null = null;
|
|
374
|
-
const
|
|
375
|
-
if (
|
|
379
|
+
let attributeValue: SelectedValue[] | (string | null)[] | string | null = null;
|
|
380
|
+
const nodes = toNodeArray(select(baseXPath, targetDoc));
|
|
381
|
+
if (nodes.length === 1) {
|
|
376
382
|
const fullPath = `string(${baseXPath}${attributeXPath})`;
|
|
377
|
-
|
|
383
|
+
const strResult = select(fullPath, targetDoc);
|
|
384
|
+
attributeValue = typeof strResult === 'string' ? strResult : strResult === null ? null : Array.isArray(strResult) ? strResult : null;
|
|
378
385
|
}
|
|
379
|
-
if (
|
|
380
|
-
attributeValue =
|
|
386
|
+
if (nodes.length > 1) {
|
|
387
|
+
attributeValue = nodes.filter((n: Node) => n.firstChild)
|
|
381
388
|
.map((n: Node) => n.firstChild!.nodeValue);
|
|
382
389
|
}
|
|
383
390
|
return {
|
package/src/libsaml.ts
CHANGED
|
@@ -4,15 +4,21 @@
|
|
|
4
4
|
* @desc A simple library including some common functions
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import utility, { flattenDeep, isString } from './utility';
|
|
7
|
+
import utility, { flattenDeep, isString, escapeXPathValue } from './utility';
|
|
8
8
|
import { algorithms, wording, namespace } from './urn';
|
|
9
|
-
import { select } from 'xpath';
|
|
9
|
+
import { select, SelectReturnType } from 'xpath';
|
|
10
|
+
|
|
11
|
+
function toNodeArray(result: SelectReturnType): Node[] {
|
|
12
|
+
if (Array.isArray(result)) return result;
|
|
13
|
+
if (result != null && typeof result === 'object' && 'nodeType' in (result as object)) return [result as Node];
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
10
16
|
import { MetadataInterface } from './metadata';
|
|
11
17
|
import nrsa, { SigningSchemeHash } from 'node-rsa';
|
|
12
18
|
import { SignedXml } from 'xml-crypto';
|
|
13
19
|
import * as xmlenc from '@authenio/xml-encryption';
|
|
14
20
|
import { extract } from './extractor';
|
|
15
|
-
import camelCase from '
|
|
21
|
+
import { camelCase } from './utility';
|
|
16
22
|
import { getContext } from './api';
|
|
17
23
|
import xmlEscape from 'xml-escape';
|
|
18
24
|
import * as fs from 'fs';
|
|
@@ -221,9 +227,10 @@ const libSaml = () => {
|
|
|
221
227
|
*/
|
|
222
228
|
function createXPath(local, isExtractAll?: boolean): string {
|
|
223
229
|
if (isString(local)) {
|
|
224
|
-
|
|
230
|
+
const escaped = escapeXPathValue(local);
|
|
231
|
+
return isExtractAll === true ? "//*[local-name(.)=" + escaped + "]/text()" : "//*[local-name(.)=" + escaped + "]";
|
|
225
232
|
}
|
|
226
|
-
return "//*[local-name(.)=
|
|
233
|
+
return "//*[local-name(.)=" + escapeXPathValue(local.name) + "]/@" + local.attr;
|
|
227
234
|
}
|
|
228
235
|
|
|
229
236
|
/**
|
|
@@ -234,7 +241,7 @@ const libSaml = () => {
|
|
|
234
241
|
* @return {string}
|
|
235
242
|
*/
|
|
236
243
|
function tagging(prefix: string, content: string): string {
|
|
237
|
-
const camelContent = camelCase(content
|
|
244
|
+
const camelContent = camelCase(content);
|
|
238
245
|
return prefix + camelContent.charAt(0).toUpperCase() + camelContent.slice(1);
|
|
239
246
|
}
|
|
240
247
|
|
|
@@ -370,7 +377,8 @@ const libSaml = () => {
|
|
|
370
377
|
const { dom } = getContext();
|
|
371
378
|
const doc = dom.parseFromString(xml);
|
|
372
379
|
|
|
373
|
-
const
|
|
380
|
+
const { dom: contextDom } = getContext();
|
|
381
|
+
const docParser = contextDom;
|
|
374
382
|
// In order to avoid the wrapping attack, we have changed to use absolute xpath instead of naively fetching the signature element
|
|
375
383
|
// message signature (logout response / saml response)
|
|
376
384
|
const messageSignatureXpath = "/*[contains(local-name(), 'Response') or contains(local-name(), 'Request')]/*[local-name(.)='Signature']";
|
|
@@ -380,10 +388,10 @@ const libSaml = () => {
|
|
|
380
388
|
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']";
|
|
381
389
|
|
|
382
390
|
// select the signature node
|
|
383
|
-
let selection:
|
|
384
|
-
const messageSignatureNode = select(messageSignatureXpath, doc);
|
|
385
|
-
const assertionSignatureNode = select(assertionSignatureXpath, doc);
|
|
386
|
-
const wrappingElementNode = select(wrappingElementsXPath, doc);
|
|
391
|
+
let selection: Node[] = [];
|
|
392
|
+
const messageSignatureNode = toNodeArray(select(messageSignatureXpath, doc));
|
|
393
|
+
const assertionSignatureNode = toNodeArray(select(assertionSignatureXpath, doc));
|
|
394
|
+
const wrappingElementNode = toNodeArray(select(wrappingElementsXPath, doc));
|
|
387
395
|
|
|
388
396
|
selection = selection.concat(messageSignatureNode);
|
|
389
397
|
selection = selection.concat(assertionSignatureNode);
|
|
@@ -415,7 +423,7 @@ const libSaml = () => {
|
|
|
415
423
|
|
|
416
424
|
if (opts.metadata) {
|
|
417
425
|
|
|
418
|
-
const certificateNode = select(".//*[local-name(.)='X509Certificate']", signatureNode)
|
|
426
|
+
const certificateNode = toNodeArray(select(".//*[local-name(.)='X509Certificate']", signatureNode));
|
|
419
427
|
// certificate in metadata
|
|
420
428
|
let metadataCert: any = opts.metadata.getX509Certificate(certUse.signing);
|
|
421
429
|
// flattens the nested array of Certificates from each KeyDescriptor
|
|
@@ -434,7 +442,8 @@ const libSaml = () => {
|
|
|
434
442
|
|
|
435
443
|
// certificate node in response
|
|
436
444
|
if (certificateNode.length !== 0) {
|
|
437
|
-
const
|
|
445
|
+
const certEl = certificateNode[0] as Element;
|
|
446
|
+
const x509CertificateData = certEl.textContent ?? '';
|
|
438
447
|
const x509Certificate = utility.normalizeCerString(x509CertificateData);
|
|
439
448
|
|
|
440
449
|
if (
|
|
@@ -473,15 +482,15 @@ const libSaml = () => {
|
|
|
473
482
|
// case 1, rootSignedDoc is a response:
|
|
474
483
|
if (rootNode.localName === 'Response') {
|
|
475
484
|
// try getting the Xml from the first assertion
|
|
476
|
-
const assertions = select(
|
|
485
|
+
const assertions = toNodeArray(select(
|
|
477
486
|
"./*[local-name()='Assertion']",
|
|
478
487
|
rootNode
|
|
479
|
-
);
|
|
488
|
+
));
|
|
480
489
|
|
|
481
|
-
const encryptedAssertions = select(
|
|
490
|
+
const encryptedAssertions = toNodeArray(select(
|
|
482
491
|
"./*[local-name()='EncryptedAssertion']",
|
|
483
492
|
rootNode
|
|
484
|
-
);
|
|
493
|
+
));
|
|
485
494
|
// now we can process the assertion as an assertion
|
|
486
495
|
if (assertions.length === 1) {
|
|
487
496
|
return [true, assertions[0].toString()];
|
package/src/utility.ts
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* @author tngan
|
|
4
4
|
* @desc Library for some common functions (e.g. de/inflation, en/decoding)
|
|
5
5
|
*/
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import { X509Certificate, createPrivateKey } from 'crypto';
|
|
7
|
+
import { deflateRawSync, inflateRawSync } from 'zlib';
|
|
8
8
|
|
|
9
9
|
const BASE64_STR = 'base64';
|
|
10
10
|
|
|
@@ -102,8 +102,8 @@ export function base64Decode(base64Message: string, isBytes?: boolean): string |
|
|
|
102
102
|
* @return {string} compressed string
|
|
103
103
|
*/
|
|
104
104
|
function deflateString(message: string): number[] {
|
|
105
|
-
const input =
|
|
106
|
-
return Array.from(
|
|
105
|
+
const input = Buffer.from(message, 'utf8');
|
|
106
|
+
return Array.from(deflateRawSync(input));
|
|
107
107
|
}
|
|
108
108
|
/**
|
|
109
109
|
* @desc Decompress the compressed string
|
|
@@ -112,10 +112,7 @@ function deflateString(message: string): number[] {
|
|
|
112
112
|
*/
|
|
113
113
|
export function inflateString(compressedString: string): string {
|
|
114
114
|
const inputBuffer = Buffer.from(compressedString, BASE64_STR);
|
|
115
|
-
|
|
116
|
-
return Array.from(inflate(input, { raw: true }))
|
|
117
|
-
.map((byte: number) => String.fromCharCode(byte))
|
|
118
|
-
.join('');
|
|
115
|
+
return inflateRawSync(inputBuffer).toString('utf8');
|
|
119
116
|
}
|
|
120
117
|
/**
|
|
121
118
|
* @desc Abstract the normalizeCerString and normalizePemString
|
|
@@ -173,10 +170,9 @@ function applyDefault(obj1, obj2) {
|
|
|
173
170
|
* @return {string} public key fetched from the certificate
|
|
174
171
|
*/
|
|
175
172
|
function getPublicKeyPemFromCertificate(x509Certificate: string) {
|
|
176
|
-
const
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
return pki.publicKeyToPem(cert.publicKey);
|
|
173
|
+
const der = Buffer.from(x509Certificate, 'base64');
|
|
174
|
+
const cert = new X509Certificate(der);
|
|
175
|
+
return cert.publicKey.export({ type: 'spki', format: 'pem' });
|
|
180
176
|
}
|
|
181
177
|
/**
|
|
182
178
|
* @desc Read private key from pem-formatted string
|
|
@@ -186,7 +182,12 @@ function getPublicKeyPemFromCertificate(x509Certificate: string) {
|
|
|
186
182
|
* If passphrase is used to protect the .pem content (recommend)
|
|
187
183
|
*/
|
|
188
184
|
export function readPrivateKey(keyString: string | Buffer, passphrase: string | undefined, isOutputString?: boolean) {
|
|
189
|
-
|
|
185
|
+
if (isString(passphrase)) {
|
|
186
|
+
const key = createPrivateKey({ key: keyString, format: 'pem', passphrase });
|
|
187
|
+
const pem = key.export({ type: 'pkcs1', format: 'pem' });
|
|
188
|
+
return convertToString(pem, isOutputString);
|
|
189
|
+
}
|
|
190
|
+
return keyString;
|
|
190
191
|
}
|
|
191
192
|
/**
|
|
192
193
|
* @desc Inline syntax sugar
|
|
@@ -210,6 +211,34 @@ export function notEmpty<TValue>(value: TValue | null | undefined): value is TVa
|
|
|
210
211
|
return value !== null && value !== undefined;
|
|
211
212
|
}
|
|
212
213
|
|
|
214
|
+
/**
|
|
215
|
+
* @desc Escape a string for safe use inside an XPath single-quoted string literal.
|
|
216
|
+
* Prevents XPath injection by splitting on single quotes and using concat().
|
|
217
|
+
*/
|
|
218
|
+
export function escapeXPathValue(value: string): string {
|
|
219
|
+
if (!value.includes("'")) {
|
|
220
|
+
return "'" + value + "'";
|
|
221
|
+
}
|
|
222
|
+
// Use XPath concat() to safely handle strings containing single quotes
|
|
223
|
+
const parts = value.split("'").map(part => "'" + part + "'");
|
|
224
|
+
return 'concat(' + parts.join(`,"'",`) + ')';
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function camelCase(input: string): string {
|
|
228
|
+
const words = input
|
|
229
|
+
.replace(/([a-z\d])([A-Z])/g, '$1\0$2')
|
|
230
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1\0$2')
|
|
231
|
+
.split(/[\0\s\-_\.]+/)
|
|
232
|
+
.filter(w => w.length > 0);
|
|
233
|
+
|
|
234
|
+
return words
|
|
235
|
+
.map((word, i) => {
|
|
236
|
+
const lower = word.toLocaleLowerCase('en-US');
|
|
237
|
+
return i === 0 ? lower : lower.charAt(0).toLocaleUpperCase('en-US') + lower.slice(1);
|
|
238
|
+
})
|
|
239
|
+
.join('');
|
|
240
|
+
}
|
|
241
|
+
|
|
213
242
|
const utility = {
|
|
214
243
|
isString,
|
|
215
244
|
base64Encode,
|
|
@@ -19,7 +19,7 @@ declare function base64LoginRequest(referenceTagXPath: string, entity: any, cust
|
|
|
19
19
|
* @param {function} customTagReplacement used when developers have their own login response template
|
|
20
20
|
* @param {boolean} encryptThenSign whether or not to encrypt then sign first (if signing). Defaults to sign-then-encrypt
|
|
21
21
|
*/
|
|
22
|
-
declare function base64LoginResponse(requestInfo: any, entity: any, user?: any, customTagReplacement?: (template: string) => BindingContext, encryptThenSign?: boolean): Promise<BindingContext>;
|
|
22
|
+
declare function base64LoginResponse(requestInfo: any | undefined, entity: any, user?: any, customTagReplacement?: (template: string) => BindingContext, encryptThenSign?: boolean): Promise<BindingContext>;
|
|
23
23
|
/**
|
|
24
24
|
* @desc Generate a base64 encoded logout request
|
|
25
25
|
* @param {object} user current logged user (e.g. req.user)
|
|
@@ -31,7 +31,7 @@ declare function base64LoginRequest(entity: any, customTagReplacement?: (templat
|
|
|
31
31
|
* @param {string} relayState the relay state
|
|
32
32
|
* @param {function} customTagReplacement used when developers have their own login response template
|
|
33
33
|
*/
|
|
34
|
-
declare function base64LoginResponse(requestInfo: any, entity: any, user?: any, relayState?: string, customTagReplacement?: (template: string) => BindingContext): Promise<BindingSimpleSignContext>;
|
|
34
|
+
declare function base64LoginResponse(requestInfo: any | undefined, entity: any, user?: any, relayState?: string, customTagReplacement?: (template: string) => BindingContext): Promise<BindingSimpleSignContext>;
|
|
35
35
|
declare const simpleSignBinding: {
|
|
36
36
|
base64LoginRequest: typeof base64LoginRequest;
|
|
37
37
|
base64LoginResponse: typeof base64LoginResponse;
|
package/types/src/entity.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
1
|
import { IdpMetadata as IdpMetadataConstructor } from './metadata-idp';
|
|
3
2
|
import { SpMetadata as SpMetadataConstructor } from './metadata-sp';
|
|
4
3
|
import { MetadataIdpConstructor, MetadataSpConstructor, EntitySetting } from './types';
|
|
@@ -31,7 +30,7 @@ export interface ParseResult {
|
|
|
31
30
|
extract: any;
|
|
32
31
|
sigAlg: string;
|
|
33
32
|
}
|
|
34
|
-
export
|
|
33
|
+
export type EntityConstructor = (MetadataIdpConstructor | MetadataSpConstructor) & {
|
|
35
34
|
metadata?: string | Buffer;
|
|
36
35
|
};
|
|
37
36
|
export default class Entity {
|
package/types/src/extractor.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ interface ExtractorField {
|
|
|
6
6
|
attributePath?: string[];
|
|
7
7
|
context?: boolean;
|
|
8
8
|
}
|
|
9
|
-
export
|
|
9
|
+
export type ExtractorFields = ExtractorField[];
|
|
10
10
|
export declare const loginRequestFields: ExtractorFields;
|
|
11
11
|
export declare const loginResponseStatusFields: {
|
|
12
12
|
key: string;
|
package/types/src/libsaml.d.ts
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
* @author tngan
|
|
4
4
|
* @desc A simple library including some common functions
|
|
5
5
|
*/
|
|
6
|
-
/// <reference types="node" />
|
|
7
6
|
import { MetadataInterface } from './metadata';
|
|
8
7
|
export interface SignatureConstructor {
|
|
9
8
|
rawSamlMessage: string;
|
|
@@ -58,7 +57,7 @@ export interface LogoutRequestTemplate extends BaseSamlTemplate {
|
|
|
58
57
|
}
|
|
59
58
|
export interface LogoutResponseTemplate extends BaseSamlTemplate {
|
|
60
59
|
}
|
|
61
|
-
export
|
|
60
|
+
export type KeyUse = 'signing' | 'encryption';
|
|
62
61
|
export interface KeyComponent {
|
|
63
62
|
[key: string]: any;
|
|
64
63
|
}
|
|
@@ -86,7 +85,7 @@ export interface LibSamlInterface {
|
|
|
86
85
|
defaultLogoutResponseTemplate: LogoutResponseTemplate;
|
|
87
86
|
}
|
|
88
87
|
declare const _default: {
|
|
89
|
-
createXPath: (local: any, isExtractAll?: boolean
|
|
88
|
+
createXPath: (local: any, isExtractAll?: boolean) => string;
|
|
90
89
|
getQueryParamByType: (type: string) => "SAMLRequest" | "SAMLResponse";
|
|
91
90
|
defaultLoginRequestTemplate: {
|
|
92
91
|
context: string;
|
|
@@ -166,7 +165,7 @@ declare const _default: {
|
|
|
166
165
|
* @param {string} signingAlgorithm signing algorithm
|
|
167
166
|
* @return {string} message signature
|
|
168
167
|
*/
|
|
169
|
-
constructMessageSignature(octetString: string, key: string, passphrase?: string
|
|
168
|
+
constructMessageSignature(octetString: string, key: string, passphrase?: string, isBase64?: boolean, signingAlgorithm?: string): string | Buffer<ArrayBufferLike>;
|
|
170
169
|
/**
|
|
171
170
|
* @desc Verifies message signature
|
|
172
171
|
* @param {Metadata} metadata metadata object of identity provider or service provider
|
|
@@ -175,7 +174,7 @@ declare const _default: {
|
|
|
175
174
|
* @param {string} verifyAlgorithm algorithm used to verify
|
|
176
175
|
* @return {boolean} verification result
|
|
177
176
|
*/
|
|
178
|
-
verifyMessageSignature(metadata: any, octetString: string, signature: string | Buffer, verifyAlgorithm?: string
|
|
177
|
+
verifyMessageSignature(metadata: any, octetString: string, signature: string | Buffer, verifyAlgorithm?: string): boolean;
|
|
179
178
|
/**
|
|
180
179
|
* @desc Get the public key in string format
|
|
181
180
|
* @param {string} x509Certificate certificate
|
|
@@ -192,7 +191,7 @@ declare const _default: {
|
|
|
192
191
|
* @param {string} xml response in xml string format
|
|
193
192
|
* @return {Promise} a promise to resolve the finalized xml
|
|
194
193
|
*/
|
|
195
|
-
encryptAssertion(sourceEntity: any, targetEntity: any, xml?: string
|
|
194
|
+
encryptAssertion(sourceEntity: any, targetEntity: any, xml?: string): Promise<string>;
|
|
196
195
|
/**
|
|
197
196
|
* @desc Decrypt the assertion section in Response
|
|
198
197
|
* @param {string} type only accept SAMLResponse to proceed decryption
|
package/types/src/metadata.d.ts
CHANGED
package/types/src/types.d.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
1
|
import { LoginResponseTemplate } from './libsaml';
|
|
3
2
|
export { IdentityProvider as IdentityProviderConstructor } from './entity-idp';
|
|
4
3
|
export { IdpMetadata as IdentityProviderMetadata } from './metadata-idp';
|
|
5
4
|
export { ServiceProvider as ServiceProviderConstructor } from './entity-sp';
|
|
6
5
|
export { SpMetadata as ServiceProviderMetadata } from './metadata-sp';
|
|
7
|
-
export
|
|
8
|
-
|
|
6
|
+
export type MetadataFile = string | Buffer;
|
|
7
|
+
type SSOService = {
|
|
9
8
|
isDefault?: boolean;
|
|
10
9
|
Binding: string;
|
|
11
10
|
Location: string;
|
|
@@ -20,7 +19,7 @@ export interface MetadataIdpOptions {
|
|
|
20
19
|
singleLogoutService?: SSOService[];
|
|
21
20
|
requestSignatureAlgorithm?: string;
|
|
22
21
|
}
|
|
23
|
-
export
|
|
22
|
+
export type MetadataIdpConstructor = MetadataIdpOptions | MetadataFile;
|
|
24
23
|
export interface MetadataSpOptions {
|
|
25
24
|
entityID?: string;
|
|
26
25
|
signingCert?: string | Buffer | (string | Buffer)[];
|
|
@@ -37,8 +36,8 @@ export interface MetadataSpOptions {
|
|
|
37
36
|
assertionConsumerService?: SSOService[];
|
|
38
37
|
elementsOrder?: string[];
|
|
39
38
|
}
|
|
40
|
-
export
|
|
41
|
-
export
|
|
39
|
+
export type MetadataSpConstructor = MetadataSpOptions | MetadataFile;
|
|
40
|
+
export type EntitySetting = ServiceProviderSettings & IdentityProviderSettings;
|
|
42
41
|
export interface SignatureConfig {
|
|
43
42
|
prefix?: string;
|
|
44
43
|
location?: {
|
|
@@ -49,7 +48,7 @@ export interface SignatureConfig {
|
|
|
49
48
|
export interface SAMLDocumentTemplate {
|
|
50
49
|
context?: string;
|
|
51
50
|
}
|
|
52
|
-
export
|
|
51
|
+
export type ServiceProviderSettings = {
|
|
53
52
|
metadata?: string | Buffer;
|
|
54
53
|
entityID?: string;
|
|
55
54
|
authnRequestsSigned?: boolean;
|
|
@@ -76,7 +75,7 @@ export declare type ServiceProviderSettings = {
|
|
|
76
75
|
relayState?: string;
|
|
77
76
|
clockDrifts?: [number, number];
|
|
78
77
|
};
|
|
79
|
-
export
|
|
78
|
+
export type IdentityProviderSettings = {
|
|
80
79
|
metadata?: string | Buffer;
|
|
81
80
|
/** signature algorithm */
|
|
82
81
|
requestSignatureAlgorithm?: string;
|
package/types/src/utility.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
1
|
/**
|
|
3
2
|
* @desc Mimic lodash.zipObject
|
|
4
3
|
* @param arr1 {string[]}
|
|
@@ -35,7 +34,7 @@ export declare function get(obj: any, path: any, defaultValue: any): any;
|
|
|
35
34
|
* @desc Check if the input is string
|
|
36
35
|
* @param {any} input
|
|
37
36
|
*/
|
|
38
|
-
export declare function isString(input: any):
|
|
37
|
+
export declare function isString(input: any): input is string;
|
|
39
38
|
/**
|
|
40
39
|
* @desc Encode string with base64 format
|
|
41
40
|
* @param {string} message plain-text message
|
|
@@ -116,6 +115,12 @@ declare function convertToString(input: any, isOutputString: any): any;
|
|
|
116
115
|
export declare function isNonEmptyArray(a: any): boolean;
|
|
117
116
|
export declare function castArrayOpt<T>(a?: T | T[]): T[];
|
|
118
117
|
export declare function notEmpty<TValue>(value: TValue | null | undefined): value is TValue;
|
|
118
|
+
/**
|
|
119
|
+
* @desc Escape a string for safe use inside an XPath single-quoted string literal.
|
|
120
|
+
* Prevents XPath injection by splitting on single quotes and using concat().
|
|
121
|
+
*/
|
|
122
|
+
export declare function escapeXPathValue(value: string): string;
|
|
123
|
+
export declare function camelCase(input: string): string;
|
|
119
124
|
declare const utility: {
|
|
120
125
|
isString: typeof isString;
|
|
121
126
|
base64Encode: typeof base64Encode;
|
package/types/src/validator.d.ts
CHANGED
package/.travis.yml
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
language: node_js
|
|
2
|
-
|
|
3
|
-
node_js:
|
|
4
|
-
- "16"
|
|
5
|
-
- "18"
|
|
6
|
-
- "20"
|
|
7
|
-
|
|
8
|
-
env:
|
|
9
|
-
- INSTALL_JDK=1
|
|
10
|
-
- INSTALL_JDK=0
|
|
11
|
-
|
|
12
|
-
before_install:
|
|
13
|
-
- if [[ "$INSTALL_JDK" == "1" ]] ; then make install_jdk ; fi
|
|
14
|
-
|
|
15
|
-
install:
|
|
16
|
-
- yarn install --production=true
|
|
17
|
-
|
|
18
|
-
script:
|
|
19
|
-
- yarn add @authenio/samlify-xsd-schema-validator
|
|
20
|
-
- yarn test --timeout=30s
|
|
21
|
-
|
|
22
|
-
branches:
|
|
23
|
-
only:
|
|
24
|
-
- master
|
|
25
|
-
- /^.*-alpha$/
|
|
26
|
-
- /^.*-rc.*$/
|
|
27
|
-
- /^feature\/.*$/
|
|
28
|
-
|
|
29
|
-
after_success: npm run coverage
|
package/CHANGELOG.md
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
# 2.10.1
|
|
2
|
-
|
|
3
|
-
* Changes to libsaml.ts verifySignature. This is an internal function, but we still document changes
|
|
4
|
-
- Does not raise error when signature is missing/invalid. Instead it now returns false. This is to simplify logic
|
|
5
|
-
- When there are encrypted assertions, returns the entire response, as the "verifiedAssertionNode"
|
|
6
|
-
|
|
7
|
-
* Fix logic around handling encrypted assertions
|
package/types/vitest.config.d.ts
DELETED