Skip to content

Commit

Permalink
feat(general): fetch user permissions
Browse files Browse the repository at this point in the history
  • Loading branch information
serge1peshcoff committed Mar 7, 2020
1 parent 0f38339 commit f74bc8e
Show file tree
Hide file tree
Showing 6 changed files with 265 additions and 6 deletions.
48 changes: 48 additions & 0 deletions lib/permissions-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,54 @@ class PermissionsManager {

this.addPermissions(permissions);
}

async fetchUserPermissions(user) {
// 1. Fetch the user's bodies and permission the current user has within them:
// - fetch user bodies
// - find all the circles inside these bodies
// - find all the circle memberships for this circles for the currert user
// - traverse them recursively
const bodyIds = user.bodies.map((body) => body.id);
const bodyCirclesIds = this.circles
.filter((circle) => bodyIds.includes(circle.body_id))
.map((circle) => circle.id);
const bodyCircleMemberships = await CircleMembership.findAll({
where: { user_id: this.user.id, circle_id: { [Sequelize.Op.in]: bodyCirclesIds } }
});
const bodyCirclesArray = helpers.traverseIndirectCircles(this.circlesMap, bodyCircleMemberships.map((membership) => membership.circle_id));
const bodyPermissions = await Permission.findAll({
where: {
'$circle_permissions.circle_id$': { [Sequelize.Op.in]: bodyCirclesArray },
scope: 'local'
},
include: [CirclePermission]
});

this.addPermissions(bodyPermissions);

// 2. Fetch the user's join requests and permission the current user has within them:
// - fetch user join requests and their bodies
// - find all the circles inside these bodies
// - find all the circle memberships for this circles for the currert user
// - traverse them recursively
const joinRequestIds = user.join_requests.map((request) => request.body_id);
const joinRequestCircleIds = this.circles
.filter((circle) => joinRequestIds.includes(circle.body_id))
.map((circle) => circle.id);
const joinRequestsCircleMemberships = await CircleMembership.findAll({
where: { user_id: this.user.id, circle_id: { [Sequelize.Op.in]: joinRequestCircleIds } }
});
const joinRequestsArray = helpers.traverseIndirectCircles(this.circlesMap, joinRequestsCircleMemberships.map((membership) => membership.circle_id));
const joinRequestPermissions = await Permission.findAll({
where: {
'$circle_permissions.circle_id$': { [Sequelize.Op.in]: joinRequestsArray },
scope: 'join_request'
},
include: [CirclePermission]
});

this.addPermissions(joinRequestPermissions);
}
}

module.exports = PermissionsManager;
1 change: 1 addition & 0 deletions lib/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ GeneralRouter.post('/campaigns', middlewares.ensureAuthorized, campaigns.createC

// Everything related to a specific (maybe logged in) user. Auth only.
MemberRouter.use(middlewares.maybeAuthorize, middlewares.ensureAuthorized, fetch.fetchUser);
MemberRouter.get('/my_permissions', myPermissions.getMyPermissions);
MemberRouter.put('/active', members.setUserActive);
MemberRouter.get('/', members.getUser);
MemberRouter.put('/', members.updateUser);
Expand Down
12 changes: 9 additions & 3 deletions middlewares/fetch.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,20 @@ exports.fetchUser = async (req, res, next) => {
where = { id: Number(req.params.user_id) };
}

const user = await User.findOne({ where });
const user = await User.findOne({
where,
include: [
{ model: Body, as: 'bodies' },
{ model: BodyMembership, as: 'body_memberships' },
{ model: JoinRequest, as: 'join_requests' }
]
});
if (!user) {
return errors.makeNotFoundError(res, 'User is not found.');
}

req.currentUser = user;

// TODO: fetch permissions
await req.permissions.fetchUserPermissions(user);

return next();
};
Expand Down
2 changes: 1 addition & 1 deletion middlewares/generic.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ exports.maybeAuthorize = async (req, res, next) => {

req.user = accessToken.user;

const circles = await Circle.findAll({ fields: ['id', 'parent_circle_id'] });
const circles = await Circle.findAll({ fields: ['id', 'parent_circle_id', 'body_id'] });

req.permissions = new PermissionManager({ user: req.user });
req.permissions.addCircles(circles);
Expand Down
4 changes: 2 additions & 2 deletions models/Permission.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ const Permission = sequelize.define('permission', {
validate: {
notEmpty: { msg: 'Scope should be set.' },
isIn: {
args: [['global', 'local']],
msg: 'Permission scope should be one of these: "global", "local".'
args: [['global', 'local', 'join_request']],
msg: 'Permission scope should be one of these: "global", "local", "join_request.'
}
},
unique: { args: true, msg: 'There\'s already a permission with such scope, action and object.' }
Expand Down
204 changes: 204 additions & 0 deletions test/api/my-permissions-user.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
const { startServer, stopServer } = require('../../lib/server.js');
const { request } = require('../scripts/helpers');
const generator = require('../scripts/generator');

describe('My permissions for user', () => {
beforeAll(async () => {
await startServer();
});

afterAll(async () => {
await stopServer();
});

afterEach(async () => {
await generator.clearAll();
});

test('should list the global permission that is assigned to a circle user is member of', async () => {
const user = await generator.createUser();
const token = await generator.createAccessToken({}, user);

const permission = await generator.createPermission({ scope: 'global', action: 'action', object: 'object' });
const circle = await generator.createCircle();
await generator.createCirclePermission(circle, permission);
await generator.createCircleMembership(circle, user);

const otherUser = await generator.createUser();

const res = await request({
uri: '/members/' + otherUser.id + '/my_permissions',
method: 'GET',
headers: { 'X-Auth-Token': token.value }
});

expect(res.statusCode).toEqual(200);
expect(res.body.success).toEqual(true);
expect(res.body).not.toHaveProperty('errors');
expect(res.body).toHaveProperty('data');
expect(res.body.data.length).toEqual(1);
expect(res.body.data[0].combined).toEqual('global:action:object');
});

test('should not list the local permissions that is assigned to a circle user is member of', async () => {
const user = await generator.createUser();
const token = await generator.createAccessToken({}, user);

const permission = await generator.createPermission({ scope: 'local', action: 'action', object: 'object' });
const circle = await generator.createCircle();
await generator.createCirclePermission(circle, permission);
await generator.createCircleMembership(circle, user);

const otherUser = await generator.createUser();

const res = await request({
uri: '/members/' + otherUser.id + '/my_permissions',
method: 'GET',
headers: { 'X-Auth-Token': token.value }
});

expect(res.statusCode).toEqual(200);
expect(res.body.success).toEqual(true);
expect(res.body).not.toHaveProperty('errors');
expect(res.body).toHaveProperty('data');
expect(res.body.data.length).toEqual(0);
});

test('should list the global permission that is assigned to a circle user is indirectly a member of', async () => {
const user = await generator.createUser();
const token = await generator.createAccessToken({}, user);

const permission = await generator.createPermission({ scope: 'global', action: 'action', object: 'object' });
const firstCircle = await generator.createCircle();
const secondCircle = await generator.createCircle({ parent_circle_id: firstCircle.id });
const thirdCircle = await generator.createCircle({ parent_circle_id: secondCircle.id });

await generator.createCirclePermission(firstCircle, permission);
await generator.createCircleMembership(thirdCircle, user);

const otherUser = await generator.createUser();

const res = await request({
uri: '/members/' + otherUser.id + '/my_permissions',
method: 'GET',
headers: { 'X-Auth-Token': token.value }
});

expect(res.statusCode).toEqual(200);
expect(res.body.success).toEqual(true);
expect(res.body).not.toHaveProperty('errors');
expect(res.body).toHaveProperty('data');
expect(res.body.data.length).toEqual(1);
expect(res.body.data[0].combined).toEqual('global:action:object');
});

test('should not list the global permission that is assigned to a circle user is not a member of', async () => {
const user = await generator.createUser();
const token = await generator.createAccessToken({}, user);

const permission = await generator.createPermission({ scope: 'global', action: 'action', object: 'object' });
const circle = await generator.createCircle();
await generator.createCirclePermission(circle, permission);

const otherUser = await generator.createUser();

const res = await request({
uri: '/members/' + otherUser.id + '/my_permissions',
method: 'GET',
headers: { 'X-Auth-Token': token.value }
});

expect(res.statusCode).toEqual(200);
expect(res.body.success).toEqual(true);
expect(res.body).not.toHaveProperty('errors');
expect(res.body).toHaveProperty('data');
expect(res.body.data.length).toEqual(0);
});

test('should list a local permission for a body for direct circle', async () => {
const user = await generator.createUser();
const token = await generator.createAccessToken({}, user);

const permission = await generator.createPermission({ scope: 'local', action: 'action', object: 'object' });
const body = await generator.createBody();
const circle = await generator.createCircle({ body_id: body.id });
const otherUser = await generator.createUser();

await generator.createCirclePermission(circle, permission);
await generator.createBodyMembership(body, user);
await generator.createBodyMembership(body, otherUser);
await generator.createCircleMembership(circle, user);

const res = await request({
uri: '/members/' + otherUser.id + '/my_permissions',
method: 'GET',
headers: { 'X-Auth-Token': token.value }
});

expect(res.statusCode).toEqual(200);
expect(res.body.success).toEqual(true);
expect(res.body).not.toHaveProperty('errors');
expect(res.body).toHaveProperty('data');
expect(res.body.data.length).toEqual(1);
expect(res.body.data[0].combined).toEqual('local:action:object');
});

test('should list a local permission for a body for a indirect circle', async () => {
const user = await generator.createUser();
const token = await generator.createAccessToken({}, user);

const body = await generator.createBody();
const otherUser = await generator.createUser();

const permission = await generator.createPermission({ scope: 'local', action: 'action', object: 'object' });
const firstCircle = await generator.createCircle();
const secondCircle = await generator.createCircle({ parent_circle_id: firstCircle.id });
const thirdCircle = await generator.createCircle({ parent_circle_id: secondCircle.id, body_id: body.id });

await generator.createCirclePermission(firstCircle, permission);
await generator.createCircleMembership(thirdCircle, user);
await generator.createBodyMembership(body, user);
await generator.createBodyMembership(body, otherUser);

const res = await request({
uri: '/members/' + otherUser.id + '/my_permissions',
method: 'GET',
headers: { 'X-Auth-Token': token.value }
});

expect(res.statusCode).toEqual(200);
expect(res.body.success).toEqual(true);
expect(res.body).not.toHaveProperty('errors');
expect(res.body).toHaveProperty('data');
expect(res.body.data.length).toEqual(1);
expect(res.body.data[0].combined).toEqual('local:action:object');
});

test('should list a join_request permission for a body for direct circle', async () => {
const user = await generator.createUser();
const token = await generator.createAccessToken({}, user);

const permission = await generator.createPermission({ scope: 'join_request', action: 'action', object: 'object' });
const body = await generator.createBody();
const circle = await generator.createCircle({ body_id: body.id });
const otherUser = await generator.createUser();

await generator.createCirclePermission(circle, permission);
await generator.createBodyMembership(body, user);
await generator.createJoinRequest(body, otherUser);
await generator.createCircleMembership(circle, user);

const res = await request({
uri: '/members/' + otherUser.id + '/my_permissions',
method: 'GET',
headers: { 'X-Auth-Token': token.value }
});

expect(res.statusCode).toEqual(200);
expect(res.body.success).toEqual(true);
expect(res.body).not.toHaveProperty('errors');
expect(res.body).toHaveProperty('data');
expect(res.body.data.length).toEqual(1);
expect(res.body.data[0].combined).toEqual('join_request:action:object');
});
});

0 comments on commit f74bc8e

Please sign in to comment.