diff --git a/spec/Middlewares.spec.js b/spec/Middlewares.spec.js index 7091206729..7079898160 100644 --- a/spec/Middlewares.spec.js +++ b/spec/Middlewares.spec.js @@ -412,4 +412,27 @@ describe('middlewares', () => { middlewares.handleParseHeaders(fakeReq, fakeRes); expect(fakeRes.status).toHaveBeenCalledWith(403); }); + + it('should success without x-parse-application-id in header when there is only one app', (done) => { + AppCache.del('test'); + delete fakeReq.body._ApplicationId; + middlewares.handleParseHeaders(fakeReq, fakeRes, () => { + expect(fakeRes.status).not.toHaveBeenCalled(); + done(); + }); + }); + it('should success with x-parse-application-id match an app when there are multiple apps', (done) => { + fakeReq.headers['x-parse-application-id'] = fakeReq.body._ApplicationId; + delete fakeReq.body._ApplicationId; + middlewares.handleParseHeaders(fakeReq, fakeRes, () => { + expect(fakeRes.status).not.toHaveBeenCalled(); + done(); + }); + }); + it('should faile with x-parse-application-id not match any app when there are multiple apps', () => { + fakeReq.headers['x-parse-application-id'] = 'do-not-match-an-app'; + delete fakeReq.body._ApplicationId; + middlewares.handleParseHeaders(fakeReq, fakeRes); + expect(fakeRes.status).toHaveBeenCalledWith(403); + }); }); diff --git a/src/middlewares.js b/src/middlewares.js index 526372ba39..ba7c4e52eb 100644 --- a/src/middlewares.js +++ b/src/middlewares.js @@ -49,73 +49,59 @@ export function handleParseHeaders(req, res, next) { } } + var fileViaJSON = false; + + // To support client SDKs that always POST instead of use proper HTTP calls, + // try parse req.body Buffer as JSON and read _ApplicationId and other keys from it + if (!info.appId && req.body instanceof Buffer) { + try { + req.body = JSON.parse(req.body); + fileViaJSON = true; + } catch (e) { + // Happens when upload non JSON file to /parse/file endpoint + } + } + + // Remove certain things sent by client if (req.body) { // Unity SDK sends a _noBody key which needs to be removed. // Unclear at this point if action needs to be taken. delete req.body._noBody; + // _Revocable Session should always be ignored + // https://github.com/parse-community/parse-server/issues/1548 + delete req.body._RevocableSession; } - var fileViaJSON = false; - - if (!info.appId || !AppCache.get(info.appId)) { - // See if we can find the app id on the body. - if (req.body instanceof Buffer) { - // The only chance to find the app id is if this is a file - // upload that actually is a JSON body. So try to parse it. - // https://github.com/parse-community/parse-server/issues/6589 - // It is also possible that the client is trying to upload a file but forgot - // to provide x-parse-app-id in header and parse a binary file will fail - try { - req.body = JSON.parse(req.body); - } catch (e) { - return invalidRequest(req, res); - } - fileViaJSON = true; + if (req.body && req.body._ApplicationId) { + info.appId = req.body._ApplicationId; + delete req.body._ApplicationId; + info.javascriptKey = req.body._JavaScriptKey || ''; + delete req.body._JavaScriptKey; + // TODO: test that the REST API formats generated by the other + // SDKs are handled ok + if (req.body._ClientVersion) { + info.clientVersion = req.body._ClientVersion; + delete req.body._ClientVersion; } - - if (req.body) { - delete req.body._RevocableSession; + if (req.body._InstallationId) { + info.installationId = req.body._InstallationId; + delete req.body._InstallationId; } - - if ( - req.body && - req.body._ApplicationId && - AppCache.get(req.body._ApplicationId) && - (!info.masterKey || - AppCache.get(req.body._ApplicationId).masterKey === info.masterKey) - ) { - info.appId = req.body._ApplicationId; - info.javascriptKey = req.body._JavaScriptKey || ''; - delete req.body._ApplicationId; - delete req.body._JavaScriptKey; - // TODO: test that the REST API formats generated by the other - // SDKs are handled ok - if (req.body._ClientVersion) { - info.clientVersion = req.body._ClientVersion; - delete req.body._ClientVersion; - } - if (req.body._InstallationId) { - info.installationId = req.body._InstallationId; - delete req.body._InstallationId; - } - if (req.body._SessionToken) { - info.sessionToken = req.body._SessionToken; - delete req.body._SessionToken; - } - if (req.body._MasterKey) { - info.masterKey = req.body._MasterKey; - delete req.body._MasterKey; - } - if (req.body._context && req.body._context instanceof Object) { - info.context = req.body._context; - delete req.body._context; - } - if (req.body._ContentType) { - req.headers['content-type'] = req.body._ContentType; - delete req.body._ContentType; - } - } else { - return invalidRequest(req, res); + if (req.body._SessionToken) { + info.sessionToken = req.body._SessionToken; + delete req.body._SessionToken; + } + if (req.body._MasterKey) { + info.masterKey = req.body._MasterKey; + delete req.body._MasterKey; + } + if (req.body._context && req.body._context instanceof Object) { + info.context = req.body._context; + delete req.body._context; + } + if (req.body._ContentType) { + req.headers['content-type'] = req.body._ContentType; + delete req.body._ContentType; } } @@ -136,8 +122,24 @@ export function handleParseHeaders(req, res, next) { const clientIp = getClientIp(req); - info.app = AppCache.get(info.appId); - req.config = Config.get(info.appId, mount); + let appId; + if (info.appId) { + // Request provided appId, just use that + appId = info.appId; + } else if (Object.keys(AppCache.cache).length === 1) { + // Request did not provide appId, but there is only one app anyway, just use that + appId = Object.keys(AppCache.cache)[0]; + } else { + // Request did not provide appId and there are multiple apps, we do not know which one to use + return invalidRequest(req, res); + } + + info.app = AppCache.get(appId); + if (!info.app) { + // The appId user provided is not valid + return invalidRequest(req, res); + } + req.config = Config.get(appId, mount); req.config.headers = req.headers || {}; req.config.ip = clientIp; req.info = info;