Skip to content

Commit

Permalink
Merge pull request #248 from oddnetworks/fix-identity-controllers
Browse files Browse the repository at this point in the history
Fix identity controllers
  • Loading branch information
kixxauth authored Oct 27, 2016
2 parents b7c89fc + 30b4ac6 commit 76edd72
Show file tree
Hide file tree
Showing 14 changed files with 2,408 additions and 1,994 deletions.
9 changes: 0 additions & 9 deletions lib/controllers/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,6 @@ class Controller {

getChannel(req) {
if (this.isAdminRequest(req)) {
if (this.type === 'channel') {
return Promise.resolve(null);
}

const channelId = (req.query || {}).channel || (req.body || {}).channel;

if (channelId) {
Expand All @@ -41,11 +37,6 @@ class Controller {

const channel = req.identity.channel;
if (channel) {
if (this.type === 'channel' && req.params.id && channel.id !== req.params.id) {
return Promise.reject(Boom.forbidden(
'Access to the requested channel is forbidden'
));
}
return Promise.resolve(channel);
}

Expand Down
120 changes: 120 additions & 0 deletions lib/services/identity/controllers/identity-channel-controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
'use strict';

const Promise = require('bluebird');
const _ = require('lodash');
const Boom = require('boom');

const Controller = require('../../../controllers/controller');
const IdentityItemController = require('./identity-item-controller');

class IdentityChannelController extends IdentityItemController {
get(req, res, next) {
const type = 'channel';
const id = req.params.id;
const args = {type, id};

const err = this.checkChannelAccess(req);
if (err) {
return next(err);
}

if (req.query.include) {
args.include = req.query.include.split(',');
}

return this.bus.query({role: 'store', cmd: 'get', type}, args)
.then(resource => {
if (!resource) {
return Promise.reject(Boom.notFound('resource not found'));
}

res.body = resource;
res.status(200);
next();
return null;
})
.catch(next);
}

patch(req, res, next) {
const type = 'channel';
const id = req.params.id;
const payload = req.body;
const args = {type, id};

const err = this.checkChannelAccess(req);
if (err) {
return next(err);
}

return this.bus.query({role: 'store', cmd: 'get', type}, args)
.then(resource => {
if (resource) {
resource = _.merge({}, resource, payload);
resource.type = type;
resource.id = id;

return this.bus.sendCommand({role: 'store', cmd: 'set', type}, resource);
}

return Promise.reject(Boom.notFound(`${type} "${id}" not found`));
})
.then(resource => {
res.body = resource;
res.status(200);
next();
return null;
})
.catch(next);
}

delete(req, res, next) {
const type = 'channel';
const id = req.params.id;
const args = {type, id};

const err = this.checkChannelAccess(req);
if (err) {
return next(err);
}

return this.bus.sendCommand({role: 'store', cmd: 'remove', type}, args)
.then(() => {
res.body = {};
res.status(200);
next();
return null;
})
.catch(next);
}

checkChannelAccess(req) {
if (this.isAdminRequest(req)) {
return null;
}

const channelId = _.get(req, 'identity.channel.id');
if (!channelId) {
return Boom.forbidden('Non admin callers must have a channel embedded in the JSON Web Token');
}

if (req.params.id !== channelId) {
return Boom.forbidden('Access to the requested channel is forbidden');
}

return null;
}

static create(spec) {
if (!spec.bus || !_.isObject(spec.bus)) {
throw new Error('IdentityChannelController spec.bus is required');
}

return Controller.create(new IdentityChannelController({
bus: spec.bus,
type: 'channel'
}));
}
}

module.exports = IdentityChannelController;
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
'use strict';

const Promise = require('bluebird');
const _ = require('lodash');
const Boom = require('boom');

const Controller = require('../../../controllers/controller');
const IdentityListController = require('./identity-list-controller');

class IdentityChannelsListController extends IdentityListController {
get(req, res, next) {
const type = 'channel';
const limit = parseInt(req.query.limit, 10) || 10;
const args = {type, limit};

const err = this.checkChannelAccess(req);
if (err) {
return next(err);
}

return this.bus.query({role: 'store', cmd: 'scan', type}, args)
.then(resources => {
// If this is not an admin request then we filter out all channel
// resources from the response other than the one which matches the
// one the caller is authenticated for.
if (!this.isAdminRequest(req)) {
const channelId = (req.identity.channel || {}).id;
resources = resources.filter(item => {
return item.id === channelId;
});
}

res.status(200);
res.body = resources.slice(0, limit);
next();
return null;
})
.catch(next);
}

post(req, res, next) {
const type = 'channel';
const payload = _.cloneDeep(req.body);
payload.type = type;

// If there is a client defined id, then we check the store for a
// conflict before moving on.
let existingResource;
if (payload.id) {
const args = {type, id: payload.id};
existingResource = this.bus.query({role: 'store', cmd: 'get', type}, args);
} else {
existingResource = Promise.resolve(null);
}

return existingResource
.then(resource => {
// Return a 409 error if there was a conflict in the store.
if (resource) {
return Promise.reject(Boom.conflict(`The ${type} "${payload.id}" already exists`));
}
return this.bus.sendCommand({role: 'store', cmd: 'set', type}, payload);
})
.then(resource => {
res.body = resource;
res.status(201);
next();
return null;
})
.catch(next);
}

checkChannelAccess(req) {
if (this.isAdminRequest(req)) {
return null;
}

if (!_.get(req, 'identity.channel.id')) {
return Boom.forbidden('Non admin callers must have a channel embedded in the JSON Web Token');
}

return null;
}

static create(spec) {
if (!spec.bus || !_.isObject(spec.bus)) {
throw new Error('IdentityChannelsListController spec.bus is required');
}

return Controller.create(new IdentityChannelsListController({
bus: spec.bus,
type: 'channel'
}));
}
}

module.exports = IdentityChannelsListController;
21 changes: 4 additions & 17 deletions lib/services/identity/controllers/identity-item-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,13 @@ class IdentityItemController extends Controller {
}

get(req, res, next) {
if (this.type === 'viewer' && !this.isAdminRequest(req) && req.params.id !== req.identity.viewer.id) {
return next(Boom.unauthorized('Viewer specified in JWT does not match requested viewer.'));
}

const type = this.type;
const id = req.params.id;
const args = {type, id};

return this.getChannel(req)
.then(channel => {
if (type !== 'channel') {
args.channel = channel.id;
}
args.channel = channel.id;

if (req.query.include) {
args.include = req.query.include.split(',');
Expand Down Expand Up @@ -55,20 +49,15 @@ class IdentityItemController extends Controller {

return this.getChannel(req)
.then(channel => {
if (type !== 'channel') {
args.channel = channel.id;
}
args.channel = channel.id;
return this.bus.query({role: 'store', cmd: 'get', type}, args);
})
.then(resource => {
if (resource) {
resource = _.merge({}, resource, payload);
resource.type = type;
resource.id = id;

if (type !== 'channel') {
resource.channel = args.channel;
}
resource.channel = args.channel;

return this.bus.sendCommand({role: 'store', cmd: 'set', type}, resource);
}
Expand All @@ -91,9 +80,7 @@ class IdentityItemController extends Controller {

return this.getChannel(req)
.then(channel => {
if (type !== 'channel') {
args.channel = channel.id;
}
args.channel = channel.id;
return this.bus.sendCommand({role: 'store', cmd: 'remove', type}, args);
})
.then(() => {
Expand Down
27 changes: 3 additions & 24 deletions lib/services/identity/controllers/identity-list-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,10 @@ class IdentityListController extends Controller {

return this.getChannel(req)
.then(channel => {
// If this is not a channel resource, then it must belong to a channel
// which we reference here with the .channel attribute.
if (type !== 'channel') {
args.channel = channel.id;
}

args.channel = channel.id;
return this.bus.query({role: 'store', cmd: 'scan', type}, args);
})
.then(resources => {
// If we're querying for channel resources and this is not an admin request,
// then we filter out all channel resources from the response other than the
// one which matches the one the caller is authenticated for.
if (type === 'channel' && !this.isAdminRequest(req)) {
const channelId = (req.identity.channel || {}).id;
resources = resources.filter(item => {
return item.id === channelId;
});
}

res.status(200);
res.body = resources.slice(0, limit);
next();
Expand All @@ -54,20 +39,14 @@ class IdentityListController extends Controller {

return this.getChannel(req)
.then(channel => {
// If this is not a channel resource, then it must belong to a channel
// which we reference here with the .channel attribute.
if (type !== 'channel') {
payload.channel = channel.id;
}
payload.channel = channel.id;

// If there is a client defined id, then we check the store for a
// conflict before moving on.
if (payload.id) {
const args = {type, id: payload.id};

if (type !== 'channel') {
args.channel = channel.id;
}
args.channel = channel.id;
return this.bus.query({role: 'store', cmd: 'get', type}, args);
}

Expand Down
Loading

0 comments on commit 76edd72

Please sign in to comment.