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.
- package/README.md +1 -1
- package/build/src/api.js +52 -3
- package/build/src/api.js.map +1 -1
- package/build/src/binding-post.js +236 -182
- package/build/src/binding-post.js.map +1 -1
- package/build/src/binding-redirect.js +303 -215
- package/build/src/binding-redirect.js.map +1 -1
- package/build/src/binding-simplesign.js +285 -137
- package/build/src/binding-simplesign.js.map +1 -1
- package/build/src/entity-idp.js +130 -47
- package/build/src/entity-idp.js.map +1 -1
- package/build/src/entity-sp.js +81 -39
- package/build/src/entity-sp.js.map +1 -1
- package/build/src/entity.js +100 -62
- package/build/src/entity.js.map +1 -1
- package/build/src/extractor.js +119 -155
- package/build/src/extractor.js.map +1 -1
- package/build/src/flow.js +100 -96
- package/build/src/flow.js.map +1 -1
- package/build/src/libsaml.js +318 -261
- package/build/src/libsaml.js.map +1 -1
- package/build/src/metadata-idp.js +60 -30
- package/build/src/metadata-idp.js.map +1 -1
- package/build/src/metadata-sp.js +51 -41
- package/build/src/metadata-sp.js.map +1 -1
- package/build/src/metadata.js +47 -43
- package/build/src/metadata.js.map +1 -1
- package/build/src/options.js +73 -0
- package/build/src/options.js.map +1 -0
- package/build/src/urn.js +28 -1
- package/build/src/urn.js.map +1 -1
- package/build/src/utility.js +165 -83
- package/build/src/utility.js.map +1 -1
- package/build/src/validator.js +27 -10
- package/build/src/validator.js.map +1 -1
- package/package.json +17 -7
- package/types/src/api.d.ts +33 -3
- package/types/src/binding-post.d.ts +67 -34
- package/types/src/binding-redirect.d.ts +58 -31
- package/types/src/binding-simplesign.d.ts +77 -21
- package/types/src/entity-idp.d.ts +40 -31
- package/types/src/entity-sp.d.ts +37 -27
- package/types/src/entity.d.ts +71 -77
- package/types/src/extractor.d.ts +31 -22
- package/types/src/flow.d.ts +24 -2
- package/types/src/libsaml.d.ts +172 -118
- package/types/src/metadata-idp.d.ts +27 -11
- package/types/src/metadata-sp.d.ts +29 -19
- package/types/src/metadata.d.ts +59 -34
- package/types/src/options.d.ts +37 -0
- package/types/src/types.d.ts +250 -24
- package/types/src/urn.d.ts +7 -0
- package/types/src/utility.d.ts +144 -89
- package/types/src/validator.d.ts +21 -0
- package/.circleci/config.yml +0 -98
- package/.editorconfig +0 -19
- package/.github/FUNDING.yml +0 -1
- package/.github/workflows/deploy-docs.yml +0 -56
- package/.pre-commit.sh +0 -15
- package/.snyk +0 -4
- package/Makefile +0 -25
- package/index.ts +0 -28
- package/src/api.ts +0 -36
- package/src/binding-post.ts +0 -336
- package/src/binding-redirect.ts +0 -335
- package/src/binding-simplesign.ts +0 -231
- package/src/entity-idp.ts +0 -145
- package/src/entity-sp.ts +0 -114
- package/src/entity.ts +0 -243
- package/src/extractor.ts +0 -399
- package/src/flow.ts +0 -469
- package/src/libsaml.ts +0 -777
- package/src/metadata-idp.ts +0 -146
- package/src/metadata-sp.ts +0 -203
- package/src/metadata.ts +0 -166
- package/src/types.ts +0 -127
- package/src/urn.ts +0 -210
- package/src/utility.ts +0 -231
- package/src/validator.ts +0 -44
- package/tsconfig.json +0 -41
- package/tslint.json +0 -35
- package/types.d.ts +0 -2
- 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
|
-
}
|