cloudcms-server 3.3.1-beta.8 → 4.0.0-beta.1

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 (109) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +1 -1
  3. package/broadcast/broadcast.js +6 -3
  4. package/broadcast/providers/redis.js +24 -49
  5. package/clients/nrp.js +117 -0
  6. package/clients/redis.js +64 -0
  7. package/d1/index.js +629 -0
  8. package/d1/index.js.works +203 -0
  9. package/d1/package.json +86 -0
  10. package/d1/package.json.works +14 -0
  11. package/duster/helpers/sample/nyt.js +2 -1
  12. package/framework/controllers.js +4 -4
  13. package/index.js +26 -14
  14. package/insight/insight.js +1 -1
  15. package/launchpad/index.js +203 -11
  16. package/launchpad/launchers/cluster.js +103 -110
  17. package/launchpad/launchers/redis.js +70 -0
  18. package/launchpad/launchers/single.js +36 -22
  19. package/locks/locks.js +63 -9
  20. package/locks/providers/cluster.js +3 -1
  21. package/locks/providers/memory.js +10 -7
  22. package/locks/providers/redis.js +62 -82
  23. package/middleware/admin/admin.js +3 -3
  24. package/middleware/authentication/adapters/session.js +11 -8
  25. package/middleware/authentication/authentication.js +28 -16
  26. package/middleware/authentication/authenticators/default.js +5 -2
  27. package/middleware/authentication/authenticators/session.js +5 -2
  28. package/middleware/authentication/providers/saml.js +1 -1
  29. package/middleware/authorization/authorization.js +11 -8
  30. package/middleware/awareness/awareness.js +55 -31
  31. package/middleware/awareness/plugins/editorial.js +4 -4
  32. package/middleware/awareness/providers/abstract-async.js +107 -84
  33. package/middleware/awareness/providers/abstract.js +1 -1
  34. package/middleware/awareness/providers/memory.js +0 -14
  35. package/middleware/awareness/providers/redis.js +186 -279
  36. package/middleware/cache/cache.js +4 -2
  37. package/middleware/cache/providers/redis.js +127 -89
  38. package/middleware/cache/providers/shared-memory.js +3 -3
  39. package/middleware/cloudcms/cloudcms.js +22 -16
  40. package/middleware/form/form.js +3 -3
  41. package/middleware/modules/modules.js +63 -10
  42. package/middleware/proxy/proxy.js +8 -21
  43. package/middleware/stores/stores.js +48 -5
  44. package/middleware/themes/themes.js +49 -0
  45. package/middleware/virtual-config/virtual-config.js +11 -8
  46. package/middleware/wcm/wcm.js +4 -4
  47. package/notifications/notifications.js +27 -4
  48. package/package.json +30 -25
  49. package/server/index.js +508 -412
  50. package/server/standalone.js +9 -0
  51. package/temp/clusterlock/index.js +3 -3
  52. package/temp/clusterlock/package.json +1 -1
  53. package/temp/passport-saml/LICENSE +23 -0
  54. package/temp/passport-saml/README.md +406 -0
  55. package/temp/passport-saml/lib/node-saml/algorithms.d.ts +5 -0
  56. package/temp/passport-saml/lib/node-saml/algorithms.js +41 -0
  57. package/temp/passport-saml/lib/node-saml/algorithms.js.map +1 -0
  58. package/temp/passport-saml/lib/node-saml/index.d.ts +3 -0
  59. package/temp/passport-saml/lib/node-saml/index.js +6 -0
  60. package/temp/passport-saml/lib/node-saml/index.js.map +1 -0
  61. package/temp/passport-saml/lib/node-saml/inmemory-cache-provider.d.ts +45 -0
  62. package/temp/passport-saml/lib/node-saml/inmemory-cache-provider.js +86 -0
  63. package/temp/passport-saml/lib/node-saml/inmemory-cache-provider.js.map +1 -0
  64. package/temp/passport-saml/lib/node-saml/saml-post-signing.d.ts +3 -0
  65. package/temp/passport-saml/lib/node-saml/saml-post-signing.js +15 -0
  66. package/temp/passport-saml/lib/node-saml/saml-post-signing.js.map +1 -0
  67. package/temp/passport-saml/lib/node-saml/saml.d.ts +77 -0
  68. package/temp/passport-saml/lib/node-saml/saml.js +1170 -0
  69. package/temp/passport-saml/lib/node-saml/saml.js.map +1 -0
  70. package/temp/passport-saml/lib/node-saml/types.d.ts +95 -0
  71. package/temp/passport-saml/lib/node-saml/types.js +8 -0
  72. package/temp/passport-saml/lib/node-saml/types.js.map +1 -0
  73. package/temp/passport-saml/lib/node-saml/utility.d.ts +3 -0
  74. package/temp/passport-saml/lib/node-saml/utility.js +19 -0
  75. package/temp/passport-saml/lib/node-saml/utility.js.map +1 -0
  76. package/temp/passport-saml/lib/node-saml/xml.d.ts +21 -0
  77. package/temp/passport-saml/lib/node-saml/xml.js +140 -0
  78. package/temp/passport-saml/lib/node-saml/xml.js.map +1 -0
  79. package/temp/passport-saml/lib/passport-saml/index.d.ts +6 -0
  80. package/temp/passport-saml/lib/passport-saml/index.js +11 -0
  81. package/temp/passport-saml/lib/passport-saml/index.js.map +1 -0
  82. package/temp/passport-saml/lib/passport-saml/multiSamlStrategy.d.ts +13 -0
  83. package/temp/passport-saml/lib/passport-saml/multiSamlStrategy.js +63 -0
  84. package/temp/passport-saml/lib/passport-saml/multiSamlStrategy.js.map +1 -0
  85. package/temp/passport-saml/lib/passport-saml/strategy.d.ts +20 -0
  86. package/temp/passport-saml/lib/passport-saml/strategy.js +167 -0
  87. package/temp/passport-saml/lib/passport-saml/strategy.js.map +1 -0
  88. package/temp/passport-saml/lib/passport-saml/types.d.ts +51 -0
  89. package/temp/passport-saml/lib/passport-saml/types.js +11 -0
  90. package/temp/passport-saml/lib/passport-saml/types.js.map +1 -0
  91. package/temp/passport-saml/package.json +96 -0
  92. package/util/auth.js +6 -6
  93. package/util/cloudcms.js +85 -88
  94. package/util/proxy-factory.js +159 -268
  95. package/util/redis.js +113 -0
  96. package/util/renditions.js +12 -6
  97. package/util/request.js +48 -12
  98. package/util/util.js +16 -2
  99. package/launchpad/launchers/sticky-cluster.js +0 -43
  100. package/temp/memored/.jshintrc +0 -4
  101. package/temp/memored/README.md +0 -240
  102. package/temp/memored/demo/demo1.js +0 -37
  103. package/temp/memored/demo/demo2.js +0 -32
  104. package/temp/memored/gulpfile.js +0 -8
  105. package/temp/memored/index.js +0 -343
  106. package/temp/memored/package.json +0 -54
  107. package/temp/memored/spec/memored.spec.js +0 -265
  108. package/web/cms/ice.js +0 -109
  109. package/web/cms/preview.js +0 -106
@@ -0,0 +1,1170 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SAML = void 0;
4
+ const debug_1 = require("debug");
5
+ const debug = (0, debug_1.default)("node-saml");
6
+ const zlib = require("zlib");
7
+ const crypto = require("crypto");
8
+ const url_1 = require("url");
9
+ const querystring = require("querystring");
10
+ const util = require("util");
11
+ const inmemory_cache_provider_1 = require("./inmemory-cache-provider");
12
+ const algorithms = require("./algorithms");
13
+ const saml_post_signing_1 = require("./saml-post-signing");
14
+ const types_1 = require("./types");
15
+ const types_2 = require("../passport-saml/types");
16
+ const utility_1 = require("./utility");
17
+ const xml_1 = require("./xml");
18
+ const inflateRawAsync = util.promisify(zlib.inflateRaw);
19
+ const deflateRawAsync = util.promisify(zlib.deflateRaw);
20
+ async function processValidlySignedPostRequestAsync(self, doc, dom) {
21
+ const request = doc.LogoutRequest;
22
+ if (request) {
23
+ const profile = {};
24
+ if (request.$.ID) {
25
+ profile.ID = request.$.ID;
26
+ }
27
+ else {
28
+ throw new Error("Missing SAML LogoutRequest ID");
29
+ }
30
+ const issuer = request.Issuer;
31
+ if (issuer && issuer[0]._) {
32
+ profile.issuer = issuer[0]._;
33
+ }
34
+ else {
35
+ throw new Error("Missing SAML issuer");
36
+ }
37
+ const nameID = await self._getNameIdAsync(self, dom);
38
+ if (nameID) {
39
+ profile.nameID = nameID.value;
40
+ if (nameID.format) {
41
+ profile.nameIDFormat = nameID.format;
42
+ }
43
+ }
44
+ else {
45
+ throw new Error("Missing SAML NameID");
46
+ }
47
+ const sessionIndex = request.SessionIndex;
48
+ if (sessionIndex) {
49
+ profile.sessionIndex = sessionIndex[0]._;
50
+ }
51
+ return { profile, loggedOut: true };
52
+ }
53
+ else {
54
+ throw new Error("Unknown SAML request message");
55
+ }
56
+ }
57
+ async function processValidlySignedSamlLogoutAsync(self, doc, dom) {
58
+ const response = doc.LogoutResponse;
59
+ const request = doc.LogoutRequest;
60
+ if (response) {
61
+ return { profile: null, loggedOut: true };
62
+ }
63
+ else if (request) {
64
+ return await processValidlySignedPostRequestAsync(self, doc, dom);
65
+ }
66
+ else {
67
+ throw new Error("Unknown SAML response message");
68
+ }
69
+ }
70
+ async function promiseWithNameID(nameid) {
71
+ const format = xml_1.xpath.selectAttributes(nameid, "@Format");
72
+ return {
73
+ value: nameid.textContent,
74
+ format: format && format[0] && format[0].nodeValue,
75
+ };
76
+ }
77
+ class SAML {
78
+ constructor(ctorOptions) {
79
+ this.options = this.initialize(ctorOptions);
80
+ this.cacheProvider = this.options.cacheProvider;
81
+ }
82
+ initialize(ctorOptions) {
83
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y;
84
+ if (!ctorOptions) {
85
+ throw new TypeError("SamlOptions required on construction");
86
+ }
87
+ const options = {
88
+ ...ctorOptions,
89
+ passive: (_a = ctorOptions.passive) !== null && _a !== void 0 ? _a : false,
90
+ disableRequestedAuthnContext: (_b = ctorOptions.disableRequestedAuthnContext) !== null && _b !== void 0 ? _b : false,
91
+ additionalParams: (_c = ctorOptions.additionalParams) !== null && _c !== void 0 ? _c : {},
92
+ additionalAuthorizeParams: (_d = ctorOptions.additionalAuthorizeParams) !== null && _d !== void 0 ? _d : {},
93
+ additionalLogoutParams: (_e = ctorOptions.additionalLogoutParams) !== null && _e !== void 0 ? _e : {},
94
+ forceAuthn: (_f = ctorOptions.forceAuthn) !== null && _f !== void 0 ? _f : false,
95
+ skipRequestCompression: (_g = ctorOptions.skipRequestCompression) !== null && _g !== void 0 ? _g : false,
96
+ disableRequestAcsUrl: (_h = ctorOptions.disableRequestAcsUrl) !== null && _h !== void 0 ? _h : false,
97
+ acceptedClockSkewMs: (_j = ctorOptions.acceptedClockSkewMs) !== null && _j !== void 0 ? _j : 0,
98
+ maxAssertionAgeMs: (_k = ctorOptions.maxAssertionAgeMs) !== null && _k !== void 0 ? _k : 0,
99
+ path: (_l = ctorOptions.path) !== null && _l !== void 0 ? _l : "/saml/consume",
100
+ host: (_m = ctorOptions.host) !== null && _m !== void 0 ? _m : "localhost",
101
+ issuer: (_o = ctorOptions.issuer) !== null && _o !== void 0 ? _o : "onelogin_saml",
102
+ identifierFormat: ctorOptions.identifierFormat === undefined
103
+ ? "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
104
+ : ctorOptions.identifierFormat,
105
+ wantAssertionsSigned: (_p = ctorOptions.wantAssertionsSigned) !== null && _p !== void 0 ? _p : false,
106
+ authnContext: (_q = ctorOptions.authnContext) !== null && _q !== void 0 ? _q : [
107
+ "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport",
108
+ ],
109
+ validateInResponseTo: (_r = ctorOptions.validateInResponseTo) !== null && _r !== void 0 ? _r : false,
110
+ //cert: (0, utility_1.assertRequired)(ctorOptions.cert, "cert is required"),
111
+ requestIdExpirationPeriodMs: (_s = ctorOptions.requestIdExpirationPeriodMs) !== null && _s !== void 0 ? _s : 28800000,
112
+ cacheProvider: (_t = ctorOptions.cacheProvider) !== null && _t !== void 0 ? _t : new inmemory_cache_provider_1.CacheProvider({
113
+ keyExpirationPeriodMs: ctorOptions.requestIdExpirationPeriodMs,
114
+ }),
115
+ logoutUrl: (_v = (_u = ctorOptions.logoutUrl) !== null && _u !== void 0 ? _u : ctorOptions.entryPoint) !== null && _v !== void 0 ? _v : "",
116
+ signatureAlgorithm: (_w = ctorOptions.signatureAlgorithm) !== null && _w !== void 0 ? _w : "sha1",
117
+ authnRequestBinding: (_x = ctorOptions.authnRequestBinding) !== null && _x !== void 0 ? _x : "HTTP-Redirect",
118
+ racComparison: (_y = ctorOptions.racComparison) !== null && _y !== void 0 ? _y : "exact",
119
+ };
120
+
121
+ // CUSTOM
122
+ options.validateAssertion = ctorOptions.validateAssertion;
123
+ if (typeof(options.validateAssertion) === "undefined") {
124
+ options.validateAssertion = true;
125
+ }
126
+ if (options.validateAssertion === false) {
127
+ delete options.cert;
128
+ }
129
+ else
130
+ {
131
+ options.cert = (0, utility_1.assertRequired)(ctorOptions.cert, "cert is required");
132
+ }
133
+
134
+ /**
135
+ * List of possible values:
136
+ * - exact : Assertion context must exactly match a context in the list
137
+ * - minimum: Assertion context must be at least as strong as a context in the list
138
+ * - maximum: Assertion context must be no stronger than a context in the list
139
+ * - better: Assertion context must be stronger than all contexts in the list
140
+ */
141
+ if (!["exact", "minimum", "maximum", "better"].includes(options.racComparison)) {
142
+ throw new TypeError("racComparison must be one of ['exact', 'minimum', 'maximum', 'better']");
143
+ }
144
+ return options;
145
+ }
146
+ getCallbackUrl(host) {
147
+ // Post-auth destination
148
+ if (this.options.callbackUrl) {
149
+ return this.options.callbackUrl;
150
+ }
151
+ else {
152
+ const url = new url_1.URL("http://localhost");
153
+ if (host) {
154
+ url.host = host;
155
+ }
156
+ else {
157
+ url.host = this.options.host;
158
+ }
159
+ if (this.options.protocol) {
160
+ url.protocol = this.options.protocol;
161
+ }
162
+ url.pathname = this.options.path;
163
+ return url.toString();
164
+ }
165
+ }
166
+ _generateUniqueID() {
167
+ return crypto.randomBytes(10).toString("hex");
168
+ }
169
+ generateInstant() {
170
+ return new Date().toISOString();
171
+ }
172
+ signRequest(samlMessage) {
173
+ this.options.privateKey = (0, utility_1.assertRequired)(this.options.privateKey, "privateKey is required");
174
+ const samlMessageToSign = {};
175
+ samlMessage.SigAlg = algorithms.getSigningAlgorithm(this.options.signatureAlgorithm);
176
+ const signer = algorithms.getSigner(this.options.signatureAlgorithm);
177
+ if (samlMessage.SAMLRequest) {
178
+ samlMessageToSign.SAMLRequest = samlMessage.SAMLRequest;
179
+ }
180
+ if (samlMessage.SAMLResponse) {
181
+ samlMessageToSign.SAMLResponse = samlMessage.SAMLResponse;
182
+ }
183
+ if (samlMessage.RelayState) {
184
+ samlMessageToSign.RelayState = samlMessage.RelayState;
185
+ }
186
+ if (samlMessage.SigAlg) {
187
+ samlMessageToSign.SigAlg = samlMessage.SigAlg;
188
+ }
189
+ signer.update(querystring.stringify(samlMessageToSign));
190
+ samlMessage.Signature = signer.sign(this._keyToPEM(this.options.privateKey), "base64");
191
+ }
192
+ async generateAuthorizeRequestAsync(isPassive, isHttpPostBinding, host) {
193
+ this.options.entryPoint = (0, utility_1.assertRequired)(this.options.entryPoint, "entryPoint is required");
194
+ const id = "_" + this._generateUniqueID();
195
+ const instant = this.generateInstant();
196
+ if (this.options.validateInResponseTo) {
197
+ await this.cacheProvider.saveAsync(id, instant);
198
+ }
199
+ const request = {
200
+ "samlp:AuthnRequest": {
201
+ "@xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol",
202
+ "@ID": id,
203
+ "@Version": "2.0",
204
+ "@IssueInstant": instant,
205
+ "@ProtocolBinding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
206
+ "@Destination": this.options.entryPoint,
207
+ "saml:Issuer": {
208
+ "@xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion",
209
+ "#text": this.options.issuer,
210
+ },
211
+ },
212
+ };
213
+ if (isPassive)
214
+ request["samlp:AuthnRequest"]["@IsPassive"] = true;
215
+ if (this.options.forceAuthn) {
216
+ request["samlp:AuthnRequest"]["@ForceAuthn"] = true;
217
+ }
218
+ if (!this.options.disableRequestAcsUrl) {
219
+ request["samlp:AuthnRequest"]["@AssertionConsumerServiceURL"] = this.getCallbackUrl(host);
220
+ }
221
+ if (this.options.identifierFormat != null) {
222
+ request["samlp:AuthnRequest"]["samlp:NameIDPolicy"] = {
223
+ "@xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol",
224
+ "@Format": this.options.identifierFormat,
225
+ "@AllowCreate": "true",
226
+ };
227
+ }
228
+ if (!this.options.disableRequestedAuthnContext) {
229
+ const authnContextClassRefs = [];
230
+ this.options.authnContext.forEach(function (value) {
231
+ authnContextClassRefs.push({
232
+ "@xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion",
233
+ "#text": value,
234
+ });
235
+ });
236
+ request["samlp:AuthnRequest"]["samlp:RequestedAuthnContext"] = {
237
+ "@xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol",
238
+ "@Comparison": this.options.racComparison,
239
+ "saml:AuthnContextClassRef": authnContextClassRefs,
240
+ };
241
+ }
242
+ if (this.options.attributeConsumingServiceIndex != null) {
243
+ request["samlp:AuthnRequest"]["@AttributeConsumingServiceIndex"] =
244
+ this.options.attributeConsumingServiceIndex;
245
+ }
246
+ if (this.options.providerName != null) {
247
+ request["samlp:AuthnRequest"]["@ProviderName"] = this.options.providerName;
248
+ }
249
+ if (this.options.scoping != null) {
250
+ const scoping = {
251
+ "@xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol",
252
+ };
253
+ if (typeof this.options.scoping.proxyCount === "number") {
254
+ scoping["@ProxyCount"] = this.options.scoping.proxyCount;
255
+ }
256
+ if (this.options.scoping.idpList) {
257
+ scoping["samlp:IDPList"] = this.options.scoping.idpList.map((idpListItem) => {
258
+ const formattedIdpListItem = {
259
+ "@xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol",
260
+ };
261
+ if (idpListItem.entries) {
262
+ formattedIdpListItem["samlp:IDPEntry"] = idpListItem.entries.map((entry) => {
263
+ const formattedEntry = {
264
+ "@xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol",
265
+ };
266
+ formattedEntry["@ProviderID"] = entry.providerId;
267
+ if (entry.name) {
268
+ formattedEntry["@Name"] = entry.name;
269
+ }
270
+ if (entry.loc) {
271
+ formattedEntry["@Loc"] = entry.loc;
272
+ }
273
+ return formattedEntry;
274
+ });
275
+ }
276
+ if (idpListItem.getComplete) {
277
+ formattedIdpListItem["samlp:GetComplete"] = idpListItem.getComplete;
278
+ }
279
+ return formattedIdpListItem;
280
+ });
281
+ }
282
+ if (this.options.scoping.requesterId) {
283
+ scoping["samlp:RequesterID"] = this.options.scoping.requesterId;
284
+ }
285
+ request["samlp:AuthnRequest"]["samlp:Scoping"] = scoping;
286
+ }
287
+ let stringRequest = (0, xml_1.buildXmlBuilderObject)(request, false);
288
+ // TODO: maybe we should always sign here
289
+ if (isHttpPostBinding && (0, types_1.isValidSamlSigningOptions)(this.options)) {
290
+ stringRequest = (0, saml_post_signing_1.signAuthnRequestPost)(stringRequest, this.options);
291
+ }
292
+ return stringRequest;
293
+ }
294
+ async _generateLogoutRequest(user) {
295
+ const id = "_" + this._generateUniqueID();
296
+ const instant = this.generateInstant();
297
+ const request = {
298
+ "samlp:LogoutRequest": {
299
+ "@xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol",
300
+ "@xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion",
301
+ "@ID": id,
302
+ "@Version": "2.0",
303
+ "@IssueInstant": instant,
304
+ "@Destination": this.options.logoutUrl,
305
+ "saml:Issuer": {
306
+ "@xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion",
307
+ "#text": this.options.issuer,
308
+ },
309
+ "saml:NameID": {
310
+ "@Format": user.nameIDFormat,
311
+ "#text": user.nameID,
312
+ },
313
+ },
314
+ };
315
+ if (user.nameQualifier != null) {
316
+ request["samlp:LogoutRequest"]["saml:NameID"]["@NameQualifier"] = user.nameQualifier;
317
+ }
318
+ if (user.spNameQualifier != null) {
319
+ request["samlp:LogoutRequest"]["saml:NameID"]["@SPNameQualifier"] = user.spNameQualifier;
320
+ }
321
+ if (user.sessionIndex) {
322
+ request["samlp:LogoutRequest"]["saml2p:SessionIndex"] = {
323
+ "@xmlns:saml2p": "urn:oasis:names:tc:SAML:2.0:protocol",
324
+ "#text": user.sessionIndex,
325
+ };
326
+ }
327
+ await this.cacheProvider.saveAsync(id, instant);
328
+ return (0, xml_1.buildXmlBuilderObject)(request, false);
329
+ }
330
+ _generateLogoutResponse(logoutRequest) {
331
+ const id = "_" + this._generateUniqueID();
332
+ const instant = this.generateInstant();
333
+ const request = {
334
+ "samlp:LogoutResponse": {
335
+ "@xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol",
336
+ "@xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion",
337
+ "@ID": id,
338
+ "@Version": "2.0",
339
+ "@IssueInstant": instant,
340
+ "@Destination": this.options.logoutUrl,
341
+ "@InResponseTo": logoutRequest.ID,
342
+ "saml:Issuer": {
343
+ "#text": this.options.issuer,
344
+ },
345
+ "samlp:Status": {
346
+ "samlp:StatusCode": {
347
+ "@Value": "urn:oasis:names:tc:SAML:2.0:status:Success",
348
+ },
349
+ },
350
+ },
351
+ };
352
+ return (0, xml_1.buildXmlBuilderObject)(request, false);
353
+ }
354
+ async _requestToUrlAsync(request, response, operation, additionalParameters) {
355
+ this.options.entryPoint = (0, utility_1.assertRequired)(this.options.entryPoint, "entryPoint is required");
356
+ let buffer;
357
+ if (this.options.skipRequestCompression) {
358
+ buffer = Buffer.from((request || response), "utf8");
359
+ }
360
+ else {
361
+ buffer = await deflateRawAsync((request || response));
362
+ }
363
+ const base64 = buffer.toString("base64");
364
+ let target = new url_1.URL(this.options.entryPoint);
365
+ if (operation === "logout") {
366
+ if (this.options.logoutUrl) {
367
+ target = new url_1.URL(this.options.logoutUrl);
368
+ }
369
+ }
370
+ else if (operation !== "authorize") {
371
+ throw new Error("Unknown operation: " + operation);
372
+ }
373
+ const samlMessage = request
374
+ ? {
375
+ SAMLRequest: base64,
376
+ }
377
+ : {
378
+ SAMLResponse: base64,
379
+ };
380
+ Object.keys(additionalParameters).forEach((k) => {
381
+ samlMessage[k] = additionalParameters[k];
382
+ });
383
+ if (this.options.privateKey != null) {
384
+ if (!this.options.entryPoint) {
385
+ throw new Error('"entryPoint" config parameter is required for signed messages');
386
+ }
387
+ // sets .SigAlg and .Signature
388
+ this.signRequest(samlMessage);
389
+ }
390
+ Object.keys(samlMessage).forEach((k) => {
391
+ target.searchParams.set(k, samlMessage[k]);
392
+ });
393
+ return target.toString();
394
+ }
395
+ _getAdditionalParams(RelayState, operation, overrideParams) {
396
+ const additionalParams = {};
397
+ if (typeof RelayState === "string" && RelayState.length > 0) {
398
+ additionalParams.RelayState = RelayState;
399
+ }
400
+ const optionsAdditionalParams = this.options.additionalParams;
401
+ Object.keys(optionsAdditionalParams).forEach(function (k) {
402
+ additionalParams[k] = optionsAdditionalParams[k];
403
+ });
404
+ let optionsAdditionalParamsForThisOperation = {};
405
+ if (operation == "authorize") {
406
+ optionsAdditionalParamsForThisOperation = this.options.additionalAuthorizeParams;
407
+ }
408
+ if (operation == "logout") {
409
+ optionsAdditionalParamsForThisOperation = this.options.additionalLogoutParams;
410
+ }
411
+ Object.keys(optionsAdditionalParamsForThisOperation).forEach(function (k) {
412
+ additionalParams[k] = optionsAdditionalParamsForThisOperation[k];
413
+ });
414
+ overrideParams = overrideParams !== null && overrideParams !== void 0 ? overrideParams : {};
415
+ Object.keys(overrideParams).forEach(function (k) {
416
+ additionalParams[k] = overrideParams[k];
417
+ });
418
+ return additionalParams;
419
+ }
420
+ async getAuthorizeUrlAsync(RelayState, host, options) {
421
+ const request = await this.generateAuthorizeRequestAsync(this.options.passive, false, host);
422
+ const operation = "authorize";
423
+ const overrideParams = options ? options.additionalParams || {} : {};
424
+ return await this._requestToUrlAsync(request, null, operation, this._getAdditionalParams(RelayState, operation, overrideParams));
425
+ }
426
+ async getAuthorizeFormAsync(RelayState, host) {
427
+ this.options.entryPoint = (0, utility_1.assertRequired)(this.options.entryPoint, "entryPoint is required");
428
+ // The quoteattr() function is used in a context, where the result will not be evaluated by javascript
429
+ // but must be interpreted by an XML or HTML parser, and it must absolutely avoid breaking the syntax
430
+ // of an element attribute.
431
+ const quoteattr = function (s, preserveCR) {
432
+ const preserveCRChar = preserveCR ? "
" : "\n";
433
+ return (("" + s) // Forces the conversion to string.
434
+ .replace(/&/g, "&") // This MUST be the 1st replacement.
435
+ .replace(/'/g, "'") // The 4 other predefined entities, required.
436
+ .replace(/"/g, """)
437
+ .replace(/</g, "&lt;")
438
+ .replace(/>/g, "&gt;")
439
+ // Add other replacements here for HTML only
440
+ // Or for XML, only if the named entities are defined in its DTD.
441
+ .replace(/\r\n/g, preserveCRChar) // Must be before the next replacement.
442
+ .replace(/[\r\n]/g, preserveCRChar));
443
+ };
444
+ const request = await this.generateAuthorizeRequestAsync(this.options.passive, true, host);
445
+ let buffer;
446
+ if (this.options.skipRequestCompression) {
447
+ buffer = Buffer.from(request, "utf8");
448
+ }
449
+ else {
450
+ buffer = await deflateRawAsync(request);
451
+ }
452
+ const operation = "authorize";
453
+ const additionalParameters = this._getAdditionalParams(RelayState, operation);
454
+ const samlMessage = {
455
+ SAMLRequest: buffer.toString("base64"),
456
+ };
457
+ Object.keys(additionalParameters).forEach((k) => {
458
+ samlMessage[k] = additionalParameters[k] || "";
459
+ });
460
+ const formInputs = Object.keys(samlMessage)
461
+ .map((k) => {
462
+ return '<input type="hidden" name="' + k + '" value="' + quoteattr(samlMessage[k]) + '" />';
463
+ })
464
+ .join("\r\n");
465
+ return [
466
+ "<!DOCTYPE html>",
467
+ "<html>",
468
+ "<head>",
469
+ '<meta charset="utf-8">',
470
+ '<meta http-equiv="x-ua-compatible" content="ie=edge">',
471
+ "</head>",
472
+ '<body onload="document.forms[0].submit()">',
473
+ "<noscript>",
474
+ "<p><strong>Note:</strong> Since your browser does not support JavaScript, you must press the button below once to proceed.</p>",
475
+ "</noscript>",
476
+ '<form method="post" action="' + encodeURI(this.options.entryPoint) + '">',
477
+ formInputs,
478
+ '<input type="submit" value="Submit" />',
479
+ "</form>",
480
+ '<script>document.forms[0].style.display="none";</script>',
481
+ "</body>",
482
+ "</html>",
483
+ ].join("\r\n");
484
+ }
485
+ async getLogoutUrlAsync(user, RelayState, options) {
486
+ const request = await this._generateLogoutRequest(user);
487
+ const operation = "logout";
488
+ const overrideParams = options ? options.additionalParams || {} : {};
489
+ return await this._requestToUrlAsync(request, null, operation, this._getAdditionalParams(RelayState, operation, overrideParams));
490
+ }
491
+ getLogoutResponseUrl(samlLogoutRequest, RelayState, options, callback) {
492
+ util.callbackify(() => this.getLogoutResponseUrlAsync(samlLogoutRequest, RelayState, options))(callback);
493
+ }
494
+ async getLogoutResponseUrlAsync(samlLogoutRequest, RelayState, options // add RelayState,
495
+ ) {
496
+ const response = this._generateLogoutResponse(samlLogoutRequest);
497
+ const operation = "logout";
498
+ const overrideParams = options ? options.additionalParams || {} : {};
499
+ return await this._requestToUrlAsync(null, response, operation, this._getAdditionalParams(RelayState, operation, overrideParams));
500
+ }
501
+ _certToPEM(cert) {
502
+ cert = cert.match(/.{1,64}/g).join("\n");
503
+ if (cert.indexOf("-BEGIN CERTIFICATE-") === -1)
504
+ cert = "-----BEGIN CERTIFICATE-----\n" + cert;
505
+ if (cert.indexOf("-END CERTIFICATE-") === -1)
506
+ cert = cert + "\n-----END CERTIFICATE-----\n";
507
+ return cert;
508
+ }
509
+ async certsToCheck() {
510
+ let checkedCerts;
511
+ if (typeof this.options.cert === "function") {
512
+ checkedCerts = await util
513
+ .promisify(this.options.cert)()
514
+ .then((certs) => {
515
+ certs = (0, utility_1.assertRequired)(certs, "callback didn't return cert");
516
+ if (!Array.isArray(certs)) {
517
+ certs = [certs];
518
+ }
519
+ return certs;
520
+ });
521
+ }
522
+ else if (Array.isArray(this.options.cert)) {
523
+ checkedCerts = this.options.cert;
524
+ }
525
+ else {
526
+ checkedCerts = [this.options.cert];
527
+ }
528
+ // CUSTOM: don't assert required certs if validateAssertion is false
529
+ if (this.options.validateAssertion == false)
530
+ {
531
+ // skip
532
+ }
533
+ else
534
+ {
535
+ checkedCerts.forEach((cert) => {
536
+ (0, utility_1.assertRequired)(cert, "unknown cert found");
537
+ });
538
+ }
539
+ return checkedCerts;
540
+ }
541
+ // This function checks that the |currentNode| in the |fullXml| document contains exactly 1 valid
542
+ // signature of the |currentNode|.
543
+ //
544
+ // See https://github.com/bergie/passport-saml/issues/19 for references to some of the attack
545
+ // vectors against SAML signature verification.
546
+ validateSignature(fullXml, currentNode, certs) {
547
+ const xpathSigQuery = ".//*[" +
548
+ "local-name(.)='Signature' and " +
549
+ "namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#' and " +
550
+ "descendant::*[local-name(.)='Reference' and @URI='#" +
551
+ currentNode.getAttribute("ID") +
552
+ "']" +
553
+ "]";
554
+ const signatures = xml_1.xpath.selectElements(currentNode, xpathSigQuery);
555
+ // This function is expecting to validate exactly one signature, so if we find more or fewer
556
+ // than that, reject.
557
+ if (signatures.length !== 1) {
558
+ return false;
559
+ }
560
+ const xpathTransformQuery = ".//*[" +
561
+ "local-name(.)='Transform' and " +
562
+ "namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#' and " +
563
+ "ancestor::*[local-name(.)='Reference' and @URI='#" +
564
+ currentNode.getAttribute("ID") +
565
+ "']" +
566
+ "]";
567
+ const transforms = xml_1.xpath.selectElements(currentNode, xpathTransformQuery);
568
+ // Reject also XMLDSIG with more than 2 Transform
569
+ if (transforms.length > 2) {
570
+ // do not return false, throw an error so that it can be caught by tests differently
571
+ throw new Error("Invalid signature, too many transforms");
572
+ }
573
+ const signature = signatures[0];
574
+ return certs.some((certToCheck) => {
575
+ return (0, xml_1.validateXmlSignatureForCert)(signature, this._certToPEM(certToCheck), fullXml, currentNode);
576
+ });
577
+ }
578
+ async validatePostResponseAsync(container, validateAssertion) {
579
+ let xml, doc, inResponseTo;
580
+ try {
581
+ xml = Buffer.from(container.SAMLResponse, "base64").toString("utf8");
582
+ doc = (0, xml_1.parseDomFromString)(xml);
583
+ if (!Object.prototype.hasOwnProperty.call(doc, "documentElement"))
584
+ throw new Error("SAMLResponse is not valid base64-encoded XML");
585
+ const inResponseToNodes = xml_1.xpath.selectAttributes(doc, "/*[local-name()='Response']/@InResponseTo");
586
+ if (inResponseToNodes) {
587
+ inResponseTo = inResponseToNodes.length ? inResponseToNodes[0].nodeValue : null;
588
+ await this.validateInResponseTo(inResponseTo);
589
+ }
590
+ // Check if this document has a valid top-level signature
591
+ var certs = await this.certsToCheck();
592
+ let validSignature = false;
593
+ if (validateAssertion === false)
594
+ {
595
+ validSignature = true;
596
+ }
597
+ else if (this.validateSignature(xml, doc.documentElement, certs)) {
598
+ validSignature = true;
599
+ }
600
+ const assertions = xml_1.xpath.selectElements(doc, "/*[local-name()='Response']/*[local-name()='Assertion']");
601
+ const encryptedAssertions = xml_1.xpath.selectElements(doc, "/*[local-name()='Response']/*[local-name()='EncryptedAssertion']");
602
+ if (assertions.length + encryptedAssertions.length > 1) {
603
+ // There's no reason I know of that we want to handle multiple assertions, and it seems like a
604
+ // potential risk vector for signature scope issues, so treat this as an invalid signature
605
+ throw new Error("Invalid signature: multiple assertions");
606
+ }
607
+ if (assertions.length == 1) {
608
+ if ((this.options.wantAssertionsSigned || !validSignature) &&
609
+ !this.validateSignature(xml, assertions[0], certs)) {
610
+ throw new Error("Invalid signature");
611
+ }
612
+ return await this.processValidlySignedAssertionAsync(assertions[0].toString(), xml, inResponseTo);
613
+ }
614
+ if (encryptedAssertions.length == 1) {
615
+ this.options.decryptionPvk = (0, utility_1.assertRequired)(this.options.decryptionPvk, "No decryption key for encrypted SAML response");
616
+ const encryptedAssertionXml = encryptedAssertions[0].toString();
617
+ const decryptedXml = await (0, xml_1.decryptXml)(encryptedAssertionXml, this.options.decryptionPvk);
618
+ const decryptedDoc = (0, xml_1.parseDomFromString)(decryptedXml);
619
+ const decryptedAssertions = xml_1.xpath.selectElements(decryptedDoc, "/*[local-name()='Assertion']");
620
+ if (decryptedAssertions.length != 1)
621
+ throw new Error("Invalid EncryptedAssertion content");
622
+ if ((this.options.wantAssertionsSigned || !validSignature) &&
623
+ !this.validateSignature(decryptedXml, decryptedAssertions[0], certs)) {
624
+ throw new Error("Invalid signature from encrypted assertion");
625
+ }
626
+ return await this.processValidlySignedAssertionAsync(decryptedAssertions[0].toString(), xml, inResponseTo);
627
+ }
628
+ // If there's no assertion, fall back on xml2js response parsing for the status &
629
+ // LogoutResponse code.
630
+ const xmljsDoc = await (0, xml_1.parseXml2JsFromString)(xml);
631
+ const response = xmljsDoc.Response;
632
+ if (response) {
633
+ const assertion = response.Assertion;
634
+ if (!assertion) {
635
+ const status = response.Status;
636
+ if (status) {
637
+ const statusCode = status[0].StatusCode;
638
+ if (statusCode &&
639
+ statusCode[0].$.Value === "urn:oasis:names:tc:SAML:2.0:status:Responder") {
640
+ const nestedStatusCode = statusCode[0].StatusCode;
641
+ if (nestedStatusCode &&
642
+ nestedStatusCode[0].$.Value === "urn:oasis:names:tc:SAML:2.0:status:NoPassive") {
643
+ if (!validSignature) {
644
+ throw new Error("Invalid signature: NoPassive");
645
+ }
646
+ return { profile: null, loggedOut: false };
647
+ }
648
+ }
649
+ // Note that we're not requiring a valid signature before this logic -- since we are
650
+ // throwing an error in any case, and some providers don't sign error results,
651
+ // let's go ahead and give the potentially more helpful error.
652
+ if (statusCode && statusCode[0].$.Value) {
653
+ const msgType = statusCode[0].$.Value.match(/[^:]*$/)[0];
654
+ if (msgType != "Success") {
655
+ let msg = "unspecified";
656
+ if (status[0].StatusMessage) {
657
+ msg = status[0].StatusMessage[0]._;
658
+ }
659
+ else if (statusCode[0].StatusCode) {
660
+ msg = statusCode[0].StatusCode[0].$.Value.match(/[^:]*$/)[0];
661
+ }
662
+ const statusXml = (0, xml_1.buildXml2JsObject)("Status", status[0]);
663
+ throw new types_2.ErrorWithXmlStatus("SAML provider returned " + msgType + " error: " + msg, statusXml);
664
+ }
665
+ }
666
+ }
667
+ }
668
+ throw new Error("Missing SAML assertion");
669
+ }
670
+ else {
671
+ if (!validSignature) {
672
+ throw new Error("Invalid signature: No response found");
673
+ }
674
+ const logoutResponse = xmljsDoc.LogoutResponse;
675
+ if (logoutResponse) {
676
+ return { profile: null, loggedOut: true };
677
+ }
678
+ else {
679
+ throw new Error("Unknown SAML response message");
680
+ }
681
+ }
682
+ }
683
+ catch (err) {
684
+ debug("validatePostResponse resulted in an error: %s", err);
685
+ if (this.options.validateInResponseTo) {
686
+ await this.cacheProvider.removeAsync(inResponseTo);
687
+ }
688
+ throw err;
689
+ }
690
+ }
691
+ async validateInResponseTo(inResponseTo) {
692
+ if (this.options.validateInResponseTo) {
693
+ if (inResponseTo) {
694
+ const result = await this.cacheProvider.getAsync(inResponseTo);
695
+ if (!result)
696
+ throw new Error("InResponseTo is not valid");
697
+ return;
698
+ }
699
+ else {
700
+ throw new Error("InResponseTo is missing from response");
701
+ }
702
+ }
703
+ else {
704
+ return;
705
+ }
706
+ }
707
+ async validateRedirectAsync(container, originalQuery, validateAssertion) {
708
+ const samlMessageType = container.SAMLRequest ? "SAMLRequest" : "SAMLResponse";
709
+ const data = Buffer.from(container[samlMessageType], "base64");
710
+ const inflated = await inflateRawAsync(data);
711
+ const dom = (0, xml_1.parseDomFromString)(inflated.toString());
712
+ const doc = await (0, xml_1.parseXml2JsFromString)(inflated);
713
+ samlMessageType === "SAMLResponse"
714
+ ? await this.verifyLogoutResponse(doc)
715
+ : this.verifyLogoutRequest(doc);
716
+ await this.hasValidSignatureForRedirect(container, originalQuery);
717
+ return await processValidlySignedSamlLogoutAsync(this, doc, dom);
718
+ }
719
+ async hasValidSignatureForRedirect(container, originalQuery, validateAssertion) {
720
+ const tokens = originalQuery.split("&");
721
+ const getParam = (key) => {
722
+ const exists = tokens.filter((t) => {
723
+ return new RegExp(key).test(t);
724
+ });
725
+ return exists[0];
726
+ };
727
+ if (container.Signature) {
728
+
729
+ // CUSTOM
730
+ if (validateAssertion === false)
731
+ {
732
+ return true;
733
+ }
734
+
735
+ let urlString = getParam("SAMLRequest") || getParam("SAMLResponse");
736
+ if (getParam("RelayState")) {
737
+ urlString += "&" + getParam("RelayState");
738
+ }
739
+ urlString += "&" + getParam("SigAlg");
740
+ const certs = await this.certsToCheck();
741
+ const hasValidQuerySignature = certs.some((cert) => {
742
+ return this.validateSignatureForRedirect(urlString, container.Signature, container.SigAlg, cert);
743
+ });
744
+ if (!hasValidQuerySignature) {
745
+ throw new Error("Invalid query signature");
746
+ }
747
+ }
748
+ else {
749
+ return true;
750
+ }
751
+ }
752
+ validateSignatureForRedirect(urlString, signature, alg, cert) {
753
+ // See if we support a matching algorithm, case-insensitive. Otherwise, throw error.
754
+ function hasMatch(ourAlgo) {
755
+ // The incoming algorithm is forwarded as a URL.
756
+ // We trim everything before the last # get something we can compare to the Node.js list
757
+ const algFromURI = alg.toLowerCase().replace(/.*#(.*)$/, "$1");
758
+ return ourAlgo.toLowerCase() === algFromURI;
759
+ }
760
+ const i = crypto.getHashes().findIndex(hasMatch);
761
+ let matchingAlgo;
762
+ if (i > -1) {
763
+ matchingAlgo = crypto.getHashes()[i];
764
+ }
765
+ else {
766
+ throw new Error(alg + " is not supported");
767
+ }
768
+ const verifier = crypto.createVerify(matchingAlgo);
769
+ verifier.update(urlString);
770
+ return verifier.verify(this._certToPEM(cert), signature, "base64");
771
+ }
772
+ verifyLogoutRequest(doc) {
773
+ this.verifyIssuer(doc.LogoutRequest);
774
+ const nowMs = new Date().getTime();
775
+ const conditions = doc.LogoutRequest.$;
776
+ const conErr = this.checkTimestampsValidityError(nowMs, conditions.NotBefore, conditions.NotOnOrAfter);
777
+ if (conErr) {
778
+ throw conErr;
779
+ }
780
+ }
781
+ async verifyLogoutResponse(doc) {
782
+ const statusCode = doc.LogoutResponse.Status[0].StatusCode[0].$.Value;
783
+ if (statusCode !== "urn:oasis:names:tc:SAML:2.0:status:Success")
784
+ throw new Error("Bad status code: " + statusCode);
785
+ this.verifyIssuer(doc.LogoutResponse);
786
+ const inResponseTo = doc.LogoutResponse.$.InResponseTo;
787
+ if (inResponseTo) {
788
+ return this.validateInResponseTo(inResponseTo);
789
+ }
790
+ return true;
791
+ }
792
+ verifyIssuer(samlMessage) {
793
+ if (this.options.idpIssuer != null) {
794
+ const issuer = samlMessage.Issuer;
795
+ if (issuer) {
796
+ if (issuer[0]._ !== this.options.idpIssuer)
797
+ throw new Error("Unknown SAML issuer. Expected: " + this.options.idpIssuer + " Received: " + issuer[0]._);
798
+ }
799
+ else {
800
+ throw new Error("Missing SAML issuer");
801
+ }
802
+ }
803
+ }
804
+ async processValidlySignedAssertionAsync(xml, samlResponseXml, inResponseTo) {
805
+ let msg;
806
+ const nowMs = new Date().getTime();
807
+ const profile = {};
808
+ const doc = await (0, xml_1.parseXml2JsFromString)(xml);
809
+ const parsedAssertion = doc;
810
+ const assertion = doc.Assertion;
811
+ getInResponseTo: {
812
+ const issuer = assertion.Issuer;
813
+ if (issuer && issuer[0]._) {
814
+ profile.issuer = issuer[0]._;
815
+ }
816
+ if (inResponseTo) {
817
+ profile.inResponseTo = inResponseTo;
818
+ }
819
+ const authnStatement = assertion.AuthnStatement;
820
+ if (authnStatement) {
821
+ if (authnStatement[0].$ && authnStatement[0].$.SessionIndex) {
822
+ profile.sessionIndex = authnStatement[0].$.SessionIndex;
823
+ }
824
+ }
825
+ const subject = assertion.Subject;
826
+ let subjectConfirmation, confirmData;
827
+ if (subject) {
828
+ const nameID = subject[0].NameID;
829
+ if (nameID && nameID[0]._) {
830
+ profile.nameID = nameID[0]._;
831
+ if (nameID[0].$ && nameID[0].$.Format) {
832
+ profile.nameIDFormat = nameID[0].$.Format;
833
+ profile.nameQualifier = nameID[0].$.NameQualifier;
834
+ profile.spNameQualifier = nameID[0].$.SPNameQualifier;
835
+ }
836
+ }
837
+ subjectConfirmation = subject[0].SubjectConfirmation
838
+ ? subject[0].SubjectConfirmation[0]
839
+ : null;
840
+ confirmData =
841
+ subjectConfirmation && subjectConfirmation.SubjectConfirmationData
842
+ ? subjectConfirmation.SubjectConfirmationData[0]
843
+ : null;
844
+ if (subject[0].SubjectConfirmation && subject[0].SubjectConfirmation.length > 1) {
845
+ msg = "Unable to process multiple SubjectConfirmations in SAML assertion";
846
+ throw new Error(msg);
847
+ }
848
+ if (subjectConfirmation) {
849
+ if (confirmData && confirmData.$) {
850
+ const subjectNotBefore = confirmData.$.NotBefore;
851
+ const subjectNotOnOrAfter = confirmData.$.NotOnOrAfter;
852
+ const maxTimeLimitMs = this.processMaxAgeAssertionTime(this.options.maxAssertionAgeMs, subjectNotOnOrAfter, assertion.$.IssueInstant);
853
+ const subjErr = this.checkTimestampsValidityError(nowMs, subjectNotBefore, subjectNotOnOrAfter, maxTimeLimitMs);
854
+ if (subjErr) {
855
+ throw subjErr;
856
+ }
857
+ }
858
+ }
859
+ }
860
+ // Test to see that if we have a SubjectConfirmation InResponseTo that it matches
861
+ // the 'InResponseTo' attribute set in the Response
862
+ if (this.options.validateInResponseTo) {
863
+ if (subjectConfirmation) {
864
+ if (confirmData && confirmData.$) {
865
+ const subjectInResponseTo = confirmData.$.InResponseTo;
866
+ if (inResponseTo && subjectInResponseTo && subjectInResponseTo != inResponseTo) {
867
+ await this.cacheProvider.removeAsync(inResponseTo);
868
+ throw new Error("InResponseTo is not valid");
869
+ }
870
+ else if (subjectInResponseTo) {
871
+ let foundValidInResponseTo = false;
872
+ const result = await this.cacheProvider.getAsync(subjectInResponseTo);
873
+ if (result) {
874
+ const createdAt = new Date(result);
875
+ if (nowMs < createdAt.getTime() + this.options.requestIdExpirationPeriodMs)
876
+ foundValidInResponseTo = true;
877
+ }
878
+ await this.cacheProvider.removeAsync(inResponseTo);
879
+ if (!foundValidInResponseTo) {
880
+ throw new Error("InResponseTo is not valid");
881
+ }
882
+ break getInResponseTo;
883
+ }
884
+ }
885
+ }
886
+ else {
887
+ await this.cacheProvider.removeAsync(inResponseTo);
888
+ break getInResponseTo;
889
+ }
890
+ }
891
+ else {
892
+ break getInResponseTo;
893
+ }
894
+ }
895
+ const conditions = assertion.Conditions ? assertion.Conditions[0] : null;
896
+ if (assertion.Conditions && assertion.Conditions.length > 1) {
897
+ msg = "Unable to process multiple conditions in SAML assertion";
898
+ throw new Error(msg);
899
+ }
900
+ if (conditions && conditions.$) {
901
+ const maxTimeLimitMs = this.processMaxAgeAssertionTime(this.options.maxAssertionAgeMs, conditions.$.NotOnOrAfter, assertion.$.IssueInstant);
902
+ const conErr = this.checkTimestampsValidityError(nowMs, conditions.$.NotBefore, conditions.$.NotOnOrAfter, maxTimeLimitMs);
903
+ if (conErr)
904
+ throw conErr;
905
+ }
906
+ if (this.options.audience != null) {
907
+ const audienceErr = this.checkAudienceValidityError(this.options.audience, conditions.AudienceRestriction);
908
+ if (audienceErr)
909
+ throw audienceErr;
910
+ }
911
+ const attributeStatement = assertion.AttributeStatement;
912
+ if (attributeStatement) {
913
+ const attributes = [].concat(...attributeStatement
914
+ .filter((attr) => Array.isArray(attr.Attribute))
915
+ .map((attr) => attr.Attribute));
916
+ const attrValueMapper = (value) => {
917
+ const hasChildren = Object.keys(value).some((cur) => {
918
+ return cur !== "_" && cur !== "$";
919
+ });
920
+ return hasChildren ? value : value._;
921
+ };
922
+ if (attributes) {
923
+ const profileAttributes = {};
924
+ attributes.forEach((attribute) => {
925
+ if (!Object.prototype.hasOwnProperty.call(attribute, "AttributeValue")) {
926
+ // if attributes has no AttributeValue child, continue
927
+ return;
928
+ }
929
+ const name = attribute.$.Name;
930
+ const value = attribute.AttributeValue.length === 1
931
+ ? attrValueMapper(attribute.AttributeValue[0])
932
+ : attribute.AttributeValue.map(attrValueMapper);
933
+ profileAttributes[name] = value;
934
+ // If any property is already present in profile and is also present
935
+ // in attributes, then skip the one from attributes. Handle this
936
+ // conflict gracefully without returning any error
937
+ if (Object.prototype.hasOwnProperty.call(profile, name)) {
938
+ return;
939
+ }
940
+ profile[name] = value;
941
+ });
942
+ profile.attributes = profileAttributes;
943
+ }
944
+ }
945
+ if (!profile.mail && profile["urn:oid:0.9.2342.19200300.100.1.3"]) {
946
+ // See https://spaces.internet2.edu/display/InCFederation/Supported+Attribute+Summary
947
+ // for definition of attribute OIDs
948
+ profile.mail = profile["urn:oid:0.9.2342.19200300.100.1.3"];
949
+ }
950
+ if (!profile.email && profile.mail) {
951
+ profile.email = profile.mail;
952
+ }
953
+ profile.getAssertionXml = () => xml.toString();
954
+ profile.getAssertion = () => parsedAssertion;
955
+ profile.getSamlResponseXml = () => samlResponseXml;
956
+ return { profile, loggedOut: false };
957
+ }
958
+ checkTimestampsValidityError(nowMs, notBefore, notOnOrAfter, maxTimeLimitMs) {
959
+ if (this.options.acceptedClockSkewMs == -1)
960
+ return null;
961
+ if (notBefore) {
962
+ const notBeforeMs = this.dateStringToTimestamp(notBefore, "NotBefore");
963
+ if (nowMs + this.options.acceptedClockSkewMs < notBeforeMs)
964
+ return new Error("SAML assertion not yet valid");
965
+ }
966
+ if (notOnOrAfter) {
967
+ const notOnOrAfterMs = this.dateStringToTimestamp(notOnOrAfter, "NotOnOrAfter");
968
+ if (nowMs - this.options.acceptedClockSkewMs >= notOnOrAfterMs)
969
+ return new Error("SAML assertion expired: clocks skewed too much");
970
+ }
971
+ if (maxTimeLimitMs) {
972
+ if (nowMs - this.options.acceptedClockSkewMs >= maxTimeLimitMs)
973
+ return new Error("SAML assertion expired: assertion too old");
974
+ }
975
+ return null;
976
+ }
977
+ checkAudienceValidityError(expectedAudience, audienceRestrictions) {
978
+ if (!audienceRestrictions || audienceRestrictions.length < 1) {
979
+ return new Error("SAML assertion has no AudienceRestriction");
980
+ }
981
+ const errors = audienceRestrictions
982
+ .map((restriction) => {
983
+ if (!restriction.Audience || !restriction.Audience[0] || !restriction.Audience[0]._) {
984
+ return new Error("SAML assertion AudienceRestriction has no Audience value");
985
+ }
986
+ if (restriction.Audience[0]._ !== expectedAudience) {
987
+ return new Error("SAML assertion audience mismatch");
988
+ }
989
+ return null;
990
+ })
991
+ .filter((result) => {
992
+ return result !== null;
993
+ });
994
+ if (errors.length > 0) {
995
+ return errors[0];
996
+ }
997
+ return null;
998
+ }
999
+ async validatePostRequestAsync(container) {
1000
+ const xml = Buffer.from(container.SAMLRequest, "base64").toString("utf8");
1001
+ const dom = (0, xml_1.parseDomFromString)(xml);
1002
+ const doc = await (0, xml_1.parseXml2JsFromString)(xml);
1003
+ const certs = await this.certsToCheck();
1004
+ if (!this.validateSignature(xml, dom.documentElement, certs)) {
1005
+ throw new Error("Invalid signature on documentElement");
1006
+ }
1007
+ return await processValidlySignedPostRequestAsync(this, doc, dom);
1008
+ }
1009
+ async _getNameIdAsync(self, doc) {
1010
+ const nameIds = xml_1.xpath.selectElements(doc, "/*[local-name()='LogoutRequest']/*[local-name()='NameID']");
1011
+ const encryptedIds = xml_1.xpath.selectElements(doc, "/*[local-name()='LogoutRequest']/*[local-name()='EncryptedID']");
1012
+ if (nameIds.length + encryptedIds.length > 1) {
1013
+ throw new Error("Invalid LogoutRequest");
1014
+ }
1015
+ if (nameIds.length === 1) {
1016
+ return promiseWithNameID(nameIds[0]);
1017
+ }
1018
+ if (encryptedIds.length === 1) {
1019
+ self.options.decryptionPvk = (0, utility_1.assertRequired)(self.options.decryptionPvk, "No decryption key found getting name ID for encrypted SAML response");
1020
+ const encryptedDatas = xml_1.xpath.selectElements(encryptedIds[0], "./*[local-name()='EncryptedData']");
1021
+ if (encryptedDatas.length !== 1) {
1022
+ throw new Error("Invalid LogoutRequest");
1023
+ }
1024
+ const encryptedDataXml = encryptedDatas[0].toString();
1025
+ const decryptedXml = await (0, xml_1.decryptXml)(encryptedDataXml, self.options.decryptionPvk);
1026
+ const decryptedDoc = (0, xml_1.parseDomFromString)(decryptedXml);
1027
+ const decryptedIds = xml_1.xpath.selectElements(decryptedDoc, "/*[local-name()='NameID']");
1028
+ if (decryptedIds.length !== 1) {
1029
+ throw new Error("Invalid EncryptedAssertion content");
1030
+ }
1031
+ return await promiseWithNameID(decryptedIds[0]);
1032
+ }
1033
+ throw new Error("Missing SAML NameID");
1034
+ }
1035
+ generateServiceProviderMetadata(decryptionCert, signingCert) {
1036
+ const metadata = {
1037
+ EntityDescriptor: {
1038
+ "@xmlns": "urn:oasis:names:tc:SAML:2.0:metadata",
1039
+ "@xmlns:ds": "http://www.w3.org/2000/09/xmldsig#",
1040
+ "@entityID": this.options.issuer,
1041
+ "@ID": this.options.issuer.replace(/\W/g, "_"),
1042
+ SPSSODescriptor: {
1043
+ "@protocolSupportEnumeration": "urn:oasis:names:tc:SAML:2.0:protocol",
1044
+ },
1045
+ },
1046
+ };
1047
+ if (this.options.decryptionPvk != null) {
1048
+ if (!decryptionCert) {
1049
+ throw new Error("Missing decryptionCert while generating metadata for decrypting service provider");
1050
+ }
1051
+ }
1052
+ if (this.options.privateKey != null) {
1053
+ if (!signingCert) {
1054
+ throw new Error("Missing signingCert while generating metadata for signing service provider messages");
1055
+ }
1056
+ }
1057
+ if (this.options.decryptionPvk != null || this.options.privateKey != null) {
1058
+ metadata.EntityDescriptor.SPSSODescriptor.KeyDescriptor = [];
1059
+ if (this.options.privateKey != null) {
1060
+ signingCert = signingCert.replace(/-+BEGIN CERTIFICATE-+\r?\n?/, "");
1061
+ signingCert = signingCert.replace(/-+END CERTIFICATE-+\r?\n?/, "");
1062
+ signingCert = signingCert.replace(/\r\n/g, "\n");
1063
+ metadata.EntityDescriptor.SPSSODescriptor.KeyDescriptor.push({
1064
+ "@use": "signing",
1065
+ "ds:KeyInfo": {
1066
+ "ds:X509Data": {
1067
+ "ds:X509Certificate": {
1068
+ "#text": signingCert,
1069
+ },
1070
+ },
1071
+ },
1072
+ });
1073
+ }
1074
+ if (this.options.decryptionPvk != null) {
1075
+ decryptionCert = decryptionCert.replace(/-+BEGIN CERTIFICATE-+\r?\n?/, "");
1076
+ decryptionCert = decryptionCert.replace(/-+END CERTIFICATE-+\r?\n?/, "");
1077
+ decryptionCert = decryptionCert.replace(/\r\n/g, "\n");
1078
+ metadata.EntityDescriptor.SPSSODescriptor.KeyDescriptor.push({
1079
+ "@use": "encryption",
1080
+ "ds:KeyInfo": {
1081
+ "ds:X509Data": {
1082
+ "ds:X509Certificate": {
1083
+ "#text": decryptionCert,
1084
+ },
1085
+ },
1086
+ },
1087
+ EncryptionMethod: [
1088
+ // this should be the set that the xmlenc library supports
1089
+ { "@Algorithm": "http://www.w3.org/2009/xmlenc11#aes256-gcm" },
1090
+ { "@Algorithm": "http://www.w3.org/2009/xmlenc11#aes128-gcm" },
1091
+ { "@Algorithm": "http://www.w3.org/2001/04/xmlenc#aes256-cbc" },
1092
+ { "@Algorithm": "http://www.w3.org/2001/04/xmlenc#aes128-cbc" },
1093
+ ],
1094
+ });
1095
+ }
1096
+ }
1097
+ if (this.options.logoutCallbackUrl != null) {
1098
+ metadata.EntityDescriptor.SPSSODescriptor.SingleLogoutService = {
1099
+ "@Binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
1100
+ "@Location": this.options.logoutCallbackUrl,
1101
+ };
1102
+ }
1103
+ if (this.options.identifierFormat != null) {
1104
+ metadata.EntityDescriptor.SPSSODescriptor.NameIDFormat = this.options.identifierFormat;
1105
+ }
1106
+ if (this.options.wantAssertionsSigned) {
1107
+ metadata.EntityDescriptor.SPSSODescriptor["@WantAssertionsSigned"] = true;
1108
+ }
1109
+ metadata.EntityDescriptor.SPSSODescriptor.AssertionConsumerService = {
1110
+ "@index": "1",
1111
+ "@isDefault": "true",
1112
+ "@Binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
1113
+ "@Location": this.getCallbackUrl(),
1114
+ };
1115
+ return (0, xml_1.buildXmlBuilderObject)(metadata, true);
1116
+ }
1117
+ _keyToPEM(key) {
1118
+ key = (0, utility_1.assertRequired)(key, "key is required");
1119
+ if (typeof key !== "string")
1120
+ return key;
1121
+ if (key.split(/\r?\n/).length !== 1)
1122
+ return key;
1123
+ const matchedKey = key.match(/.{1,64}/g);
1124
+ if (matchedKey) {
1125
+ const wrappedKey = [
1126
+ "-----BEGIN PRIVATE KEY-----",
1127
+ ...matchedKey,
1128
+ "-----END PRIVATE KEY-----",
1129
+ "",
1130
+ ].join("\n");
1131
+ return wrappedKey;
1132
+ }
1133
+ throw new Error("Invalid key");
1134
+ }
1135
+ /**
1136
+ * Process max age assertion and use it if it is more restrictive than the NotOnOrAfter age
1137
+ * assertion received in the SAMLResponse.
1138
+ *
1139
+ * @param maxAssertionAgeMs Max time after IssueInstant that we will accept assertion, in Ms.
1140
+ * @param notOnOrAfter Expiration provided in response.
1141
+ * @param issueInstant Time when response was issued.
1142
+ * @returns {*} The expiration time to be used, in Ms.
1143
+ */
1144
+ processMaxAgeAssertionTime(maxAssertionAgeMs, notOnOrAfter, issueInstant) {
1145
+ const notOnOrAfterMs = this.dateStringToTimestamp(notOnOrAfter, "NotOnOrAfter");
1146
+ const issueInstantMs = this.dateStringToTimestamp(issueInstant, "IssueInstant");
1147
+ if (maxAssertionAgeMs === 0) {
1148
+ return notOnOrAfterMs;
1149
+ }
1150
+ const maxAssertionTimeMs = issueInstantMs + maxAssertionAgeMs;
1151
+ return maxAssertionTimeMs < notOnOrAfterMs ? maxAssertionTimeMs : notOnOrAfterMs;
1152
+ }
1153
+ /**
1154
+ * Convert a date string to a timestamp (in milliseconds).
1155
+ *
1156
+ * @param dateString A string representation of a date
1157
+ * @param label Descriptive name of the date being passed in, e.g. "NotOnOrAfter"
1158
+ * @throws Will throw an error if parsing `dateString` returns `NaN`
1159
+ * @returns {number} The timestamp (in milliseconds) representation of the given date
1160
+ */
1161
+ dateStringToTimestamp(dateString, label) {
1162
+ const dateMs = Date.parse(dateString);
1163
+ if (isNaN(dateMs)) {
1164
+ throw new Error(`Error parsing ${label}: '${dateString}' is not a valid date`);
1165
+ }
1166
+ return dateMs;
1167
+ }
1168
+ }
1169
+ exports.SAML = SAML;
1170
+ //# sourceMappingURL=saml.js.map