diff --git a/src/Config.js b/src/Config.js index 52d1bb816b..4bbfc8a651 100644 --- a/src/Config.js +++ b/src/Config.js @@ -73,6 +73,7 @@ export class Config { this.generateSessionExpiresAt = this.generateSessionExpiresAt.bind(this); this.generateEmailVerifyTokenExpiresAt = this.generateEmailVerifyTokenExpiresAt.bind(this); this.revokeSessionOnPasswordReset = cacheInfo.revokeSessionOnPasswordReset; + this.authenticatedFileRetrieval = cacheInfo.authenticatedFileRetrieval; } static validate({ diff --git a/src/ParseServer.js b/src/ParseServer.js index bdb0c07f8a..4cc4c6dc6a 100644 --- a/src/ParseServer.js +++ b/src/ParseServer.js @@ -141,6 +141,7 @@ class ParseServer { schemaCacheTTL = defaults.schemaCacheTTL, // cache for 5s enableSingleSchemaCache = false, __indexBuildCompletionCallbackForTests = () => {}, + authenticatedFileRetrieval, }) { // Initialize the node client SDK automatically Parse.initialize(appId, javascriptKey || 'unused', masterKey); @@ -253,6 +254,7 @@ class ParseServer { liveQueryController: liveQueryController, sessionLength: Number(sessionLength), expireInactiveSessions: expireInactiveSessions, + authenticatedFileRetrieval: authenticatedFileRetrieval, jsonLogs, revokeSessionOnPasswordReset, databaseController, diff --git a/src/Routers/FilesRouter.js b/src/Routers/FilesRouter.js index 4f788d679e..9d814a6870 100644 --- a/src/Routers/FilesRouter.js +++ b/src/Routers/FilesRouter.js @@ -10,7 +10,7 @@ export class FilesRouter { expressRouter(options = {}) { var router = express.Router(); - router.get('/files/:appId/:filename', this.getHandler); + router.get('/files/:appId/:filename/:referencingClass?/:referencingKey?/:sessionToken?', this.getHandler); router.post('/files', function(req, res, next) { next(new Parse.Error(Parse.Error.INVALID_FILE_NAME, @@ -36,28 +36,39 @@ export class FilesRouter { getHandler(req, res) { const config = new Config(req.params.appId); const filesController = config.filesController; - const filename = req.params.filename; + const params = req.params; + const filename = params.filename; const contentType = mime.lookup(filename); - if (isFileStreamable(req, filesController)) { - filesController.getFileStream(config, filename).then((stream) => { - handleFileStream(stream, req, res, contentType); - }).catch(() => { - res.status(404); - res.set('Content-Type', 'text/plain'); - res.end('File not found.'); - }); - } else { - filesController.getFileData(config, filename).then((data) => { - res.status(200); - res.set('Content-Type', contentType); - res.set('Content-Length', data.length); - res.end(data); - }).catch(() => { + + checkAuthentication(config, params).then((isAuthenticated) => { + if (isAuthenticated) { + if (isFileStreamable(req, filesController)) { + filesController.getFileStream(config, filename).then((stream) => { + handleFileStream(stream, req, res, contentType); + }).catch(() => { + res.status(404); + res.set('Content-Type', 'text/plain'); + res.end('File not found.'); + }); + } else { + filesController.getFileData(config, filename).then((data) => { + res.status(200); + res.set('Content-Type', contentType); + res.set('Content-Length', data.length); + res.end(data); + }).catch(() => { + res.status(404); + res.set('Content-Type', 'text/plain'); + res.end('File not found.'); + }); + } + } else { res.status(404); res.set('Content-Type', 'text/plain'); - res.end('File not found.'); - }); - } + res.end('Unauthorized.'); + } + }) + } createHandler(req, res, next) { @@ -107,6 +118,31 @@ export class FilesRouter { } } +function checkAuthentication(config, params) { + if (config.authenticatedFileRetrieval) { + const restController = Parse.CoreManager.getRESTController(); + const filename = params.filename; + const referencingClass = params.referencingClass; + const referencingKey = params.referencingKey; + const sessionToken = params.sessionToken; + const url = `${config.publicServerURL}files/${config.appId}/${filename}`; + const fileObject = {name: filename, url: url, __type: 'File'}; + const query = {where: {[referencingKey]: fileObject}}; + + if (!referencingClass || !referencingKey || !sessionToken) return Promise.resolve(false); + else { + return restController.request("GET", `/classes/${referencingClass}`, query, {sessionToken}).then((res) => { + if (res.results.length > 0) + return true; + else + return false; + }, (err) => { + return false; + }); + } + } else return Promise.resolve(true); +} + function isFileStreamable(req, filesController){ if (req.get('Range')) { if (!(typeof filesController.adapter.getFileStream === 'function')) { diff --git a/src/defaults.js b/src/defaults.js index 1b519617d4..d7a5ffbf34 100644 --- a/src/defaults.js +++ b/src/defaults.js @@ -32,5 +32,6 @@ export default { expireInactiveSessions: true, revokeSessionOnPasswordReset: true, schemaCacheTTL: 5000, // in ms - userSensitiveFields: ['email'] + userSensitiveFields: ['email'], + authenticatedFileRetrieval: false, }