@boxyhq/saml-jackson 1.0.1 → 1.0.2
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/dist/controller/api.js +3 -3
- package/dist/controller/{signout.d.ts → logout.d.ts} +0 -0
- package/dist/controller/{signout.js → logout.js} +14 -46
- package/dist/controller/oauth/redirect.d.ts +1 -1
- package/dist/controller/oauth/redirect.js +6 -1
- package/dist/controller/oauth.d.ts +5 -3
- package/dist/controller/oauth.js +144 -33
- package/dist/controller/utils.d.ts +1 -1
- package/dist/controller/utils.js +2 -25
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -3
- package/dist/typings.d.ts +7 -20
- package/package.json +15 -18
- package/dist/saml/saml.d.ts +0 -15
- package/dist/saml/saml.js +0 -240
package/dist/controller/api.js
CHANGED
@@ -50,7 +50,7 @@ exports.APIController = void 0;
|
|
50
50
|
const crypto_1 = __importDefault(require("crypto"));
|
51
51
|
const dbutils = __importStar(require("../db/utils"));
|
52
52
|
const metrics = __importStar(require("../opentelemetry/metrics"));
|
53
|
-
const
|
53
|
+
const saml20_1 = __importDefault(require("@boxyhq/saml20"));
|
54
54
|
const x509_1 = __importDefault(require("../saml/x509"));
|
55
55
|
const error_1 = require("./error");
|
56
56
|
const utils_1 = require("./utils");
|
@@ -187,7 +187,7 @@ class APIController {
|
|
187
187
|
if (encodedRawMetadata) {
|
188
188
|
metaData = Buffer.from(encodedRawMetadata, 'base64').toString();
|
189
189
|
}
|
190
|
-
const idpMetadata = yield
|
190
|
+
const idpMetadata = yield saml20_1.default.parseMetadataAsync(metaData);
|
191
191
|
// extract provider
|
192
192
|
let providerName = extractHostName(idpMetadata.entityID);
|
193
193
|
if (!providerName) {
|
@@ -323,7 +323,7 @@ class APIController {
|
|
323
323
|
}
|
324
324
|
let newMetadata;
|
325
325
|
if (metaData) {
|
326
|
-
newMetadata = yield
|
326
|
+
newMetadata = yield saml20_1.default.parseMetadataAsync(metaData);
|
327
327
|
// extract provider
|
328
328
|
let providerName = extractHostName(newMetadata.entityID);
|
329
329
|
if (!providerName) {
|
File without changes
|
@@ -36,21 +36,19 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
36
|
};
|
37
37
|
Object.defineProperty(exports, "__esModule", { value: true });
|
38
38
|
exports.LogoutController = void 0;
|
39
|
-
const xmldom_1 = require("@xmldom/xmldom");
|
40
39
|
const crypto_1 = __importDefault(require("crypto"));
|
41
|
-
const thumbprint_1 = __importDefault(require("thumbprint"));
|
42
40
|
const util_1 = require("util");
|
43
|
-
const xml_crypto_1 = require("xml-crypto");
|
44
41
|
const xml2js_1 = __importDefault(require("xml2js"));
|
45
42
|
const xmlbuilder_1 = __importDefault(require("xmlbuilder"));
|
46
43
|
const zlib_1 = require("zlib");
|
47
44
|
const dbutils = __importStar(require("../db/utils"));
|
48
|
-
const
|
45
|
+
const saml20_1 = __importDefault(require("@boxyhq/saml20"));
|
49
46
|
const error_1 = require("./error");
|
50
47
|
const redirect = __importStar(require("./oauth/redirect"));
|
51
48
|
const utils_1 = require("./utils");
|
52
49
|
const deflateRawAsync = (0, util_1.promisify)(zlib_1.deflateRaw);
|
53
50
|
const relayStatePrefix = 'boxyhq_jackson_';
|
51
|
+
const logoutXPath = "/*[local-name(.)='LogoutRequest']";
|
54
52
|
class LogoutController {
|
55
53
|
constructor({ configStore, sessionStore, opts }) {
|
56
54
|
this.opts = opts;
|
@@ -97,7 +95,16 @@ class LogoutController {
|
|
97
95
|
}
|
98
96
|
// HTTP-POST binding
|
99
97
|
if ('postUrl' in slo) {
|
100
|
-
logoutForm =
|
98
|
+
logoutForm = saml20_1.default.createPostForm(slo.postUrl, [
|
99
|
+
{
|
100
|
+
name: 'RelayState',
|
101
|
+
value: relayState,
|
102
|
+
},
|
103
|
+
{
|
104
|
+
name: 'SAMLRequest',
|
105
|
+
value: Buffer.from(signedXML).toString('base64'),
|
106
|
+
},
|
107
|
+
]);
|
101
108
|
}
|
102
109
|
return { logoutUrl, logoutForm };
|
103
110
|
});
|
@@ -127,7 +134,7 @@ class LogoutController {
|
|
127
134
|
throw new error_1.JacksonError('SAML configuration not found.', 403);
|
128
135
|
}
|
129
136
|
const { idpMetadata, defaultRedirectUrl } = samlConfigs[0];
|
130
|
-
if (!(yield
|
137
|
+
if (!(yield saml20_1.default.validateSignature(rawResponse, null, idpMetadata.thumbprint))) {
|
131
138
|
throw new error_1.JacksonError('Invalid signature.', 403);
|
132
139
|
}
|
133
140
|
try {
|
@@ -188,44 +195,5 @@ const parseSAMLResponse = (rawResponse) => __awaiter(void 0, void 0, void 0, fun
|
|
188
195
|
});
|
189
196
|
// Sign the XML
|
190
197
|
const signXML = (xml, signingKey, publicKey) => __awaiter(void 0, void 0, void 0, function* () {
|
191
|
-
|
192
|
-
sig.signatureAlgorithm = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256';
|
193
|
-
sig.keyInfoProvider = new saml_1.default.PubKeyInfo(publicKey);
|
194
|
-
sig.signingKey = signingKey;
|
195
|
-
sig.addReference("/*[local-name(.)='LogoutRequest']", ['http://www.w3.org/2000/09/xmldsig#enveloped-signature', 'http://www.w3.org/2001/10/xml-exc-c14n#'], 'http://www.w3.org/2001/04/xmlenc#sha256');
|
196
|
-
sig.computeSignature(xml);
|
197
|
-
return sig.getSignedXml();
|
198
|
-
});
|
199
|
-
// Validate signature
|
200
|
-
const hasValidSignature = (xml, certThumbprint) => __awaiter(void 0, void 0, void 0, function* () {
|
201
|
-
return new Promise((resolve, reject) => {
|
202
|
-
const doc = new xmldom_1.DOMParser().parseFromString(xml);
|
203
|
-
const signed = new xml_crypto_1.SignedXml();
|
204
|
-
let calculatedThumbprint;
|
205
|
-
const signature = (0, xml_crypto_1.xpath)(doc, "/*/*/*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']")[0] ||
|
206
|
-
(0, xml_crypto_1.xpath)(doc, "/*/*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']")[0] ||
|
207
|
-
(0, xml_crypto_1.xpath)(doc, "/*/*/*/*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']")[0];
|
208
|
-
signed.keyInfoProvider = {
|
209
|
-
getKey: function getKey(keyInfo) {
|
210
|
-
if (certThumbprint) {
|
211
|
-
const embeddedSignature = keyInfo[0].getElementsByTagNameNS('http://www.w3.org/2000/09/xmldsig#', 'X509Certificate');
|
212
|
-
if (embeddedSignature.length > 0) {
|
213
|
-
const base64cer = embeddedSignature[0].firstChild.toString();
|
214
|
-
calculatedThumbprint = thumbprint_1.default.calculate(base64cer);
|
215
|
-
return saml_1.default.certToPEM(base64cer);
|
216
|
-
}
|
217
|
-
}
|
218
|
-
},
|
219
|
-
getKeyInfo: function getKeyInfo() {
|
220
|
-
return '<X509Data></X509Data>';
|
221
|
-
},
|
222
|
-
};
|
223
|
-
signed.loadSignature(signature.toString());
|
224
|
-
try {
|
225
|
-
return resolve(signed.checkSignature(xml) && calculatedThumbprint.toUpperCase() === certThumbprint.toUpperCase());
|
226
|
-
}
|
227
|
-
catch (err) {
|
228
|
-
return reject(err);
|
229
|
-
}
|
230
|
-
});
|
198
|
+
return yield saml20_1.default.sign(xml, signingKey, publicKey, logoutXPath);
|
231
199
|
});
|
@@ -1 +1 @@
|
|
1
|
-
export declare const success: (redirectUrl: string, params: Record<string, string>) => string;
|
1
|
+
export declare const success: (redirectUrl: string, params: Record<string, string | string[] | undefined>) => string;
|
@@ -4,7 +4,12 @@ exports.success = void 0;
|
|
4
4
|
const success = (redirectUrl, params) => {
|
5
5
|
const url = new URL(redirectUrl);
|
6
6
|
for (const [key, value] of Object.entries(params)) {
|
7
|
-
|
7
|
+
if (Array.isArray(value)) {
|
8
|
+
value.forEach((v) => url.searchParams.append(key, v));
|
9
|
+
}
|
10
|
+
else if (value !== undefined) {
|
11
|
+
url.searchParams.set(key, value);
|
12
|
+
}
|
8
13
|
}
|
9
14
|
return url.href;
|
10
15
|
};
|
@@ -12,12 +12,14 @@ export declare class OAuthController implements IOAuthController {
|
|
12
12
|
tokenStore: any;
|
13
13
|
opts: any;
|
14
14
|
});
|
15
|
+
private resolveMultipleConfigMatches;
|
15
16
|
authorize(body: OAuthReqBody): Promise<{
|
16
|
-
redirect_url
|
17
|
-
authorize_form
|
17
|
+
redirect_url?: string;
|
18
|
+
authorize_form?: string;
|
18
19
|
}>;
|
19
20
|
samlResponse(body: SAMLResponsePayload): Promise<{
|
20
|
-
redirect_url
|
21
|
+
redirect_url?: string;
|
22
|
+
app_select_form?: string;
|
21
23
|
}>;
|
22
24
|
/**
|
23
25
|
* @swagger
|
package/dist/controller/oauth.js
CHANGED
@@ -41,14 +41,26 @@ const util_1 = require("util");
|
|
41
41
|
const zlib_1 = require("zlib");
|
42
42
|
const dbutils = __importStar(require("../db/utils"));
|
43
43
|
const metrics = __importStar(require("../opentelemetry/metrics"));
|
44
|
-
const
|
44
|
+
const saml20_1 = __importDefault(require("@boxyhq/saml20"));
|
45
|
+
const claims_1 = __importDefault(require("../saml/claims"));
|
45
46
|
const error_1 = require("./error");
|
46
47
|
const allowed = __importStar(require("./oauth/allowed"));
|
47
48
|
const codeVerifier = __importStar(require("./oauth/code-verifier"));
|
48
49
|
const redirect = __importStar(require("./oauth/redirect"));
|
49
50
|
const utils_1 = require("./utils");
|
50
51
|
const deflateRawAsync = (0, util_1.promisify)(zlib_1.deflateRaw);
|
51
|
-
const
|
52
|
+
const validateResponse = (rawResponse, validateOpts) => __awaiter(void 0, void 0, void 0, function* () {
|
53
|
+
const profile = yield saml20_1.default.validateAsync(rawResponse, validateOpts);
|
54
|
+
if (profile && profile.claims) {
|
55
|
+
// we map claims to our attributes id, email, firstName, lastName where possible. We also map original claims to raw
|
56
|
+
profile.claims = claims_1.default.map(profile.claims);
|
57
|
+
// some providers don't return the id in the assertion, we set it to a sha256 hash of the email
|
58
|
+
if (!profile.claims.id && profile.claims.email) {
|
59
|
+
profile.claims.id = crypto_1.default.createHash('sha256').update(profile.claims.email).digest('hex');
|
60
|
+
}
|
61
|
+
}
|
62
|
+
return profile;
|
63
|
+
});
|
52
64
|
function getEncodedClientId(client_id) {
|
53
65
|
try {
|
54
66
|
const sp = new URLSearchParams(client_id);
|
@@ -74,11 +86,51 @@ class OAuthController {
|
|
74
86
|
this.tokenStore = tokenStore;
|
75
87
|
this.opts = opts;
|
76
88
|
}
|
89
|
+
resolveMultipleConfigMatches(samlConfigs, idp_hint, originalParams, isIdpFlow = false) {
|
90
|
+
if (samlConfigs.length > 1) {
|
91
|
+
if (idp_hint) {
|
92
|
+
return { resolvedSamlConfig: samlConfigs.find(({ clientID }) => clientID === idp_hint) };
|
93
|
+
}
|
94
|
+
else if (this.opts.idpDiscoveryPath) {
|
95
|
+
if (!isIdpFlow) {
|
96
|
+
// redirect to IdP selection page
|
97
|
+
const idpList = samlConfigs.map(({ idpMetadata: { provider }, clientID }) => JSON.stringify({
|
98
|
+
provider,
|
99
|
+
clientID,
|
100
|
+
}));
|
101
|
+
return {
|
102
|
+
redirect_url: redirect.success(this.opts.externalUrl + this.opts.idpDiscoveryPath, Object.assign(Object.assign({}, originalParams), { idp: idpList })),
|
103
|
+
};
|
104
|
+
}
|
105
|
+
else {
|
106
|
+
const appList = samlConfigs.map(({ product, name, description, clientID }) => ({
|
107
|
+
product,
|
108
|
+
name,
|
109
|
+
description,
|
110
|
+
clientID,
|
111
|
+
}));
|
112
|
+
return {
|
113
|
+
app_select_form: saml20_1.default.createPostForm(this.opts.idpDiscoveryPath, [
|
114
|
+
{
|
115
|
+
name: 'SAMLResponse',
|
116
|
+
value: originalParams.SAMLResponse,
|
117
|
+
},
|
118
|
+
{
|
119
|
+
name: 'app',
|
120
|
+
value: encodeURIComponent(JSON.stringify(appList)),
|
121
|
+
},
|
122
|
+
]),
|
123
|
+
};
|
124
|
+
}
|
125
|
+
}
|
126
|
+
}
|
127
|
+
return {};
|
128
|
+
}
|
77
129
|
authorize(body) {
|
78
130
|
return __awaiter(this, void 0, void 0, function* () {
|
79
131
|
const { response_type = 'code', client_id, redirect_uri, state, tenant, product, code_challenge, code_challenge_method = '',
|
80
132
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
81
|
-
provider = 'saml', } = body;
|
133
|
+
provider = 'saml', idp_hint, } = body;
|
82
134
|
let requestedTenant = tenant;
|
83
135
|
let requestedProduct = product;
|
84
136
|
metrics.increment('oauthAuthorize');
|
@@ -97,24 +149,58 @@ class OAuthController {
|
|
97
149
|
if (!samlConfigs || samlConfigs.length === 0) {
|
98
150
|
throw new error_1.JacksonError('SAML configuration not found.', 403);
|
99
151
|
}
|
100
|
-
// TODO: Support multiple matches
|
101
152
|
samlConfig = samlConfigs[0];
|
153
|
+
// Support multiple matches
|
154
|
+
const { resolvedSamlConfig, redirect_url } = this.resolveMultipleConfigMatches(samlConfigs, idp_hint, {
|
155
|
+
response_type,
|
156
|
+
client_id,
|
157
|
+
redirect_uri,
|
158
|
+
state,
|
159
|
+
tenant,
|
160
|
+
product,
|
161
|
+
code_challenge,
|
162
|
+
code_challenge_method,
|
163
|
+
provider,
|
164
|
+
});
|
165
|
+
if (redirect_url) {
|
166
|
+
return { redirect_url };
|
167
|
+
}
|
168
|
+
if (resolvedSamlConfig) {
|
169
|
+
samlConfig = resolvedSamlConfig;
|
170
|
+
}
|
102
171
|
}
|
103
172
|
else if (client_id && client_id !== '' && client_id !== 'undefined' && client_id !== 'null') {
|
104
173
|
// if tenant and product are encoded in the client_id then we parse it and check for the relevant config(s)
|
105
174
|
const sp = getEncodedClientId(client_id);
|
106
|
-
if (sp
|
175
|
+
if (sp && sp.tenant && sp.product) {
|
107
176
|
requestedTenant = sp.tenant;
|
108
|
-
requestedProduct = sp.product
|
177
|
+
requestedProduct = sp.product;
|
109
178
|
const samlConfigs = yield this.configStore.getByIndex({
|
110
179
|
name: utils_1.IndexNames.TenantProduct,
|
111
|
-
value: dbutils.keyFromParts(sp.tenant, sp.product
|
180
|
+
value: dbutils.keyFromParts(sp.tenant, sp.product),
|
112
181
|
});
|
113
182
|
if (!samlConfigs || samlConfigs.length === 0) {
|
114
183
|
throw new error_1.JacksonError('SAML configuration not found.', 403);
|
115
184
|
}
|
116
|
-
// TODO: Support multiple matches
|
117
185
|
samlConfig = samlConfigs[0];
|
186
|
+
// Support multiple matches
|
187
|
+
const { resolvedSamlConfig, redirect_url } = this.resolveMultipleConfigMatches(samlConfigs, idp_hint, {
|
188
|
+
response_type,
|
189
|
+
client_id,
|
190
|
+
redirect_uri,
|
191
|
+
state,
|
192
|
+
tenant,
|
193
|
+
product,
|
194
|
+
code_challenge,
|
195
|
+
code_challenge_method,
|
196
|
+
provider,
|
197
|
+
});
|
198
|
+
if (redirect_url) {
|
199
|
+
return { redirect_url };
|
200
|
+
}
|
201
|
+
if (resolvedSamlConfig) {
|
202
|
+
samlConfig = resolvedSamlConfig;
|
203
|
+
}
|
118
204
|
}
|
119
205
|
else {
|
120
206
|
samlConfig = yield this.configStore.get(client_id);
|
@@ -141,7 +227,7 @@ class OAuthController {
|
|
141
227
|
ssoUrl = sso.postUrl;
|
142
228
|
post = true;
|
143
229
|
}
|
144
|
-
const samlReq =
|
230
|
+
const samlReq = saml20_1.default.request({
|
145
231
|
ssoUrl,
|
146
232
|
entityID: this.opts.samlAudience,
|
147
233
|
callbackUrl: this.opts.externalUrl + this.opts.samlPath,
|
@@ -149,12 +235,16 @@ class OAuthController {
|
|
149
235
|
publicKey: samlConfig.certs.publicKey,
|
150
236
|
});
|
151
237
|
const sessionId = crypto_1.default.randomBytes(16).toString('hex');
|
152
|
-
const requested = {
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
238
|
+
const requested = { client_id, state };
|
239
|
+
if (requestedTenant) {
|
240
|
+
requested.tenant = requestedTenant;
|
241
|
+
}
|
242
|
+
if (requestedProduct) {
|
243
|
+
requested.product = requestedProduct;
|
244
|
+
}
|
245
|
+
if (idp_hint) {
|
246
|
+
requested.idp_hint = idp_hint;
|
247
|
+
}
|
158
248
|
yield this.sessionStore.put(sessionId, {
|
159
249
|
id: samlReq.id,
|
160
250
|
redirect_uri,
|
@@ -164,7 +254,7 @@ class OAuthController {
|
|
164
254
|
code_challenge_method,
|
165
255
|
requested,
|
166
256
|
});
|
167
|
-
const relayState = relayStatePrefix + sessionId;
|
257
|
+
const relayState = utils_1.relayStatePrefix + sessionId;
|
168
258
|
let redirectUrl;
|
169
259
|
let authorizeForm;
|
170
260
|
if (!post) {
|
@@ -176,7 +266,16 @@ class OAuthController {
|
|
176
266
|
}
|
177
267
|
else {
|
178
268
|
// HTTP POST binding
|
179
|
-
authorizeForm =
|
269
|
+
authorizeForm = saml20_1.default.createPostForm(ssoUrl, [
|
270
|
+
{
|
271
|
+
name: 'RelayState',
|
272
|
+
value: relayState,
|
273
|
+
},
|
274
|
+
{
|
275
|
+
name: 'SAMLRequest',
|
276
|
+
value: Buffer.from(samlReq.request).toString('base64'),
|
277
|
+
},
|
278
|
+
]);
|
180
279
|
}
|
181
280
|
return {
|
182
281
|
redirect_url: redirectUrl,
|
@@ -186,18 +285,16 @@ class OAuthController {
|
|
186
285
|
}
|
187
286
|
samlResponse(body) {
|
188
287
|
return __awaiter(this, void 0, void 0, function* () {
|
189
|
-
const { SAMLResponse } = body;
|
288
|
+
const { SAMLResponse, idp_hint } = body;
|
190
289
|
let RelayState = body.RelayState || ''; // RelayState will contain the sessionId from earlier quasi-oauth flow
|
191
|
-
|
290
|
+
const isIdPFlow = !RelayState.startsWith(utils_1.relayStatePrefix);
|
291
|
+
if (!this.opts.idpEnabled && isIdPFlow) {
|
192
292
|
// IDP is disabled so block the request
|
193
293
|
throw new error_1.JacksonError('IdP (Identity Provider) flow has been disabled. Please head to your Service Provider to login.', 403);
|
194
294
|
}
|
195
|
-
|
196
|
-
RelayState = '';
|
197
|
-
}
|
198
|
-
RelayState = RelayState.replace(relayStatePrefix, '');
|
295
|
+
RelayState = RelayState.replace(utils_1.relayStatePrefix, '');
|
199
296
|
const rawResponse = Buffer.from(SAMLResponse, 'base64').toString();
|
200
|
-
const parsedResp = yield
|
297
|
+
const parsedResp = yield saml20_1.default.parseAsync(rawResponse);
|
201
298
|
const samlConfigs = yield this.configStore.getByIndex({
|
202
299
|
name: utils_1.IndexNames.EntityID,
|
203
300
|
value: parsedResp === null || parsedResp === void 0 ? void 0 : parsedResp.issuer,
|
@@ -205,6 +302,17 @@ class OAuthController {
|
|
205
302
|
if (!samlConfigs || samlConfigs.length === 0) {
|
206
303
|
throw new error_1.JacksonError('SAML configuration not found.', 403);
|
207
304
|
}
|
305
|
+
let samlConfig = samlConfigs[0];
|
306
|
+
if (isIdPFlow) {
|
307
|
+
RelayState = '';
|
308
|
+
const { resolvedSamlConfig, app_select_form } = this.resolveMultipleConfigMatches(samlConfigs, idp_hint, { SAMLResponse }, true);
|
309
|
+
if (app_select_form) {
|
310
|
+
return { app_select_form };
|
311
|
+
}
|
312
|
+
if (resolvedSamlConfig) {
|
313
|
+
samlConfig = resolvedSamlConfig;
|
314
|
+
}
|
315
|
+
}
|
208
316
|
let session;
|
209
317
|
if (RelayState !== '') {
|
210
318
|
session = yield this.sessionStore.get(RelayState);
|
@@ -212,14 +320,17 @@ class OAuthController {
|
|
212
320
|
throw new error_1.JacksonError('Unable to validate state from the origin request.', 403);
|
213
321
|
}
|
214
322
|
}
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
323
|
+
if (!isIdPFlow) {
|
324
|
+
// Resolve if there are multiple matches for SP login. TODO: Support multiple matches for IdP login
|
325
|
+
samlConfig =
|
326
|
+
samlConfigs.length === 1
|
327
|
+
? samlConfigs[0]
|
328
|
+
: samlConfigs.filter((c) => {
|
329
|
+
var _a, _b, _c;
|
330
|
+
return (c.clientID === ((_a = session === null || session === void 0 ? void 0 : session.requested) === null || _a === void 0 ? void 0 : _a.client_id) ||
|
331
|
+
(c.tenant === ((_b = session === null || session === void 0 ? void 0 : session.requested) === null || _b === void 0 ? void 0 : _b.tenant) && c.product === ((_c = session === null || session === void 0 ? void 0 : session.requested) === null || _c === void 0 ? void 0 : _c.product)));
|
332
|
+
})[0];
|
333
|
+
}
|
223
334
|
if (!samlConfig) {
|
224
335
|
throw new error_1.JacksonError('SAML configuration not found.', 403);
|
225
336
|
}
|
@@ -230,7 +341,7 @@ class OAuthController {
|
|
230
341
|
if (session && session.id) {
|
231
342
|
validateOpts.inResponseTo = session.id;
|
232
343
|
}
|
233
|
-
const profile = yield
|
344
|
+
const profile = yield validateResponse(rawResponse, validateOpts);
|
234
345
|
// store details against a code
|
235
346
|
const code = crypto_1.default.randomBytes(20).toString('hex');
|
236
347
|
const codeVal = {
|
@@ -2,5 +2,5 @@ export declare enum IndexNames {
|
|
2
2
|
EntityID = "entityID",
|
3
3
|
TenantProduct = "tenantProduct"
|
4
4
|
}
|
5
|
-
export declare const
|
5
|
+
export declare const relayStatePrefix = "boxyhq_jackson_";
|
6
6
|
export declare const validateAbsoluteUrl: (url: any, message: any) => void;
|
package/dist/controller/utils.js
CHANGED
@@ -1,36 +1,13 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
exports.validateAbsoluteUrl = exports.
|
3
|
+
exports.validateAbsoluteUrl = exports.relayStatePrefix = exports.IndexNames = void 0;
|
4
4
|
const error_1 = require("./error");
|
5
5
|
var IndexNames;
|
6
6
|
(function (IndexNames) {
|
7
7
|
IndexNames["EntityID"] = "entityID";
|
8
8
|
IndexNames["TenantProduct"] = "tenantProduct";
|
9
9
|
})(IndexNames = exports.IndexNames || (exports.IndexNames = {}));
|
10
|
-
|
11
|
-
const formElements = [
|
12
|
-
'<!DOCTYPE html>',
|
13
|
-
'<html>',
|
14
|
-
'<head>',
|
15
|
-
'<meta charset="utf-8">',
|
16
|
-
'<meta http-equiv="x-ua-compatible" content="ie=edge">',
|
17
|
-
'</head>',
|
18
|
-
'<body onload="document.forms[0].submit()">',
|
19
|
-
'<noscript>',
|
20
|
-
'<p>Note: Since your browser does not support JavaScript, you must press the Continue button once to proceed.</p>',
|
21
|
-
'</noscript>',
|
22
|
-
'<form method="post" action="' + encodeURI(postUrl) + '">',
|
23
|
-
'<input type="hidden" name="RelayState" value="' + relayState + '"/>',
|
24
|
-
'<input type="hidden" name="SAMLRequest" value="' + samlReqEnc + '"/>',
|
25
|
-
'<input type="submit" value="Continue" />',
|
26
|
-
'</form>',
|
27
|
-
'<script>document.forms[0].style.display="none";</script>',
|
28
|
-
'</body>',
|
29
|
-
'</html>',
|
30
|
-
];
|
31
|
-
return formElements.join('');
|
32
|
-
};
|
33
|
-
exports.createRequestForm = createRequestForm;
|
10
|
+
exports.relayStatePrefix = 'boxyhq_jackson_';
|
34
11
|
const validateAbsoluteUrl = (url, message) => {
|
35
12
|
try {
|
36
13
|
new URL(url);
|
package/dist/index.d.ts
CHANGED
@@ -2,7 +2,7 @@ import { AdminController } from './controller/admin';
|
|
2
2
|
import { APIController } from './controller/api';
|
3
3
|
import { OAuthController } from './controller/oauth';
|
4
4
|
import { HealthCheckController } from './controller/health-check';
|
5
|
-
import { LogoutController } from './controller/
|
5
|
+
import { LogoutController } from './controller/logout';
|
6
6
|
import { JacksonOption } from './typings';
|
7
7
|
export declare const controllers: (opts: JacksonOption) => Promise<{
|
8
8
|
apiController: APIController;
|
package/dist/index.js
CHANGED
@@ -31,7 +31,7 @@ const admin_1 = require("./controller/admin");
|
|
31
31
|
const api_1 = require("./controller/api");
|
32
32
|
const oauth_1 = require("./controller/oauth");
|
33
33
|
const health_check_1 = require("./controller/health-check");
|
34
|
-
const
|
34
|
+
const logout_1 = require("./controller/logout");
|
35
35
|
const db_1 = __importDefault(require("./db/db"));
|
36
36
|
const defaultDb_1 = __importDefault(require("./db/defaultDb"));
|
37
37
|
const read_config_1 = __importDefault(require("./read-config"));
|
@@ -58,7 +58,7 @@ const controllers = (opts) => __awaiter(void 0, void 0, void 0, function* () {
|
|
58
58
|
const sessionStore = db.store('oauth:session', opts.db.ttl);
|
59
59
|
const codeStore = db.store('oauth:code', opts.db.ttl);
|
60
60
|
const tokenStore = db.store('oauth:token', opts.db.ttl);
|
61
|
-
const healthCheckStore = db.store('_health');
|
61
|
+
const healthCheckStore = db.store('_health:check');
|
62
62
|
const apiController = new api_1.APIController({ configStore });
|
63
63
|
const adminController = new admin_1.AdminController({ configStore });
|
64
64
|
const healthCheckController = new health_check_1.HealthCheckController({ healthCheckStore });
|
@@ -70,7 +70,7 @@ const controllers = (opts) => __awaiter(void 0, void 0, void 0, function* () {
|
|
70
70
|
tokenStore,
|
71
71
|
opts,
|
72
72
|
});
|
73
|
-
const logoutController = new
|
73
|
+
const logoutController = new logout_1.LogoutController({
|
74
74
|
configStore,
|
75
75
|
sessionStore,
|
76
76
|
opts,
|
package/dist/typings.d.ts
CHANGED
@@ -29,7 +29,8 @@ export interface IOAuthController {
|
|
29
29
|
authorize_form?: string;
|
30
30
|
}>;
|
31
31
|
samlResponse(body: SAMLResponsePayload): Promise<{
|
32
|
-
redirect_url
|
32
|
+
redirect_url?: string;
|
33
|
+
app_select_form?: string;
|
33
34
|
}>;
|
34
35
|
token(body: OAuthTokenReq): Promise<OAuthTokenRes>;
|
35
36
|
userInfo(token: string): Promise<Profile>;
|
@@ -48,15 +49,17 @@ export interface OAuthReqBody {
|
|
48
49
|
client_id: string;
|
49
50
|
redirect_uri: string;
|
50
51
|
state: string;
|
51
|
-
tenant
|
52
|
-
product
|
52
|
+
tenant?: string;
|
53
|
+
product?: string;
|
53
54
|
code_challenge: string;
|
54
55
|
code_challenge_method: 'plain' | 'S256' | '';
|
55
56
|
provider: 'saml';
|
57
|
+
idp_hint?: string;
|
56
58
|
}
|
57
59
|
export interface SAMLResponsePayload {
|
58
60
|
SAMLResponse: string;
|
59
61
|
RelayState: string;
|
62
|
+
idp_hint?: string;
|
60
63
|
}
|
61
64
|
export interface OAuthTokenReq {
|
62
65
|
client_id: string;
|
@@ -111,23 +114,6 @@ export interface DatabaseOption {
|
|
111
114
|
encryptionKey?: string;
|
112
115
|
pageLimit?: number;
|
113
116
|
}
|
114
|
-
export interface SAMLReq {
|
115
|
-
ssoUrl?: string;
|
116
|
-
entityID: string;
|
117
|
-
callbackUrl: string;
|
118
|
-
isPassive?: boolean;
|
119
|
-
forceAuthn?: boolean;
|
120
|
-
identifierFormat?: 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress';
|
121
|
-
providerName?: 'BoxyHQ';
|
122
|
-
signingKey: string;
|
123
|
-
publicKey: string;
|
124
|
-
}
|
125
|
-
export interface SAMLProfile {
|
126
|
-
audience: string;
|
127
|
-
claims: Record<string, any>;
|
128
|
-
issuer: string;
|
129
|
-
sessionIndex: string;
|
130
|
-
}
|
131
117
|
export interface JacksonOption {
|
132
118
|
externalUrl: string;
|
133
119
|
samlPath: string;
|
@@ -136,6 +122,7 @@ export interface JacksonOption {
|
|
136
122
|
idpEnabled?: boolean;
|
137
123
|
db: DatabaseOption;
|
138
124
|
clientSecretVerifier?: string;
|
125
|
+
idpDiscoveryPath?: string;
|
139
126
|
}
|
140
127
|
export interface SLORequestParams {
|
141
128
|
nameId: string;
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@boxyhq/saml-jackson",
|
3
|
-
"version": "1.0.
|
3
|
+
"version": "1.0.2",
|
4
4
|
"description": "SAML Jackson library",
|
5
5
|
"keywords": [
|
6
6
|
"SAML 2.0"
|
@@ -36,38 +36,35 @@
|
|
36
36
|
"statements": 70
|
37
37
|
},
|
38
38
|
"dependencies": {
|
39
|
-
"@boxyhq/saml20": "0.2
|
39
|
+
"@boxyhq/saml20": "1.0.2",
|
40
40
|
"@opentelemetry/api-metrics": "0.27.0",
|
41
|
-
"@peculiar/webcrypto": "1.3.
|
41
|
+
"@peculiar/webcrypto": "1.3.3",
|
42
42
|
"@peculiar/x509": "1.6.1",
|
43
|
-
"mongodb": "4.
|
43
|
+
"mongodb": "4.5.0",
|
44
44
|
"mysql2": "2.3.3",
|
45
45
|
"pg": "8.7.3",
|
46
|
-
"
|
47
|
-
"redis": "4.0.4",
|
46
|
+
"redis": "4.0.6",
|
48
47
|
"reflect-metadata": "0.1.13",
|
49
48
|
"ripemd160": "2.0.2",
|
50
|
-
"
|
51
|
-
"typeorm": "0.3.3",
|
52
|
-
"xml-crypto": "2.1.3",
|
49
|
+
"typeorm": "0.3.6",
|
53
50
|
"xml2js": "0.4.23",
|
54
51
|
"xmlbuilder": "15.1.1"
|
55
52
|
},
|
56
53
|
"devDependencies": {
|
57
|
-
"@types/node": "17.0.
|
54
|
+
"@types/node": "17.0.30",
|
58
55
|
"@types/sinon": "10.0.11",
|
59
|
-
"@types/tap": "15.0.
|
60
|
-
"@typescript-eslint/eslint-plugin": "5.
|
61
|
-
"@typescript-eslint/parser": "5.
|
56
|
+
"@types/tap": "15.0.7",
|
57
|
+
"@typescript-eslint/eslint-plugin": "5.21.0",
|
58
|
+
"@typescript-eslint/parser": "5.21.0",
|
62
59
|
"cross-env": "7.0.3",
|
63
|
-
"eslint": "8.
|
60
|
+
"eslint": "8.14.0",
|
64
61
|
"eslint-config-prettier": "8.5.0",
|
65
|
-
"prettier": "2.6.
|
66
|
-
"sinon": "13.0.
|
67
|
-
"tap": "16.0
|
62
|
+
"prettier": "2.6.2",
|
63
|
+
"sinon": "13.0.2",
|
64
|
+
"tap": "16.1.0",
|
68
65
|
"ts-node": "10.7.0",
|
69
66
|
"tsconfig-paths": "3.14.1",
|
70
|
-
"typescript": "4.6.
|
67
|
+
"typescript": "4.6.4"
|
71
68
|
},
|
72
69
|
"engines": {
|
73
70
|
"node": ">=14.18.1 <=16.x"
|
package/dist/saml/saml.d.ts
DELETED
@@ -1,15 +0,0 @@
|
|
1
|
-
import { SAMLProfile, SAMLReq } from '../typings';
|
2
|
-
export declare const stripCertHeaderAndFooter: (cert: string) => string;
|
3
|
-
declare function PubKeyInfo(this: any, pubKey: string): void;
|
4
|
-
declare const _default: {
|
5
|
-
request: ({ ssoUrl, entityID, callbackUrl, isPassive, forceAuthn, identifierFormat, providerName, signingKey, publicKey, }: SAMLReq) => {
|
6
|
-
id: string;
|
7
|
-
request: string;
|
8
|
-
};
|
9
|
-
parseAsync: (rawAssertion: string) => Promise<SAMLProfile>;
|
10
|
-
validateAsync: (rawAssertion: string, options: any) => Promise<SAMLProfile>;
|
11
|
-
parseMetadataAsync: (idpMeta: string) => Promise<Record<string, any>>;
|
12
|
-
PubKeyInfo: typeof PubKeyInfo;
|
13
|
-
certToPEM: (cert: string) => string;
|
14
|
-
};
|
15
|
-
export default _default;
|
package/dist/saml/saml.js
DELETED
@@ -1,240 +0,0 @@
|
|
1
|
-
"use strict";
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
3
|
-
if (k2 === undefined) k2 = k;
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
7
|
-
}
|
8
|
-
Object.defineProperty(o, k2, desc);
|
9
|
-
}) : (function(o, m, k, k2) {
|
10
|
-
if (k2 === undefined) k2 = k;
|
11
|
-
o[k2] = m[k];
|
12
|
-
}));
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
15
|
-
}) : function(o, v) {
|
16
|
-
o["default"] = v;
|
17
|
-
});
|
18
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
19
|
-
if (mod && mod.__esModule) return mod;
|
20
|
-
var result = {};
|
21
|
-
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
22
|
-
__setModuleDefault(result, mod);
|
23
|
-
return result;
|
24
|
-
};
|
25
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
26
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
27
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
28
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
29
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
30
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
31
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
32
|
-
});
|
33
|
-
};
|
34
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
35
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
36
|
-
};
|
37
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
38
|
-
exports.stripCertHeaderAndFooter = void 0;
|
39
|
-
const saml20_1 = __importDefault(require("@boxyhq/saml20"));
|
40
|
-
const crypto_1 = __importDefault(require("crypto"));
|
41
|
-
const rambda = __importStar(require("rambda"));
|
42
|
-
const thumbprint_1 = __importDefault(require("thumbprint"));
|
43
|
-
const xml_crypto_1 = __importDefault(require("xml-crypto"));
|
44
|
-
const xml2js_1 = __importDefault(require("xml2js"));
|
45
|
-
const xmlbuilder_1 = __importDefault(require("xmlbuilder"));
|
46
|
-
const claims_1 = __importDefault(require("./claims"));
|
47
|
-
const idPrefix = '_';
|
48
|
-
const authnXPath = '/*[local-name(.)="AuthnRequest" and namespace-uri(.)="urn:oasis:names:tc:SAML:2.0:protocol"]';
|
49
|
-
const issuerXPath = '/*[local-name(.)="Issuer" and namespace-uri(.)="urn:oasis:names:tc:SAML:2.0:assertion"]';
|
50
|
-
const stripCertHeaderAndFooter = (cert) => {
|
51
|
-
cert = cert.replace(/-+BEGIN CERTIFICATE-+\r?\n?/, '');
|
52
|
-
cert = cert.replace(/-+END CERTIFICATE-+\r?\n?/, '');
|
53
|
-
cert = cert.replace(/\r\n/g, '\n');
|
54
|
-
return cert;
|
55
|
-
};
|
56
|
-
exports.stripCertHeaderAndFooter = stripCertHeaderAndFooter;
|
57
|
-
function PubKeyInfo(pubKey) {
|
58
|
-
this.pubKey = (0, exports.stripCertHeaderAndFooter)(pubKey);
|
59
|
-
this.getKeyInfo = function (_key, prefix) {
|
60
|
-
prefix = prefix || '';
|
61
|
-
prefix = prefix ? prefix + ':' : prefix;
|
62
|
-
return `<${prefix}X509Data><${prefix}X509Certificate>${this.pubKey}</${prefix}X509Certificate</${prefix}X509Data>`;
|
63
|
-
};
|
64
|
-
}
|
65
|
-
const signRequest = (xml, signingKey, publicKey) => {
|
66
|
-
if (!xml) {
|
67
|
-
throw new Error('Please specify xml');
|
68
|
-
}
|
69
|
-
if (!signingKey) {
|
70
|
-
throw new Error('Please specify signingKey');
|
71
|
-
}
|
72
|
-
const sig = new xml_crypto_1.default.SignedXml();
|
73
|
-
sig.signatureAlgorithm = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256';
|
74
|
-
sig.keyInfoProvider = new PubKeyInfo(publicKey);
|
75
|
-
sig.signingKey = signingKey;
|
76
|
-
sig.addReference(authnXPath, ['http://www.w3.org/2000/09/xmldsig#enveloped-signature', 'http://www.w3.org/2001/10/xml-exc-c14n#'], 'http://www.w3.org/2001/04/xmlenc#sha256');
|
77
|
-
sig.computeSignature(xml, {
|
78
|
-
location: { reference: authnXPath + issuerXPath, action: 'after' },
|
79
|
-
});
|
80
|
-
return sig.getSignedXml();
|
81
|
-
};
|
82
|
-
const request = ({ ssoUrl, entityID, callbackUrl, isPassive = false, forceAuthn = false, identifierFormat = 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress', providerName = 'BoxyHQ', signingKey, publicKey, }) => {
|
83
|
-
const id = idPrefix + crypto_1.default.randomBytes(10).toString('hex');
|
84
|
-
const date = new Date().toISOString();
|
85
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
86
|
-
const samlReq = {
|
87
|
-
'samlp:AuthnRequest': {
|
88
|
-
'@xmlns:samlp': 'urn:oasis:names:tc:SAML:2.0:protocol',
|
89
|
-
'@ID': id,
|
90
|
-
'@Version': '2.0',
|
91
|
-
'@IssueInstant': date,
|
92
|
-
'@ProtocolBinding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST',
|
93
|
-
'@Destination': ssoUrl,
|
94
|
-
'saml:Issuer': {
|
95
|
-
'@xmlns:saml': 'urn:oasis:names:tc:SAML:2.0:assertion',
|
96
|
-
'#text': entityID,
|
97
|
-
},
|
98
|
-
},
|
99
|
-
};
|
100
|
-
if (isPassive)
|
101
|
-
samlReq['samlp:AuthnRequest']['@IsPassive'] = true;
|
102
|
-
if (forceAuthn) {
|
103
|
-
samlReq['samlp:AuthnRequest']['@ForceAuthn'] = true;
|
104
|
-
}
|
105
|
-
samlReq['samlp:AuthnRequest']['@AssertionConsumerServiceURL'] = callbackUrl;
|
106
|
-
samlReq['samlp:AuthnRequest']['samlp:NameIDPolicy'] = {
|
107
|
-
'@xmlns:samlp': 'urn:oasis:names:tc:SAML:2.0:protocol',
|
108
|
-
'@Format': identifierFormat,
|
109
|
-
'@AllowCreate': 'true',
|
110
|
-
};
|
111
|
-
if (providerName != null) {
|
112
|
-
samlReq['samlp:AuthnRequest']['@ProviderName'] = providerName;
|
113
|
-
}
|
114
|
-
let xml = xmlbuilder_1.default.create(samlReq).end({});
|
115
|
-
if (signingKey) {
|
116
|
-
xml = signRequest(xml, signingKey, publicKey);
|
117
|
-
}
|
118
|
-
return {
|
119
|
-
id,
|
120
|
-
request: xml,
|
121
|
-
};
|
122
|
-
};
|
123
|
-
const parseAsync = (rawAssertion) => __awaiter(void 0, void 0, void 0, function* () {
|
124
|
-
return new Promise((resolve, reject) => {
|
125
|
-
saml20_1.default.parse(rawAssertion, function onParseAsync(err, profile) {
|
126
|
-
if (err) {
|
127
|
-
reject(err);
|
128
|
-
return;
|
129
|
-
}
|
130
|
-
resolve(profile);
|
131
|
-
});
|
132
|
-
});
|
133
|
-
});
|
134
|
-
const validateAsync = (rawAssertion, options) => __awaiter(void 0, void 0, void 0, function* () {
|
135
|
-
return new Promise((resolve, reject) => {
|
136
|
-
saml20_1.default.validate(rawAssertion, options, function onValidateAsync(err, profile) {
|
137
|
-
if (err) {
|
138
|
-
reject(err);
|
139
|
-
return;
|
140
|
-
}
|
141
|
-
if (profile && profile.claims) {
|
142
|
-
// we map claims to our attributes id, email, firstName, lastName where possible. We also map original claims to raw
|
143
|
-
profile.claims = claims_1.default.map(profile.claims);
|
144
|
-
// some providers don't return the id in the assertion, we set it to a sha256 hash of the email
|
145
|
-
if (!profile.claims.id) {
|
146
|
-
profile.claims.id = crypto_1.default.createHash('sha256').update(profile.claims.email).digest('hex');
|
147
|
-
}
|
148
|
-
}
|
149
|
-
resolve(profile);
|
150
|
-
});
|
151
|
-
});
|
152
|
-
});
|
153
|
-
const parseMetadataAsync = (idpMeta) => __awaiter(void 0, void 0, void 0, function* () {
|
154
|
-
return new Promise((resolve, reject) => {
|
155
|
-
xml2js_1.default.parseString(idpMeta, { tagNameProcessors: [xml2js_1.default.processors.stripPrefix] }, (err, res) => {
|
156
|
-
if (err) {
|
157
|
-
reject(err);
|
158
|
-
return;
|
159
|
-
}
|
160
|
-
const entityID = rambda.path('EntityDescriptor.$.entityID', res);
|
161
|
-
let X509Certificate = null;
|
162
|
-
let ssoPostUrl = null;
|
163
|
-
let ssoRedirectUrl = null;
|
164
|
-
let loginType = 'idp';
|
165
|
-
let sloRedirectUrl = null;
|
166
|
-
let sloPostUrl = null;
|
167
|
-
let ssoDes = rambda.pathOr(null, 'EntityDescriptor.IDPSSODescriptor', res);
|
168
|
-
if (!ssoDes) {
|
169
|
-
ssoDes = rambda.pathOr([], 'EntityDescriptor.SPSSODescriptor', res);
|
170
|
-
if (!ssoDes) {
|
171
|
-
loginType = 'sp';
|
172
|
-
}
|
173
|
-
}
|
174
|
-
for (const ssoDesRec of ssoDes) {
|
175
|
-
const keyDes = ssoDesRec['KeyDescriptor'];
|
176
|
-
for (const keyDesRec of keyDes) {
|
177
|
-
if (keyDesRec['$'] && keyDesRec['$'].use === 'signing') {
|
178
|
-
const ki = keyDesRec['KeyInfo'][0];
|
179
|
-
const cd = ki['X509Data'][0];
|
180
|
-
X509Certificate = cd['X509Certificate'][0];
|
181
|
-
}
|
182
|
-
}
|
183
|
-
const ssoSvc = ssoDesRec['SingleSignOnService'] || ssoDesRec['AssertionConsumerService'] || [];
|
184
|
-
for (const ssoSvcRec of ssoSvc) {
|
185
|
-
if (rambda.pathOr('', '$.Binding', ssoSvcRec).endsWith('HTTP-POST')) {
|
186
|
-
ssoPostUrl = rambda.path('$.Location', ssoSvcRec);
|
187
|
-
}
|
188
|
-
else if (rambda.pathOr('', '$.Binding', ssoSvcRec).endsWith('HTTP-Redirect')) {
|
189
|
-
ssoRedirectUrl = rambda.path('$.Location', ssoSvcRec);
|
190
|
-
}
|
191
|
-
}
|
192
|
-
const sloSvc = ssoDesRec['SingleLogoutService'] || [];
|
193
|
-
for (const sloSvcRec of sloSvc) {
|
194
|
-
if (rambda.pathOr('', '$.Binding', sloSvcRec).endsWith('HTTP-Redirect')) {
|
195
|
-
sloRedirectUrl = rambda.path('$.Location', sloSvcRec);
|
196
|
-
}
|
197
|
-
else if (rambda.pathOr('', '$.Binding', sloSvcRec).endsWith('HTTP-POST')) {
|
198
|
-
sloPostUrl = rambda.path('$.Location', sloSvcRec);
|
199
|
-
}
|
200
|
-
}
|
201
|
-
}
|
202
|
-
const ret = {
|
203
|
-
sso: {},
|
204
|
-
slo: {},
|
205
|
-
};
|
206
|
-
if (entityID) {
|
207
|
-
ret.entityID = entityID;
|
208
|
-
}
|
209
|
-
if (X509Certificate) {
|
210
|
-
ret.thumbprint = thumbprint_1.default.calculate(X509Certificate);
|
211
|
-
}
|
212
|
-
if (ssoPostUrl) {
|
213
|
-
ret.sso.postUrl = ssoPostUrl;
|
214
|
-
}
|
215
|
-
if (ssoRedirectUrl) {
|
216
|
-
ret.sso.redirectUrl = ssoRedirectUrl;
|
217
|
-
}
|
218
|
-
if (sloRedirectUrl) {
|
219
|
-
ret.slo.redirectUrl = sloRedirectUrl;
|
220
|
-
}
|
221
|
-
if (sloPostUrl) {
|
222
|
-
ret.slo.postUrl = sloPostUrl;
|
223
|
-
}
|
224
|
-
ret.loginType = loginType;
|
225
|
-
resolve(ret);
|
226
|
-
});
|
227
|
-
});
|
228
|
-
});
|
229
|
-
const certToPEM = (cert) => {
|
230
|
-
if (cert.indexOf('BEGIN CERTIFICATE') === -1 && cert.indexOf('END CERTIFICATE') === -1) {
|
231
|
-
const matches = cert.match(/.{1,64}/g);
|
232
|
-
if (matches) {
|
233
|
-
cert = matches.join('\n');
|
234
|
-
cert = '-----BEGIN CERTIFICATE-----\n' + cert;
|
235
|
-
cert = cert + '\n-----END CERTIFICATE-----\n';
|
236
|
-
}
|
237
|
-
}
|
238
|
-
return cert;
|
239
|
-
};
|
240
|
-
exports.default = { request, parseAsync, validateAsync, parseMetadataAsync, PubKeyInfo, certToPEM };
|