mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-12 13:13:45 +01:00
[idp] adopt strict zod types and extend test coverage (#36038)
GitOrigin-RevId: d9887f877b44e03b501072f738387e4646a1431a
This commit is contained in:
@@ -0,0 +1,251 @@
|
|||||||
|
diff --git a/lib/logout.js b/lib/logout.js
|
||||||
|
index 77c417213851ffbe4a29f1b501027b8727931d8f..03d1bd8452ce80964f187f541ff47986931d3b73 100644
|
||||||
|
--- a/lib/logout.js
|
||||||
|
+++ b/lib/logout.js
|
||||||
|
@@ -10,6 +10,20 @@ var zlib = require('zlib');
|
||||||
|
var qs = require('querystring');
|
||||||
|
var xtend = require('xtend');
|
||||||
|
var constants = require('./constants');
|
||||||
|
+// --- Overleaf patch: request-input lockdown compatibility ---
|
||||||
|
+// See lib/samlp.js for the full rationale; the IdP-initiated and
|
||||||
|
+// SP-initiated logout flows read SAMLRequest/SAMLResponse/RelayState off
|
||||||
|
+// req.query/req.body directly, same as the login flow there.
|
||||||
|
+var __overleafLockdownInstalled = Symbol.for('overleaf.lockdown.installed');
|
||||||
|
+var __overleafLockdownRawQuery = Symbol.for('overleaf.lockdown.rawQuery');
|
||||||
|
+var __overleafLockdownRawBody = Symbol.for('overleaf.lockdown.rawBody');
|
||||||
|
+function __overleafRawQuery(req) {
|
||||||
|
+ return req[__overleafLockdownInstalled] ? req[__overleafLockdownRawQuery] : req.query;
|
||||||
|
+}
|
||||||
|
+function __overleafRawBody(req) {
|
||||||
|
+ return req[__overleafLockdownInstalled] ? req[__overleafLockdownRawBody] : req.body;
|
||||||
|
+}
|
||||||
|
+// --- end Overleaf patch ---
|
||||||
|
|
||||||
|
var BINDINGS = constants.BINDINGS;
|
||||||
|
var STATUS = constants.STATUS;
|
||||||
|
@@ -87,7 +101,7 @@ module.exports.logout = function (options) {
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateSamlResponse(req, sessionParticipant, cb) {
|
||||||
|
- var SAMLResponse = req.query.SAMLResponse || req.body.SAMLResponse;
|
||||||
|
+ var SAMLResponse = __overleafRawQuery(req).SAMLResponse || __overleafRawBody(req).SAMLResponse;
|
||||||
|
|
||||||
|
function parseAndValidate(err, buffer) {
|
||||||
|
if (err) { return cb(err); }
|
||||||
|
@@ -141,7 +155,7 @@ module.exports.logout = function (options) {
|
||||||
|
cb(null, parsedResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
- if (req.body.SAMLResponse || !options.deflate) {
|
||||||
|
+ if (__overleafRawBody(req).SAMLResponse || !options.deflate) {
|
||||||
|
// HTTP-POST or HTTP-Redirect without deflate encoding
|
||||||
|
return parseAndValidate(null, new Buffer(SAMLResponse, 'base64'));
|
||||||
|
}
|
||||||
|
@@ -153,7 +167,7 @@ module.exports.logout = function (options) {
|
||||||
|
return function (req, res, next) {
|
||||||
|
try {
|
||||||
|
// SP Initated flow.
|
||||||
|
- if (req.query.SAMLRequest || req.body.SAMLRequest) {
|
||||||
|
+ if (__overleafRawQuery(req).SAMLRequest || __overleafRawBody(req).SAMLRequest) {
|
||||||
|
const opts = {
|
||||||
|
getCredentials: function getCredentials(issuer, sessionIndices, nameId, cb) {
|
||||||
|
options.sessionParticipants.get(issuer, sessionIndices, nameId, function (err, session) {
|
||||||
|
@@ -168,7 +182,7 @@ module.exports.logout = function (options) {
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
- parseIncomingLogoutRequest(req, req.query.SAMLRequest || req.body.SAMLRequest, opts, function (err, requestData) {
|
||||||
|
+ parseIncomingLogoutRequest(req, __overleafRawQuery(req).SAMLRequest || __overleafRawBody(req).SAMLRequest, opts, function (err, requestData) {
|
||||||
|
if (err) { return next(err); }
|
||||||
|
|
||||||
|
if (!requestData.issuer) {
|
||||||
|
@@ -185,7 +199,7 @@ module.exports.logout = function (options) {
|
||||||
|
id: requestData.id,
|
||||||
|
serviceProviderLogoutURL: (session|| {}).serviceProviderLogoutURL || options.destination
|
||||||
|
},
|
||||||
|
- relayState: req.query.RelayState || (req.body && req.body.RelayState)
|
||||||
|
+ relayState: __overleafRawQuery(req).RelayState || (__overleafRawBody(req) && __overleafRawBody(req).RelayState)
|
||||||
|
};
|
||||||
|
|
||||||
|
options.store.save(req, spData, function (err, transactionId) {
|
||||||
|
@@ -206,7 +220,7 @@ module.exports.logout = function (options) {
|
||||||
|
});
|
||||||
|
|
||||||
|
// Logout flow in progress, incoming SAMLResponse from SP. (Could be SP initiated or IdP initiated)
|
||||||
|
- } else if (req.query.SAMLResponse || req.body.SAMLResponse) {
|
||||||
|
+ } else if (__overleafRawQuery(req).SAMLResponse || __overleafRawBody(req).SAMLResponse) {
|
||||||
|
function process(state, transactionId) {
|
||||||
|
// LogoutResponse was OK, we remove the session participant from the IdP
|
||||||
|
options.sessionParticipants.remove(state.issuer, state.sessionIndex, state.nameId, function (err) {
|
||||||
|
@@ -218,7 +232,7 @@ module.exports.logout = function (options) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that the state sent to the SP matches the one returned
|
||||||
|
- var h = req.query.RelayState || (req.body && req.body.RelayState);
|
||||||
|
+ var h = __overleafRawQuery(req).RelayState || (__overleafRawBody(req) && __overleafRawBody(req).RelayState);
|
||||||
|
options.store.load(req, h, {destroy: true}, function (err, state) {
|
||||||
|
if (err) { return next(err); }
|
||||||
|
if (!state) { return next(new Error('Invalid RelayState')); }
|
||||||
|
@@ -379,7 +393,7 @@ module.exports.sendLogoutError = function(options){
|
||||||
|
|
||||||
|
// Signature location
|
||||||
|
options.reference = constants.ELEMENTS.LOGOUT_RESPONSE.SIGNATURE_LOCATION_PATH;
|
||||||
|
- options.relayState = options.relayState || req.query.RelayState || (req.body || {}).RelayState;
|
||||||
|
+ options.relayState = options.relayState || __overleafRawQuery(req).RelayState || (__overleafRawBody(req) || {}).RelayState;
|
||||||
|
|
||||||
|
return prepareAndSendToken(req, res, 'LOGOUT_RESPONSE', logoutResponse, options, next);
|
||||||
|
};
|
||||||
|
diff --git a/lib/samlp.js b/lib/samlp.js
|
||||||
|
index 19a13328d60124417e9aa844c6d54745f7121166..08bef8456e1dcbadceb0460588bb78fff1e3f122 100644
|
||||||
|
--- a/lib/samlp.js
|
||||||
|
+++ b/lib/samlp.js
|
||||||
|
@@ -7,6 +7,30 @@ var templates = require('./templates');
|
||||||
|
var encoders = require('./encoders');
|
||||||
|
var PassportProfileMapper = require('./claims/PassportProfileMapper');
|
||||||
|
var constants = require('./constants');
|
||||||
|
+// --- Overleaf patch: request-input lockdown compatibility ---
|
||||||
|
+// This library reads req.query/req.body directly to parse the SAML protocol
|
||||||
|
+// fields (Signature, SigAlg, RelayState, SAMLRequest/SAMLResponse) it is
|
||||||
|
+// built around, which is otherwise indistinguishable from unvalidated
|
||||||
|
+// application code reading raw request input under Overleaf's
|
||||||
|
+// request-input lockdown (see libraries/validation-tools and the patched
|
||||||
|
+// express in .yarn/patches/express-npm-*.patch). The lockdown captures the
|
||||||
|
+// original parsed values in symbol-keyed fields, registered in the global
|
||||||
|
+// symbol registry (the same ones @overleaf/validation-tools' lockdown.js
|
||||||
|
+// reads), and only turns the *public* req.query/req.body properties into
|
||||||
|
+// throwing accessors -- reading through these raw fields instead sidesteps
|
||||||
|
+// that, and is identical to plain req.query/req.body when the lockdown
|
||||||
|
+// isn't installed (i.e. for every consumer of this package outside this
|
||||||
|
+// monorepo).
|
||||||
|
+var __overleafLockdownInstalled = Symbol.for('overleaf.lockdown.installed');
|
||||||
|
+var __overleafLockdownRawQuery = Symbol.for('overleaf.lockdown.rawQuery');
|
||||||
|
+var __overleafLockdownRawBody = Symbol.for('overleaf.lockdown.rawBody');
|
||||||
|
+function __overleafRawQuery(req) {
|
||||||
|
+ return req[__overleafLockdownInstalled] ? req[__overleafLockdownRawQuery] : req.query;
|
||||||
|
+}
|
||||||
|
+function __overleafRawBody(req) {
|
||||||
|
+ return req[__overleafLockdownInstalled] ? req[__overleafLockdownRawBody] : req.body;
|
||||||
|
+}
|
||||||
|
+// --- end Overleaf patch ---
|
||||||
|
|
||||||
|
function buildSamlResponse(options) {
|
||||||
|
var SAMLResponse = templates.samlresponse({
|
||||||
|
@@ -153,10 +177,10 @@ module.exports.auth = function (options) {
|
||||||
|
return function (req, res, next) {
|
||||||
|
var opts = xtend({}, options || {}); // clone options
|
||||||
|
|
||||||
|
- if (req.method === 'GET' && req.query.Signature) {
|
||||||
|
- opts.signature = req.query.Signature;
|
||||||
|
- opts.sigAlg = req.query.SigAlg;
|
||||||
|
- opts.relayState = opts.RelayState || req.query.RelayState;
|
||||||
|
+ if (req.method === 'GET' && __overleafRawQuery(req).Signature) {
|
||||||
|
+ opts.signature = __overleafRawQuery(req).Signature;
|
||||||
|
+ opts.sigAlg = __overleafRawQuery(req).SigAlg;
|
||||||
|
+ opts.relayState = opts.RelayState || __overleafRawQuery(req).RelayState;
|
||||||
|
}
|
||||||
|
|
||||||
|
function execute(postUrl, audience, req, res, next) {
|
||||||
|
@@ -178,14 +202,14 @@ module.exports.auth = function (options) {
|
||||||
|
res.send(templates.form({
|
||||||
|
type: 'SAMLResponse',
|
||||||
|
callback: postUrl,
|
||||||
|
- RelayState: opts.RelayState || (req.query || {}).RelayState || (req.body || {}).RelayState || '',
|
||||||
|
+ RelayState: opts.RelayState || (__overleafRawQuery(req) || {}).RelayState || (__overleafRawBody(req) || {}).RelayState || '',
|
||||||
|
token: response.toString('base64')
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
- utils.parseSamlRequest(req, (req.query || {}).SAMLRequest || (req.body || {}).SAMLRequest, "AUTHN_REQUEST", opts, function (err, samlRequestDom) {
|
||||||
|
+ utils.parseSamlRequest(req, (__overleafRawQuery(req) || {}).SAMLRequest || (__overleafRawBody(req) || {}).SAMLRequest, "AUTHN_REQUEST", opts, function (err, samlRequestDom) {
|
||||||
|
if (err) return next(err);
|
||||||
|
|
||||||
|
var audience = opts.audience;
|
||||||
|
@@ -216,7 +240,7 @@ module.exports.parseRequest = function (req, options, callback) {
|
||||||
|
options = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
- var samlRequest = (req.query || {}).SAMLRequest || (req.body || {}).SAMLRequest;
|
||||||
|
+ var samlRequest = (__overleafRawQuery(req) || {}).SAMLRequest || (__overleafRawBody(req) || {}).SAMLRequest;
|
||||||
|
if (!samlRequest)
|
||||||
|
return callback();
|
||||||
|
|
||||||
|
diff --git a/lib/utils.js b/lib/utils.js
|
||||||
|
index c091ac335be08b2bf55907b269e8e1ef79b686bb..23940db43725664367329341a713f2a1077ad071 100644
|
||||||
|
--- a/lib/utils.js
|
||||||
|
+++ b/lib/utils.js
|
||||||
|
@@ -6,6 +6,21 @@ const url = require('url');
|
||||||
|
const xtend = require('xtend');
|
||||||
|
const signers = require('./signers');
|
||||||
|
const constants = require('./constants');
|
||||||
|
+// --- Overleaf patch: request-input lockdown compatibility ---
|
||||||
|
+// See lib/samlp.js for the full rationale; this module validates SAML
|
||||||
|
+// signatures carried on req.query/req.body (Signature, SigAlg, RelayState,
|
||||||
|
+// SAMLRequest/SAMLResponse) for both the login (samlp.js) and logout
|
||||||
|
+// (logout.js) flows.
|
||||||
|
+const __overleafLockdownInstalled = Symbol.for('overleaf.lockdown.installed');
|
||||||
|
+const __overleafLockdownRawQuery = Symbol.for('overleaf.lockdown.rawQuery');
|
||||||
|
+const __overleafLockdownRawBody = Symbol.for('overleaf.lockdown.rawBody');
|
||||||
|
+function __overleafRawQuery(req) {
|
||||||
|
+ return req[__overleafLockdownInstalled] ? req[__overleafLockdownRawQuery] : req.query;
|
||||||
|
+}
|
||||||
|
+function __overleafRawBody(req) {
|
||||||
|
+ return req[__overleafLockdownInstalled] ? req[__overleafLockdownRawBody] : req.body;
|
||||||
|
+}
|
||||||
|
+// --- end Overleaf patch ---
|
||||||
|
const IdGenerator = require('auth0-id-generator');
|
||||||
|
|
||||||
|
const generator = new IdGenerator({len: 20, alphabet:"abcdef0123456789"});
|
||||||
|
@@ -20,7 +35,7 @@ module.exports.parseSamlRequest = function(req, samlRequest, type, options, call
|
||||||
|
const opts = {
|
||||||
|
signingCert: options.signingCert,
|
||||||
|
thumbprints: options.thumbprints,
|
||||||
|
- deflate: !!req.query.Signature,
|
||||||
|
+ deflate: !!__overleafRawQuery(req).Signature,
|
||||||
|
relayState: options.relayState
|
||||||
|
};
|
||||||
|
|
||||||
|
@@ -186,10 +201,10 @@ function validateSignature(req, element_type, xml, options) {
|
||||||
|
const type = constants.ELEMENTS[element_type].PROP;
|
||||||
|
|
||||||
|
const isRequestSigned = !options.deflate ?
|
||||||
|
- xpath.select(options.signaturePath || constants.ELEMENTS[element_type].SIGNATURE_VALIDATION_PATH, xml).length > 0 : !!req.query.Signature;
|
||||||
|
+ xpath.select(options.signaturePath || constants.ELEMENTS[element_type].SIGNATURE_VALIDATION_PATH, xml).length > 0 : !!__overleafRawQuery(req).Signature;
|
||||||
|
|
||||||
|
if (isRequestSigned) {
|
||||||
|
- if ((req.body && req.body[type]) || !options.deflate) {
|
||||||
|
+ if ((__overleafRawBody(req) && __overleafRawBody(req)[type]) || !options.deflate) {
|
||||||
|
// HTTP-POST or HTTP-Redirect without deflate encoding
|
||||||
|
const validationErrors = signers.validateXmlEmbeddedSignature(xml, options);
|
||||||
|
if (validationErrors && validationErrors.length > 0) {
|
||||||
|
@@ -199,9 +214,9 @@ function validateSignature(req, element_type, xml, options) {
|
||||||
|
else {
|
||||||
|
// HTTP-Redirect with deflate encoding
|
||||||
|
let signedContent = {};
|
||||||
|
- signedContent[type] = req.query[type];
|
||||||
|
- signedContent.RelayState = req.query.RelayState || options.relayState;
|
||||||
|
- signedContent.SigAlg = req.query.SigAlg;
|
||||||
|
+ signedContent[type] = __overleafRawQuery(req)[type];
|
||||||
|
+ signedContent.RelayState = __overleafRawQuery(req).RelayState || options.relayState;
|
||||||
|
+ signedContent.SigAlg = __overleafRawQuery(req).SigAlg;
|
||||||
|
|
||||||
|
if (!signedContent.RelayState && !options.relayState) {
|
||||||
|
delete signedContent.RelayState;
|
||||||
|
@@ -215,13 +230,13 @@ function validateSignature(req, element_type, xml, options) {
|
||||||
|
throw new Error('Invalid signature algorithm. Supported algorithms are http://www.w3.org/2001/04/xmldsig-more#rsa-sha1 and http://www.w3.org/2001/04/xmldsig-more#rsa-sha256');
|
||||||
|
}
|
||||||
|
|
||||||
|
- const valid = signers.isValidContentAndSignature(qs.stringify(signedContent), req.query.Signature, {
|
||||||
|
+ const valid = signers.isValidContentAndSignature(qs.stringify(signedContent), __overleafRawQuery(req).Signature, {
|
||||||
|
signingCert: options.signingCert,
|
||||||
|
- signatureAlgorithm: req.query.SigAlg
|
||||||
|
+ signatureAlgorithm: __overleafRawQuery(req).SigAlg
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!valid) {
|
||||||
|
- throw new Error('Signature check errors: The signature provided (' + req.query.Signature + ') does not match the one calculated');
|
||||||
|
+ throw new Error('Signature check errors: The signature provided (' + __overleafRawQuery(req).Signature + ') does not match the one calculated');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (options.signingCert) {
|
||||||
+1
-1
@@ -109,7 +109,7 @@
|
|||||||
"http-status": "1.5.0",
|
"http-status": "1.5.0",
|
||||||
"knex": "2.4.0",
|
"knex": "2.4.0",
|
||||||
"utf-8-validate": "5.0.8",
|
"utf-8-validate": "5.0.8",
|
||||||
"samlp": "7.0.2",
|
"samlp": "patch:samlp@npm%3A7.0.2#~/.yarn/patches/samlp-npm-7.0.2-8295b3cf85.patch",
|
||||||
"compression": "1.7.4",
|
"compression": "1.7.4",
|
||||||
"cookie-parser": "1.4.6",
|
"cookie-parser": "1.4.6",
|
||||||
"react-cookie": "7.2.0",
|
"react-cookie": "7.2.0",
|
||||||
|
|||||||
@@ -7319,6 +7319,7 @@ __metadata:
|
|||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "@overleaf/idp@workspace:services/idp"
|
resolution: "@overleaf/idp@workspace:services/idp"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
"@overleaf/validation-tools": "workspace:*"
|
||||||
"@types/node": "npm:^24.5.2"
|
"@types/node": "npm:^24.5.2"
|
||||||
chai: "npm:^4.3.6"
|
chai: "npm:^4.3.6"
|
||||||
chai-as-promised: "npm:^7.1.1"
|
chai-as-promised: "npm:^7.1.1"
|
||||||
@@ -30188,6 +30189,24 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"samlp@patch:samlp@npm%3A7.0.2#~/.yarn/patches/samlp-npm-7.0.2-8295b3cf85.patch":
|
||||||
|
version: 7.0.2
|
||||||
|
resolution: "samlp@patch:samlp@npm%3A7.0.2#~/.yarn/patches/samlp-npm-7.0.2-8295b3cf85.patch::version=7.0.2&hash=d6c3e2"
|
||||||
|
dependencies:
|
||||||
|
"@auth0/thumbprint": "npm:0.0.6"
|
||||||
|
"@auth0/xmldom": "npm:0.1.21"
|
||||||
|
auth0-id-generator: "npm:^0.2.0"
|
||||||
|
ejs: "npm:^3.1.8"
|
||||||
|
flowstate: "npm:^0.4.0"
|
||||||
|
querystring: "npm:^0.2.0"
|
||||||
|
saml: "npm:^3.0.1"
|
||||||
|
xml-crypto: "npm:^2.0.0"
|
||||||
|
xpath: "npm:0.0.5"
|
||||||
|
xtend: "npm:^1.0.3"
|
||||||
|
checksum: 10c0/400079ee2871372847142e3d02500303b894e64d2c471f7861d674ebd0c672ef4e11399c2b1522755d0cf475109e61bb06cff0c23474aae673ebc5ba477a1cc8
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"sandboxed-module@npm:2.0.4":
|
"sandboxed-module@npm:2.0.4":
|
||||||
version: 2.0.4
|
version: 2.0.4
|
||||||
resolution: "sandboxed-module@npm:2.0.4"
|
resolution: "sandboxed-module@npm:2.0.4"
|
||||||
|
|||||||
Reference in New Issue
Block a user