Merge pull request #753 from ParsePlatform/nlutsenko.middleware.routers

Use shared middleware to enforce masterkey security on logs/global config/hooks APIs.
This commit is contained in:
Nikita Lutsenko
2016-03-01 22:44:04 -08:00
5 changed files with 36 additions and 67 deletions

View File

@@ -1,3 +1,6 @@
'use strict';
const request = require('request');
var LogsRouter = require('../src/Routers/LogsRouter').LogsRouter; var LogsRouter = require('../src/Routers/LogsRouter').LogsRouter;
var LoggerController = require('../src/Controllers/LoggerController').LoggerController; var LoggerController = require('../src/Controllers/LoggerController').LoggerController;
var FileLoggerAdapter = require('../src/Adapters/Logger/FileLoggerAdapter').FileLoggerAdapter; var FileLoggerAdapter = require('../src/Adapters/Logger/FileLoggerAdapter').FileLoggerAdapter;
@@ -45,23 +48,18 @@ describe('LogsRouter', () => {
done(); done();
}); });
it('can check invalid master key of request', (done) => { it('can check invalid master key of request', done => {
// Make mock request request.get({
var request = { url: 'http://localhost:8378/1/logs',
auth: { json: true,
isMaster: false headers: {
}, 'X-Parse-Application-Id': 'test',
query: {}, 'X-Parse-REST-API-Key': 'rest'
config: {
loggerController: loggerController
} }
}; }, (error, response, body) => {
expect(response.statusCode).toEqual(403);
var router = new LogsRouter(); expect(body.error).toEqual('unauthorized: master key is required');
done();
expect(() => { });
router.handleGET(request);
}).toThrow();
done();
}); });
}); });

View File

@@ -53,8 +53,8 @@ describe('a GlobalConfig', () => {
'X-Parse-REST-API-Key': 'rest' 'X-Parse-REST-API-Key': 'rest'
}, },
}, (error, response, body) => { }, (error, response, body) => {
expect(response.statusCode).toEqual(401); expect(response.statusCode).toEqual(403);
expect(body.error).toEqual('unauthorized'); expect(body.error).toEqual('unauthorized: master key is required');
done(); done();
}); });
}); });

View File

@@ -3,6 +3,7 @@
var Parse = require('parse/node').Parse; var Parse = require('parse/node').Parse;
import PromiseRouter from '../PromiseRouter'; import PromiseRouter from '../PromiseRouter';
import * as middleware from "../middlewares";
export class GlobalConfigRouter extends PromiseRouter { export class GlobalConfigRouter extends PromiseRouter {
getGlobalConfig(req) { getGlobalConfig(req) {
@@ -18,13 +19,6 @@ export class GlobalConfigRouter extends PromiseRouter {
})); }));
} }
updateGlobalConfig(req) { updateGlobalConfig(req) {
if (!req.auth.isMaster) {
return Promise.resolve({
status: 401,
response: {error: 'unauthorized'},
});
}
return req.config.database.rawCollection('_GlobalConfig') return req.config.database.rawCollection('_GlobalConfig')
.then(coll => coll.findOneAndUpdate({ _id: 1 }, { $set: req.body })) .then(coll => coll.findOneAndUpdate({ _id: 1 }, { $set: req.body }))
.then(response => { .then(response => {
@@ -41,7 +35,7 @@ export class GlobalConfigRouter extends PromiseRouter {
mountRoutes() { mountRoutes() {
this.route('GET', '/config', req => { return this.getGlobalConfig(req) }); this.route('GET', '/config', req => { return this.getGlobalConfig(req) });
this.route('PUT', '/config', req => { return this.updateGlobalConfig(req) }); this.route('PUT', '/config', middleware.promiseEnforceMasterKeyAccess, req => { return this.updateGlobalConfig(req) });
} }
} }

View File

@@ -1,15 +1,9 @@
import { Parse } from 'parse/node'; import { Parse } from 'parse/node';
import PromiseRouter from '../PromiseRouter'; import PromiseRouter from '../PromiseRouter';
import { HooksController } from '../Controllers/HooksController'; import { HooksController } from '../Controllers/HooksController';
import * as middleware from "../middlewares";
function enforceMasterKeyAccess(req) {
if (!req.auth.isMaster) {
throw new Parse.Error(403, "unauthorized: master key is required");
}
}
export class HooksRouter extends PromiseRouter { export class HooksRouter extends PromiseRouter {
createHook(aHook, config) { createHook(aHook, config) {
return config.hooksController.createHook(aHook).then( (hook) => ({response: hook})); return config.hooksController.createHook(aHook).then( (hook) => ({response: hook}));
}; };
@@ -93,14 +87,14 @@ export class HooksRouter extends PromiseRouter {
} }
mountRoutes() { mountRoutes() {
this.route('GET', '/hooks/functions', enforceMasterKeyAccess, this.handleGetFunctions.bind(this)); this.route('GET', '/hooks/functions', middleware.promiseEnforceMasterKeyAccess, this.handleGetFunctions.bind(this));
this.route('GET', '/hooks/triggers', enforceMasterKeyAccess, this.handleGetTriggers.bind(this)); this.route('GET', '/hooks/triggers', middleware.promiseEnforceMasterKeyAccess, this.handleGetTriggers.bind(this));
this.route('GET', '/hooks/functions/:functionName', enforceMasterKeyAccess, this.handleGetFunctions.bind(this)); this.route('GET', '/hooks/functions/:functionName', middleware.promiseEnforceMasterKeyAccess, this.handleGetFunctions.bind(this));
this.route('GET', '/hooks/triggers/:className/:triggerName', enforceMasterKeyAccess, this.handleGetTriggers.bind(this)); this.route('GET', '/hooks/triggers/:className/:triggerName', middleware.promiseEnforceMasterKeyAccess, this.handleGetTriggers.bind(this));
this.route('POST', '/hooks/functions', enforceMasterKeyAccess, this.handlePost.bind(this)); this.route('POST', '/hooks/functions', middleware.promiseEnforceMasterKeyAccess, this.handlePost.bind(this));
this.route('POST', '/hooks/triggers', enforceMasterKeyAccess, this.handlePost.bind(this)); this.route('POST', '/hooks/triggers', middleware.promiseEnforceMasterKeyAccess, this.handlePost.bind(this));
this.route('PUT', '/hooks/functions/:functionName', enforceMasterKeyAccess, this.handlePut.bind(this)); this.route('PUT', '/hooks/functions/:functionName', middleware.promiseEnforceMasterKeyAccess, this.handlePut.bind(this));
this.route('PUT', '/hooks/triggers/:className/:triggerName', enforceMasterKeyAccess, this.handlePut.bind(this)); this.route('PUT', '/hooks/triggers/:className/:triggerName', middleware.promiseEnforceMasterKeyAccess, this.handlePut.bind(this));
} }
} }

View File

@@ -1,23 +1,11 @@
import { Parse } from 'parse/node'; import { Parse } from 'parse/node';
import PromiseRouter from '../PromiseRouter'; import PromiseRouter from '../PromiseRouter';
import * as middleware from "../middlewares";
// only allow request with master key
let enforceSecurity = (auth) => {
if (!auth || !auth.isMaster) {
throw new Parse.Error(
Parse.Error.OPERATION_FORBIDDEN,
'Clients aren\'t allowed to perform the ' +
'get' + ' operation on logs.'
);
}
}
export class LogsRouter extends PromiseRouter { export class LogsRouter extends PromiseRouter {
mountRoutes() { mountRoutes() {
this.route('GET','/logs', (req) => { this.route('GET','/logs', middleware.promiseEnforceMasterKeyAccess, req => { return this.handleGET(req); });
return this.handleGET(req);
});
} }
// Returns a promise for a {response} object. // Returns a promise for a {response} object.
@@ -29,31 +17,26 @@ export class LogsRouter extends PromiseRouter {
// size (optional) Number of rows returned by search. Defaults to 10 // size (optional) Number of rows returned by search. Defaults to 10
handleGET(req) { handleGET(req) {
if (!req.config || !req.config.loggerController) { if (!req.config || !req.config.loggerController) {
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED, throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED, 'Logger adapter is not available.');
'Logger adapter is not availabe');
} }
let promise = new Parse.Promise();
let from = req.query.from; let from = req.query.from;
let until = req.query.until; let until = req.query.until;
let size = req.query.size; let size = req.query.size;
let order = req.query.order let order = req.query.order
let level = req.query.level; let level = req.query.level;
enforceSecurity(req.auth);
const options = { const options = {
from, from,
until, until,
size, size,
order, order,
level, level
} };
return req.config.loggerController.getLogs(options).then((result) => { return req.config.loggerController
return Promise.resolve({ .getLogs(options)
response: result .then(result => ({ response: result }));
});
})
} }
} }