mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-31 00:16:47 +01:00
feature: Add action specific user permissions
This commit is contained in:
parent
e256db29a5
commit
df7509e381
45
lib/permissions.js
Normal file
45
lib/permissions.js
Normal file
@ -0,0 +1,45 @@
|
||||
'use strict';
|
||||
|
||||
const ADMIN = 'ADMIN';
|
||||
const REVIVE_FEATURE = 'REVIVE_FEATURE';
|
||||
const CREATE_FEATURE = 'CREATE_FEATURE';
|
||||
const UPDATE_FEATURE = 'UPDATE_FEATURE';
|
||||
const DELETE_FEATURE = 'DELETE_FEATURE';
|
||||
const CREATE_STRATEGY = 'CREATE_STRATEGY';
|
||||
const UPDATE_STRATEGY = 'UPDATE_STRATEGY';
|
||||
const DELETE_STRATEGY = 'DELETE_STRATEGY';
|
||||
const UPDATE_APPLICATION = 'UPDATE_APPLICATION';
|
||||
|
||||
function requirePerms(prms) {
|
||||
return (req, res, next) => {
|
||||
for (const permission of prms) {
|
||||
if (
|
||||
req.user &&
|
||||
req.user.permissions &&
|
||||
(req.user.permissions.indexOf(ADMIN) !== -1 ||
|
||||
req.user.permissions.indexOf(permission) !== -1)
|
||||
) {
|
||||
return next();
|
||||
}
|
||||
}
|
||||
return res
|
||||
.status(403)
|
||||
.json({
|
||||
message: 'Missing permissions to perform this action.',
|
||||
})
|
||||
.end();
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
requirePerms,
|
||||
ADMIN,
|
||||
REVIVE_FEATURE,
|
||||
CREATE_FEATURE,
|
||||
UPDATE_FEATURE,
|
||||
DELETE_FEATURE,
|
||||
CREATE_STRATEGY,
|
||||
UPDATE_STRATEGY,
|
||||
DELETE_STRATEGY,
|
||||
UPDATE_APPLICATION,
|
||||
};
|
78
lib/permissions.test.js
Normal file
78
lib/permissions.test.js
Normal file
@ -0,0 +1,78 @@
|
||||
'use strict';
|
||||
|
||||
const test = require('ava');
|
||||
const store = require('./../test/fixtures/store');
|
||||
const { requirePerms } = require('./permissions');
|
||||
const supertest = require('supertest');
|
||||
const getApp = require('./app');
|
||||
|
||||
const { EventEmitter } = require('events');
|
||||
const eventBus = new EventEmitter();
|
||||
|
||||
function getSetup(preRouterHook) {
|
||||
const base = `/random${Math.round(Math.random() * 1000)}`;
|
||||
const stores = store.createStores();
|
||||
const app = getApp({
|
||||
baseUriPath: base,
|
||||
stores,
|
||||
eventBus,
|
||||
extendedPermissions: true,
|
||||
preRouterHook(_app) {
|
||||
preRouterHook(_app);
|
||||
|
||||
_app.get(
|
||||
`${base}/protectedResource`,
|
||||
requirePerms(['READ']),
|
||||
(req, res) => {
|
||||
res.status(200)
|
||||
.json({ message: 'OK' })
|
||||
.end();
|
||||
}
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
base,
|
||||
request: supertest(app),
|
||||
};
|
||||
}
|
||||
|
||||
test('should return 403 when missing permission', t => {
|
||||
t.plan(0);
|
||||
const { base, request } = getSetup(() => {});
|
||||
|
||||
return request.get(`${base}/protectedResource`).expect(403);
|
||||
});
|
||||
|
||||
test('should allow access with correct permissions', t => {
|
||||
const { base, request } = getSetup(app => {
|
||||
app.use((req, res, next) => {
|
||||
req.user = { email: 'some@email.com', permissions: ['READ'] };
|
||||
next();
|
||||
});
|
||||
});
|
||||
|
||||
return request
|
||||
.get(`${base}/protectedResource`)
|
||||
.expect(200)
|
||||
.expect(res => {
|
||||
t.is(res.body.message, 'OK');
|
||||
});
|
||||
});
|
||||
|
||||
test('should allow access with admin permissions', t => {
|
||||
const { base, request } = getSetup(app => {
|
||||
app.use((req, res, next) => {
|
||||
req.user = { email: 'some@email.com', permissions: ['ADMIN'] };
|
||||
next();
|
||||
});
|
||||
});
|
||||
|
||||
return request
|
||||
.get(`${base}/protectedResource`)
|
||||
.expect(200)
|
||||
.expect(res => {
|
||||
t.is(res.body.message, 'OK');
|
||||
});
|
||||
});
|
@ -5,15 +5,16 @@ const Controller = require('../controller');
|
||||
const logger = require('../../logger')('/admin-api/archive.js');
|
||||
const { FEATURE_REVIVED } = require('../../event-type');
|
||||
const extractUser = require('../../extract-user');
|
||||
const { REVIVE_FEATURE } = require('../../permissions');
|
||||
|
||||
class ArchiveController extends Controller {
|
||||
constructor({ featureToggleStore, eventStore }) {
|
||||
super();
|
||||
constructor(extendedPerms, { featureToggleStore, eventStore }) {
|
||||
super(extendedPerms);
|
||||
this.featureToggleStore = featureToggleStore;
|
||||
this.eventStore = eventStore;
|
||||
|
||||
this.get('/features', this.getArchivedFeatures);
|
||||
this.post('/revive/:name', this.reviveFeatureToggle);
|
||||
this.post('/revive/:name', this.reviveFeatureToggle, REVIVE_FEATURE);
|
||||
}
|
||||
|
||||
async getArchivedFeatures(req, res) {
|
||||
|
@ -2,8 +2,10 @@
|
||||
|
||||
const test = require('ava');
|
||||
const store = require('./../../../test/fixtures/store');
|
||||
const permissions = require('../../../test/fixtures/permissions');
|
||||
const supertest = require('supertest');
|
||||
const getApp = require('../../app');
|
||||
const { REVIVE_FEATURE } = require('../../permissions');
|
||||
|
||||
const { EventEmitter } = require('events');
|
||||
const eventBus = new EventEmitter();
|
||||
@ -11,14 +13,18 @@ const eventBus = new EventEmitter();
|
||||
function getSetup() {
|
||||
const base = `/random${Math.round(Math.random() * 1000)}`;
|
||||
const stores = store.createStores();
|
||||
const perms = permissions();
|
||||
const app = getApp({
|
||||
baseUriPath: base,
|
||||
stores,
|
||||
eventBus,
|
||||
extendedPermissions: true,
|
||||
preRouterHook: perms.hook,
|
||||
});
|
||||
|
||||
return {
|
||||
base,
|
||||
perms,
|
||||
archiveStore: stores.featureToggleStore,
|
||||
eventStore: stores.eventStore,
|
||||
request: supertest(app),
|
||||
@ -60,7 +66,8 @@ test('should get archived toggles via admin', t => {
|
||||
test('should revive toggle', t => {
|
||||
t.plan(0);
|
||||
const name = 'name1';
|
||||
const { request, base, archiveStore } = getSetup();
|
||||
const { request, base, archiveStore, perms } = getSetup();
|
||||
perms.withPerms(REVIVE_FEATURE);
|
||||
archiveStore.addArchivedFeature({
|
||||
name,
|
||||
strategies: [{ name: 'default' }],
|
||||
@ -72,7 +79,8 @@ test('should revive toggle', t => {
|
||||
test('should create event when reviving toggle', async t => {
|
||||
t.plan(4);
|
||||
const name = 'name1';
|
||||
const { request, base, archiveStore, eventStore } = getSetup();
|
||||
const { request, base, archiveStore, eventStore, perms } = getSetup();
|
||||
perms.withPerms(REVIVE_FEATURE);
|
||||
archiveStore.addArchivedFeature({
|
||||
name,
|
||||
strategies: [{ name: 'default' }],
|
||||
|
@ -11,22 +11,27 @@ const {
|
||||
const NameExistsError = require('../../error/name-exists-error');
|
||||
const { handleErrors } = require('./util');
|
||||
const extractUser = require('../../extract-user');
|
||||
const {
|
||||
UPDATE_FEATURE,
|
||||
DELETE_FEATURE,
|
||||
CREATE_FEATURE,
|
||||
} = require('../../permissions');
|
||||
const { featureShema, nameSchema } = require('./feature-schema');
|
||||
const version = 1;
|
||||
|
||||
class FeatureController extends Controller {
|
||||
constructor({ featureToggleStore, eventStore }) {
|
||||
super();
|
||||
constructor(extendedPerms, { featureToggleStore, eventStore }) {
|
||||
super(extendedPerms);
|
||||
this.featureToggleStore = featureToggleStore;
|
||||
this.eventStore = eventStore;
|
||||
|
||||
this.get('/', this.getAllToggles);
|
||||
this.post('/', this.createToggle);
|
||||
this.post('/', this.createToggle, CREATE_FEATURE);
|
||||
this.get('/:featureName', this.getToggle);
|
||||
this.put('/:featureName', this.updateToggle);
|
||||
this.delete('/:featureName', this.deleteToggle);
|
||||
this.put('/:featureName', this.updateToggle, UPDATE_FEATURE);
|
||||
this.delete('/:featureName', this.deleteToggle, DELETE_FEATURE);
|
||||
this.post('/validate', this.validate);
|
||||
this.post('/:featureName/toggle', this.toggle);
|
||||
this.post('/:featureName/toggle', this.toggle, UPDATE_FEATURE);
|
||||
}
|
||||
|
||||
async getAllToggles(req, res) {
|
||||
|
@ -2,8 +2,10 @@
|
||||
|
||||
const test = require('ava');
|
||||
const store = require('./../../../test/fixtures/store');
|
||||
const permissions = require('../../../test/fixtures/permissions');
|
||||
const supertest = require('supertest');
|
||||
const getApp = require('../../app');
|
||||
const { UPDATE_FEATURE, CREATE_FEATURE } = require('../../permissions');
|
||||
|
||||
const { EventEmitter } = require('events');
|
||||
const eventBus = new EventEmitter();
|
||||
@ -11,14 +13,18 @@ const eventBus = new EventEmitter();
|
||||
function getSetup() {
|
||||
const base = `/random${Math.round(Math.random() * 1000)}`;
|
||||
const stores = store.createStores();
|
||||
const perms = permissions();
|
||||
const app = getApp({
|
||||
baseUriPath: base,
|
||||
stores,
|
||||
eventBus,
|
||||
extendedPermissions: true,
|
||||
preRouterHook: perms.hook,
|
||||
});
|
||||
|
||||
return {
|
||||
base,
|
||||
perms,
|
||||
featureToggleStore: stores.featureToggleStore,
|
||||
request: supertest(app),
|
||||
};
|
||||
@ -72,7 +78,8 @@ test('should add version numbers for /features', t => {
|
||||
|
||||
test('should require at least one strategy when creating a feature toggle', t => {
|
||||
t.plan(0);
|
||||
const { request, base } = getSetup();
|
||||
const { request, base, perms } = getSetup();
|
||||
perms.withPerms(CREATE_FEATURE);
|
||||
|
||||
return request
|
||||
.post(`${base}/api/admin/features`)
|
||||
@ -83,7 +90,8 @@ test('should require at least one strategy when creating a feature toggle', t =>
|
||||
|
||||
test('should be allowed to use new toggle name', t => {
|
||||
t.plan(0);
|
||||
const { request, base } = getSetup();
|
||||
const { request, base, perms } = getSetup();
|
||||
perms.withPerms(CREATE_FEATURE);
|
||||
|
||||
return request
|
||||
.post(`${base}/api/admin/features/validate`)
|
||||
@ -136,7 +144,8 @@ test('should not be allowed to reuse archived toggle name', t => {
|
||||
|
||||
test('should require at least one strategy when updating a feature toggle', t => {
|
||||
t.plan(0);
|
||||
const { request, featureToggleStore, base } = getSetup();
|
||||
const { request, featureToggleStore, base, perms } = getSetup();
|
||||
perms.withPerms(UPDATE_FEATURE);
|
||||
featureToggleStore.addFeature({
|
||||
name: 'ts',
|
||||
strategies: [{ name: 'default' }],
|
||||
@ -151,7 +160,8 @@ test('should require at least one strategy when updating a feature toggle', t =>
|
||||
|
||||
test('valid feature names should pass validation', t => {
|
||||
t.plan(0);
|
||||
const { request, base } = getSetup();
|
||||
const { request, base, perms } = getSetup();
|
||||
perms.withPerms(CREATE_FEATURE);
|
||||
|
||||
const validNames = [
|
||||
'com.example',
|
||||
@ -179,7 +189,8 @@ test('valid feature names should pass validation', t => {
|
||||
|
||||
test('invalid feature names should not pass validation', t => {
|
||||
t.plan(0);
|
||||
const { request, base } = getSetup();
|
||||
const { request, base, perms } = getSetup();
|
||||
perms.withPerms(CREATE_FEATURE);
|
||||
|
||||
const invalidNames = [
|
||||
'some example',
|
||||
@ -207,7 +218,8 @@ test('invalid feature names should not pass validation', t => {
|
||||
// Make sure current UI works. Should align on joi errors in future.
|
||||
test('invalid feature names should have error msg', t => {
|
||||
t.plan(1);
|
||||
const { request, base } = getSetup();
|
||||
const { request, base, perms } = getSetup();
|
||||
perms.withPerms(CREATE_FEATURE);
|
||||
|
||||
const name = 'ØÆ`';
|
||||
|
||||
|
@ -14,13 +14,17 @@ class AdminApi extends Controller {
|
||||
super();
|
||||
|
||||
const stores = config.stores;
|
||||
const perms = config.extendedPermissions;
|
||||
|
||||
this.app.get('/', this.index);
|
||||
this.app.use('/features', new FeatureController(stores).router);
|
||||
this.app.use('/archive', new ArchiveController(stores).router);
|
||||
this.app.use('/strategies', new StrategyController(stores).router);
|
||||
this.app.use('/features', new FeatureController(perms, stores).router);
|
||||
this.app.use('/archive', new ArchiveController(perms, stores).router);
|
||||
this.app.use(
|
||||
'/strategies',
|
||||
new StrategyController(perms, stores).router
|
||||
);
|
||||
this.app.use('/events', new EventController(stores).router);
|
||||
this.app.use('/metrics', new MetricsController(stores).router);
|
||||
this.app.use('/metrics', new MetricsController(perms, stores).router);
|
||||
this.app.use('/user', new UserController().router);
|
||||
}
|
||||
|
||||
|
22
lib/routes/admin-api/metrics-schema.js
Normal file
22
lib/routes/admin-api/metrics-schema.js
Normal file
@ -0,0 +1,22 @@
|
||||
'use strict';
|
||||
|
||||
const joi = require('joi');
|
||||
const { nameType } = require('./util');
|
||||
|
||||
const applicationSchema = joi
|
||||
.object()
|
||||
.options({ stripUnknown: false })
|
||||
.keys({
|
||||
appName: nameType,
|
||||
sdkVersion: joi.string().optional(),
|
||||
strategies: joi
|
||||
.array()
|
||||
.required()
|
||||
.items(joi.string(), joi.any().strip()),
|
||||
description: joi.string().optional(),
|
||||
url: joi.string().optional(),
|
||||
color: joi.string().optional(),
|
||||
icon: joi.string().optional(),
|
||||
});
|
||||
|
||||
module.exports = applicationSchema;
|
@ -1,18 +1,24 @@
|
||||
'use strict';
|
||||
|
||||
const joi = require('joi');
|
||||
const Controller = require('../controller');
|
||||
const logger = require('../../logger')('/admin-api/metrics.js');
|
||||
const ClientMetrics = require('../../client-metrics');
|
||||
const schema = require('./metrics-schema');
|
||||
const { UPDATE_APPLICATION } = require('../../permissions');
|
||||
|
||||
class MetricsController extends Controller {
|
||||
constructor({
|
||||
clientMetricsStore,
|
||||
clientInstanceStore,
|
||||
clientApplicationsStore,
|
||||
strategyStore,
|
||||
featureToggleStore,
|
||||
}) {
|
||||
super();
|
||||
constructor(
|
||||
extendedPerms,
|
||||
{
|
||||
clientMetricsStore,
|
||||
clientInstanceStore,
|
||||
clientApplicationsStore,
|
||||
strategyStore,
|
||||
featureToggleStore,
|
||||
}
|
||||
) {
|
||||
super(extendedPerms);
|
||||
this.metrics = new ClientMetrics(clientMetricsStore);
|
||||
this.clientInstanceStore = clientInstanceStore;
|
||||
this.clientApplicationsStore = clientApplicationsStore;
|
||||
@ -23,7 +29,11 @@ class MetricsController extends Controller {
|
||||
this.get('/seen-apps', this.getSeenApps);
|
||||
this.get('/feature-toggles', this.getFeatureToggles);
|
||||
this.get('/feature-toggles/:name', this.getFeatureToggle);
|
||||
this.post('/applications/:appName', this.createApplication);
|
||||
this.post(
|
||||
'/applications/:appName',
|
||||
this.createApplication,
|
||||
UPDATE_APPLICATION
|
||||
);
|
||||
this.get('/applications/', this.getApplications);
|
||||
this.get('/applications/:appName', this.getApplication);
|
||||
}
|
||||
@ -67,14 +77,19 @@ class MetricsController extends Controller {
|
||||
});
|
||||
}
|
||||
|
||||
// Todo: add joi-schema validation
|
||||
async createApplication(req, res) {
|
||||
const input = Object.assign({}, req.body, {
|
||||
appName: req.params.appName,
|
||||
});
|
||||
const { value: applicationData, error } = joi.validate(input, schema);
|
||||
|
||||
if (error) {
|
||||
logger.warn('Invalid application data posted', error);
|
||||
return res.status(400).json(error);
|
||||
}
|
||||
|
||||
try {
|
||||
await this.clientApplicationsStore.upsert(input);
|
||||
await this.clientApplicationsStore.upsert(applicationData);
|
||||
res.status(202).end();
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
|
@ -2,23 +2,29 @@
|
||||
|
||||
const test = require('ava');
|
||||
const store = require('./../../../test/fixtures/store');
|
||||
const permissions = require('../../../test/fixtures/permissions');
|
||||
const supertest = require('supertest');
|
||||
const getApp = require('../../app');
|
||||
const { UPDATE_APPLICATION } = require('../../permissions');
|
||||
|
||||
const { EventEmitter } = require('events');
|
||||
const eventBus = new EventEmitter();
|
||||
|
||||
function getSetup() {
|
||||
const stores = store.createStores();
|
||||
const perms = permissions();
|
||||
const app = getApp({
|
||||
baseUriPath: '',
|
||||
stores,
|
||||
eventBus,
|
||||
extendedPermissions: true,
|
||||
preRouterHook: perms.hook,
|
||||
});
|
||||
|
||||
return {
|
||||
request: supertest(app),
|
||||
stores,
|
||||
perms,
|
||||
};
|
||||
}
|
||||
|
||||
@ -125,11 +131,12 @@ test('should return applications', t => {
|
||||
|
||||
test('should store application', t => {
|
||||
t.plan(0);
|
||||
const { request } = getSetup();
|
||||
const { request, perms } = getSetup();
|
||||
const appName = '123!23';
|
||||
perms.withPerms(UPDATE_APPLICATION);
|
||||
|
||||
return request
|
||||
.post(`/api/admin/metrics/applications/${appName}`)
|
||||
.send({ appName })
|
||||
.send({ appName, strategies: ['default'] })
|
||||
.expect(202);
|
||||
});
|
||||
|
@ -8,19 +8,24 @@ const NameExistsError = require('../../error/name-exists-error');
|
||||
const extractUser = require('../../extract-user');
|
||||
const strategySchema = require('./strategy-schema');
|
||||
const { handleErrors } = require('./util');
|
||||
const {
|
||||
DELETE_STRATEGY,
|
||||
CREATE_STRATEGY,
|
||||
UPDATE_STRATEGY,
|
||||
} = require('../../permissions');
|
||||
const version = 1;
|
||||
|
||||
class StrategyController extends Controller {
|
||||
constructor({ strategyStore, eventStore }) {
|
||||
super();
|
||||
constructor(extendedPerms, { strategyStore, eventStore }) {
|
||||
super(extendedPerms);
|
||||
this.strategyStore = strategyStore;
|
||||
this.eventStore = eventStore;
|
||||
|
||||
this.get('/', this.getAllStratgies);
|
||||
this.get('/:name', this.getStrategy);
|
||||
this.delete('/:name', this.removeStrategy);
|
||||
this.post('/', this.createStrategy);
|
||||
this.put('/:strategyName', this.updateStrategy);
|
||||
this.delete('/:name', this.removeStrategy, DELETE_STRATEGY);
|
||||
this.post('/', this.createStrategy, CREATE_STRATEGY);
|
||||
this.put('/:strategyName', this.updateStrategy, UPDATE_STRATEGY);
|
||||
}
|
||||
|
||||
async getAllStratgies(req, res) {
|
||||
|
@ -2,25 +2,35 @@
|
||||
|
||||
const test = require('ava');
|
||||
const store = require('./../../../test/fixtures/store');
|
||||
const permissions = require('../../../test/fixtures/permissions');
|
||||
const supertest = require('supertest');
|
||||
const getApp = require('../../app');
|
||||
const {
|
||||
DELETE_STRATEGY,
|
||||
CREATE_STRATEGY,
|
||||
UPDATE_STRATEGY,
|
||||
} = require('../../permissions');
|
||||
|
||||
const { EventEmitter } = require('events');
|
||||
const eventBus = new EventEmitter();
|
||||
|
||||
function getSetup() {
|
||||
const base = `/random${Math.round(Math.random() * 1000)}`;
|
||||
const perms = permissions();
|
||||
const stores = store.createStores();
|
||||
const app = getApp({
|
||||
baseUriPath: base,
|
||||
stores,
|
||||
eventBus,
|
||||
extendedPermissions: true,
|
||||
preRouterHook: perms.hook,
|
||||
});
|
||||
|
||||
return {
|
||||
base,
|
||||
strategyStore: stores.strategyStore,
|
||||
request: supertest(app),
|
||||
perms,
|
||||
};
|
||||
}
|
||||
|
||||
@ -39,7 +49,8 @@ test('add version numbers for /stategies', t => {
|
||||
|
||||
test('require a name when creating a new stratey', t => {
|
||||
t.plan(1);
|
||||
const { request, base } = getSetup();
|
||||
const { request, base, perms } = getSetup();
|
||||
perms.withPerms(CREATE_STRATEGY);
|
||||
|
||||
return request
|
||||
.post(`${base}/api/admin/strategies`)
|
||||
@ -52,7 +63,8 @@ test('require a name when creating a new stratey', t => {
|
||||
|
||||
test('require parameters array when creating a new stratey', t => {
|
||||
t.plan(1);
|
||||
const { request, base } = getSetup();
|
||||
const { request, base, perms } = getSetup();
|
||||
perms.withPerms(CREATE_STRATEGY);
|
||||
|
||||
return request
|
||||
.post(`${base}/api/admin/strategies`)
|
||||
@ -65,7 +77,8 @@ test('require parameters array when creating a new stratey', t => {
|
||||
|
||||
test('create a new stratey with empty parameters', t => {
|
||||
t.plan(0);
|
||||
const { request, base } = getSetup();
|
||||
const { request, base, perms } = getSetup();
|
||||
perms.withPerms(CREATE_STRATEGY);
|
||||
|
||||
return request
|
||||
.post(`${base}/api/admin/strategies`)
|
||||
@ -75,7 +88,8 @@ test('create a new stratey with empty parameters', t => {
|
||||
|
||||
test('not be possible to override name', t => {
|
||||
t.plan(0);
|
||||
const { request, base, strategyStore } = getSetup();
|
||||
const { request, base, strategyStore, perms } = getSetup();
|
||||
perms.withPerms(CREATE_STRATEGY);
|
||||
strategyStore.addStrategy({ name: 'Testing', parameters: [] });
|
||||
|
||||
return request
|
||||
@ -87,7 +101,8 @@ test('not be possible to override name', t => {
|
||||
test('update strategy', t => {
|
||||
t.plan(0);
|
||||
const name = 'AnotherStrat';
|
||||
const { request, base, strategyStore } = getSetup();
|
||||
const { request, base, strategyStore, perms } = getSetup();
|
||||
perms.withPerms(UPDATE_STRATEGY);
|
||||
strategyStore.addStrategy({ name, parameters: [] });
|
||||
|
||||
return request
|
||||
@ -96,10 +111,11 @@ test('update strategy', t => {
|
||||
.expect(200);
|
||||
});
|
||||
|
||||
test('not update uknown strategy', t => {
|
||||
test('not update unknown strategy', t => {
|
||||
t.plan(0);
|
||||
const name = 'UnknownStrat';
|
||||
const { request, base } = getSetup();
|
||||
const { request, base, perms } = getSetup();
|
||||
perms.withPerms(UPDATE_STRATEGY);
|
||||
|
||||
return request
|
||||
.put(`${base}/api/admin/strategies/${name}`)
|
||||
@ -110,7 +126,8 @@ test('not update uknown strategy', t => {
|
||||
test('validate format when updating strategy', t => {
|
||||
t.plan(0);
|
||||
const name = 'AnotherStrat';
|
||||
const { request, base, strategyStore } = getSetup();
|
||||
const { request, base, strategyStore, perms } = getSetup();
|
||||
perms.withPerms(UPDATE_STRATEGY);
|
||||
strategyStore.addStrategy({ name, parameters: [] });
|
||||
|
||||
return request
|
||||
@ -122,7 +139,8 @@ test('validate format when updating strategy', t => {
|
||||
test('editable=false will stop delete request', t => {
|
||||
t.plan(0);
|
||||
const name = 'default';
|
||||
const { request, base } = getSetup();
|
||||
const { request, base, perms } = getSetup();
|
||||
perms.withPerms(DELETE_STRATEGY);
|
||||
|
||||
return request.delete(`${base}/api/admin/strategies/${name}`).expect(500);
|
||||
});
|
||||
@ -130,7 +148,8 @@ test('editable=false will stop delete request', t => {
|
||||
test('editable=false will stop edit request', t => {
|
||||
t.plan(0);
|
||||
const name = 'default';
|
||||
const { request, base } = getSetup();
|
||||
const { request, base, perms } = getSetup();
|
||||
perms.withPerms(UPDATE_STRATEGY);
|
||||
|
||||
return request
|
||||
.put(`${base}/api/admin/strategies/${name}`)
|
||||
@ -141,7 +160,8 @@ test('editable=false will stop edit request', t => {
|
||||
test('editable=true will allow delete request', t => {
|
||||
t.plan(0);
|
||||
const name = 'deleteStrat';
|
||||
const { request, base, strategyStore } = getSetup();
|
||||
const { request, base, strategyStore, perms } = getSetup();
|
||||
perms.withPerms(DELETE_STRATEGY);
|
||||
strategyStore.addStrategy({ name, parameters: [] });
|
||||
|
||||
return request
|
||||
@ -153,7 +173,8 @@ test('editable=true will allow delete request', t => {
|
||||
test('editable=true will allow edit request', t => {
|
||||
t.plan(0);
|
||||
const name = 'editStrat';
|
||||
const { request, base, strategyStore } = getSetup();
|
||||
const { request, base, strategyStore, perms } = getSetup();
|
||||
perms.withPerms(UPDATE_STRATEGY);
|
||||
strategyStore.addStrategy({ name, parameters: [] });
|
||||
|
||||
return request
|
||||
|
@ -1,28 +1,42 @@
|
||||
'use strict';
|
||||
|
||||
const { Router } = require('express');
|
||||
const { requirePerms } = require('./../permissions');
|
||||
/**
|
||||
* Base class for Controllers to standardize binding to express Router.
|
||||
*/
|
||||
class Controller {
|
||||
constructor() {
|
||||
constructor(extendedPerms) {
|
||||
const router = Router();
|
||||
this.app = router;
|
||||
this.extendedPerms = extendedPerms;
|
||||
}
|
||||
|
||||
get(path, handler) {
|
||||
get(path, handler, ...perms) {
|
||||
if (this.extendedPerms && perms.length > 0) {
|
||||
this.app.get(path, requirePerms(perms), handler.bind(this));
|
||||
}
|
||||
this.app.get(path, handler.bind(this));
|
||||
}
|
||||
|
||||
post(path, handler) {
|
||||
post(path, handler, ...perms) {
|
||||
if (this.extendedPerms && perms.length > 0) {
|
||||
this.app.post(path, requirePerms(perms), handler.bind(this));
|
||||
}
|
||||
this.app.post(path, handler.bind(this));
|
||||
}
|
||||
|
||||
put(path, handler) {
|
||||
put(path, handler, ...perms) {
|
||||
if (this.extendedPerms && perms.length > 0) {
|
||||
this.app.put(path, requirePerms(perms), handler.bind(this));
|
||||
}
|
||||
this.app.put(path, handler.bind(this));
|
||||
}
|
||||
|
||||
delete(path, handler) {
|
||||
delete(path, handler, ...perms) {
|
||||
if (this.extendedPerms && perms.length > 0) {
|
||||
this.app.delete(path, requirePerms(perms), handler.bind(this));
|
||||
}
|
||||
this.app.delete(path, handler.bind(this));
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,7 @@ const gravatar = require('gravatar');
|
||||
const Joi = require('joi');
|
||||
|
||||
module.exports = class User {
|
||||
constructor({ name, email, imageUrl } = {}) {
|
||||
constructor({ name, email, permissions, imageUrl } = {}) {
|
||||
Joi.assert(
|
||||
email,
|
||||
Joi.string()
|
||||
@ -14,6 +14,7 @@ module.exports = class User {
|
||||
);
|
||||
this.email = email;
|
||||
this.name = name;
|
||||
this.permissions = permissions;
|
||||
this.imageUrl =
|
||||
imageUrl || gravatar.url(email, { s: '42', d: 'retro' });
|
||||
}
|
||||
|
17
test/fixtures/permissions.js
vendored
Normal file
17
test/fixtures/permissions.js
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = () => {
|
||||
let _perms = [];
|
||||
return {
|
||||
hook(app) {
|
||||
app.use((req, res, next) => {
|
||||
if (req.user) req.user.permissions = _perms;
|
||||
else req.user = { email: 'unknown', permissions: _perms };
|
||||
next();
|
||||
});
|
||||
},
|
||||
withPerms(...prms) {
|
||||
_perms = prms;
|
||||
},
|
||||
};
|
||||
};
|
Loading…
Reference in New Issue
Block a user