Skip to content

Commit

Permalink
Merge pull request RocketChat#429 from assistify/port/setting-based-p…
Browse files Browse the repository at this point in the history
…ermissions

Port: Setting based permissions
  • Loading branch information
ruKurz authored Sep 4, 2018
2 parents 272deec + 2209f4d commit bbb988d
Show file tree
Hide file tree
Showing 24 changed files with 1,467 additions and 117 deletions.
1 change: 1 addition & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ jobs:
- run:
name: Run Tests
command: |
if [[ $DISABLE_SMARTI ]]; then rm -rf ./tests/end-to-end/ui_smarti; fi;
for i in $(seq 1 5); do npm test && s=0 && break || s=$? && sleep 1; done; (exit $s)
- store_artifacts:
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/rocketchat-authorization/client/startup.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ RocketChat.AdminBox.addOption({
i18nLabel: 'Permissions',
icon: 'lock',
permissionGranted() {
return RocketChat.authz.hasAllPermission('access-permissions');
return RocketChat.authz.hasAtLeastOnePermission(['access-permissions', 'access-setting-permissions']);
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
font-weight: bold !important;
}

& .section:not(.section-collapsed) {
inline-size: fit-content;
}

& .permission-grid {
& th {
position: relative;
Expand Down
109 changes: 75 additions & 34 deletions packages/rocketchat-authorization/client/views/permissions.html
Original file line number Diff line number Diff line change
@@ -1,36 +1,77 @@
<template name="permissionsTable">
<table border="1" class="permission-grid secondary-background-color">
<thead class="content-background-color">
<tr>
<th class="border-component-color">&nbsp;</th>
{{#each role in allRoles}}
<th class="border-component-color" title="{{role.description}}">
<a href="{{pathFor "admin-permissions-edit" name=role._id}}">
{{role._id}}
<i class="icon-edit"></i>
</a>
</th>
{{/each}}
</tr>
</thead>
<tbody>
{{#each permission in permissions}}
<tr class="admin-table-row">
<td class="permission-name border-component-color"
title="{{permissionDescription permission}}">{{permissionName permission}}<br>[{{permission._id}}]
</td>
{{#each role in allRoles}}
<td class="border-component-color">
<input type="checkbox" name="perm[{{role._id}}][{{permission._id}}]" class="role-permission"
value="1" checked="{{granted permission.roles role}}" data-role="{{role._id}}"
data-permission="{{permission._id}}">
</td>
{{/each}}
</tr>
{{/each}}
</tbody>
</table>
</template>
<template name="permissions">
<div class="permissions-manager">
{{#if hasPermission}}
<a href="{{pathFor "admin-permissions-new"}}" class="button primary new-role">{{_ "New_role"}}</a>
<table border="1" class="permission-grid secondary-background-color">
<thead class="content-background-color">
<tr>
<th class="border-component-color">&nbsp;</th>
{{#each role}}
<th class="border-component-color" title="{{description}}">
<a href="{{pathFor "admin-permissions-edit" name=_id}}">
{{_id}}
<i class="icon-edit"></i>
</a>
</th>
{{/each}}
</tr>
</thead>
<tbody>
{{#each permission}}
<tr class="admin-table-row">
<td class="permission-name border-component-color" title="{{_ permissionDescription}}">{{_ permissionName}}<br>[{{_id}}]</td>
{{#each role}}
<td class="border-component-color">
<input type="checkbox" name="perm[{{_id}}][{{../_id}}]" class="role-permission" value="1" checked="{{granted ../roles}}" data-role="{{_id}}" data-permission="{{../_id}}">
</td>
{{/each}}
</tr>
{{/each}}
</tbody>
</table>
{{else}}
{{_ "Not_authorized"}}
{{/if}}
</div>
<section class="page-settings">
<div class="permissions-manager">
{{#if hasPermission}}
<a href="{{pathFor "admin-permissions-new"}}" class="button primary new-role">{{_ "New_role"}}</a>
<div class="rocket-form">
<div class="section">
{{> permissionsTable permissions=permissions allRoles=roles collection='Chat'}}
</div>
</div>
{{/if}}
{{#if hasSettingPermission}}
<div class="rocket-form">
<div class="section {{#unless settingPermissionExpanded}}section-collapsed{{/unless}}">
<div class="section-title">
<div class="section-title-text">
{{_ "Setting_permissions"}}</div>
<div class="section-title-right">
<button class="button primary js-toggle-setting-permissions"><span>
{{#if settingPermissionExpanded }}
{{_ "Collapse"}}
{{else}}
{{_ "Expand"}}
{{/if}}
</span>
</button>
</div>
</div>
<div class="section-content border-component-color">
{{#if settingPermissionExpanded }}
{{> permissionsTable permissions=settingPermissions allRoles=roles collection='Setting'}}
{{else}}
{{_ "Not_authorized"}}
{{/if}}
</div>
</div>
</div>
{{/if}}
{{#if hasNoPermission}}
{{_ "Not_authorized"}}
{{/if}}
</div>
</section>
</template>
119 changes: 93 additions & 26 deletions packages/rocketchat-authorization/client/views/permissions.js
Original file line number Diff line number Diff line change
@@ -1,66 +1,125 @@
/* globals ChatPermissions */
import {permissionLevel} from '../../lib/rocketchat';

const whereNotSetting = {
$where: function() {
return this.level !== permissionLevel.SETTING;
}.toString()
};

Template.permissions.helpers({
role() {
roles() {
return Template.instance().roles.get();
},

permission() {
return ChatPermissions.find({}, {
sort: {
_id: 1
permissions() {
return ChatPermissions.find(whereNotSetting, //the $where seems to have no effect - filtered as workaround after fetch()
{
sort: {
_id: 1
}
}).fetch()
.filter((setting) => !setting.level);
},

settingPermissions() {
return ChatPermissions.find({
level: permissionLevel.SETTING
},
{
sort: { //sorting seems not to be copied from the publication, we need to request it explicitly in find()
group: 1,
section: 1
}
});
}).fetch()
.filter((setting) => setting.group); //group permissions are assigned implicitly, we can hide them. $exists: {group:false} not supported by Minimongo
},

hasPermission() {
return RocketChat.authz.hasAllPermission('access-permissions');
},

hasSettingPermission() {
return RocketChat.authz.hasAllPermission('access-setting-permissions');
},

granted(roles) {
hasNoPermission() {
return !RocketChat.authz.hasAtLeastOnePermission(['access-permissions', 'access-setting-permissions']);
},

settingPermissionExpanded() {
return Template.instance().settingPermissionsExpanded.get();
}
});

Template.permissions.events({
'click .js-toggle-setting-permissions'(event, instance) {
instance.settingPermissionsExpanded.set(!instance.settingPermissionsExpanded.get());
}
});

Template.permissions.onCreated(function() {
this.settingPermissionsExpanded = new ReactiveVar(false);
this.roles = new ReactiveVar([]);

Tracker.autorun(() => {
this.roles.set(RocketChat.models.Roles.find().fetch());
});
});

Template.permissionsTable.helpers({
granted(roles, role) {
if (roles) {
if (roles.indexOf(this._id) !== -1) {
if (roles.indexOf(role._id) !== -1) {
return 'checked';
}
}
},

permissionName() {
return `${ this._id }`;
},

permissionDescription() {
return `${ this._id }_description`;
permissionName(permission) {
if (permission.level === permissionLevel.SETTING) {
let path = '';
if (permission.group) {
path = `${ t(permission.group) } > `;
}
if (permission.section) {
path = `${ path }${ t(permission.section) } > `;
}
path = `${ path }${ t(permission.settingId) }`;
return path;
} else {
return t(permission._id);
}
},

hasPermission() {
return RocketChat.authz.hasAllPermission('access-permissions');
permissionDescription(permission) {
return t(`${ permission._id }_description`);
}
});

Template.permissions.events({
Template.permissionsTable.events({
'click .role-permission'(e, instance) {
const permission = e.currentTarget.getAttribute('data-permission');
const role = e.currentTarget.getAttribute('data-role');

if (instance.permissionByRole[permission].indexOf(role) === -1) {
if (!instance.permissionByRole[permission] // the permissino has this role not assigned at all (undefined)
|| instance.permissionByRole[permission].indexOf(role) === -1) {
return Meteor.call('authorization:addPermissionToRole', permission, role);
} else {
return Meteor.call('authorization:removeRoleFromPermission', permission, role);
}
}
});

Template.permissions.onCreated(function() {
this.roles = new ReactiveVar([]);
Template.permissionsTable.onCreated(function() {
this.permissionByRole = {};
this.actions = {
added: {},
removed: {}
};

Tracker.autorun(() => {
this.roles.set(RocketChat.models.Roles.find().fetch());
});

Tracker.autorun(() => {
ChatPermissions.find().observeChanges({
const observer = {
added: (id, fields) => {
this.permissionByRole[id] = fields.roles;
},
Expand All @@ -70,6 +129,14 @@ Template.permissions.onCreated(function() {
removed: (id) => {
delete this.permissionByRole[id];
}
});
};
if (this.data.collection === 'Chat') {
ChatPermissions.find(whereNotSetting).observeChanges(observer);
}

if (this.data.collection === 'Setting') {
ChatPermissions.find({level: permissionLevel.SETTING}).observeChanges(observer);
}
});
});

4 changes: 4 additions & 0 deletions packages/rocketchat-authorization/lib/rocketchat.js
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
RocketChat.authz = {};

export const permissionLevel = {
SETTING: 'setting'
};
Original file line number Diff line number Diff line change
@@ -1,12 +1,28 @@
import {permissionLevel} from '../../lib/rocketchat';

Meteor.methods({
'authorization:addPermissionToRole'(permission, role) {
if (!Meteor.userId() || !RocketChat.authz.hasPermission(Meteor.userId(), 'access-permissions')) {
if (!Meteor.userId() || !RocketChat.authz.hasPermission(Meteor.userId(), 'access-permissions')
|| (permission.level === permissionLevel.SETTING && !RocketChat.authz.hasPermission(Meteor.userId(), 'access-setting-permissions'))
) {
throw new Meteor.Error('error-action-not-allowed', 'Adding permission is not allowed', {
method: 'authorization:addPermissionToRole',
action: 'Adding_permission'
});
}

// for setting-based-permissions, authorize the group access as well
const addParentPermissions = function(permissionId, role) {
const permission = RocketChat.models.Permissions.findOneById(permissionId);
if (permission.groupPermissionId) {
const groupPermission = RocketChat.models.Permissions.findOneById(permission.groupPermissionId);
if (groupPermission.roles.indexOf(role) === -1) {
RocketChat.models.Permissions.addRole(permission.groupPermissionId, role);
}
}
};

addParentPermissions(permission, role);
return RocketChat.models.Permissions.addRole(permission, role);
}
});
Original file line number Diff line number Diff line change
@@ -1,12 +1,34 @@
import {permissionLevel} from '../../lib/rocketchat';

Meteor.methods({
'authorization:removeRoleFromPermission'(permission, role) {
if (!Meteor.userId() || !RocketChat.authz.hasPermission(Meteor.userId(), 'access-permissions')) {
if (!Meteor.userId() || !RocketChat.authz.hasPermission(Meteor.userId(), 'access-permissions')
|| (permission.level === permissionLevel.SETTING && !RocketChat.authz.hasPermission(Meteor.userId(), 'access-setting-permissions'))
) {
throw new Meteor.Error('error-action-not-allowed', 'Accessing permissions is not allowed', {
method: 'authorization:removeRoleFromPermission',
action: 'Accessing_permissions'
});
}

return RocketChat.models.Permissions.removeRole(permission, role);
// for setting based permissions, revoke the group permission once all setting permissions
// related to this group have been removed
const removeStaleParentPermissions = function(permissionId, role) {
const permission = RocketChat.models.Permissions.findOneById(permissionId);
if (permission.groupPermissionId) {
const groupPermission = RocketChat.models.Permissions.findOneById(permission.groupPermissionId);
if (groupPermission.roles.indexOf(role) !== -1) {
// the role has the group permission assigned, so check whether it's still needed
if (RocketChat.models.Permissions.find({
groupPermissionId: permission.groupPermissionId,
roles: role
}).count() === 0) {
RocketChat.models.Permissions.removeRole(permission.groupPermissionId, role);
}
}
}
};
RocketChat.models.Permissions.removeRole(permission, role);
removeStaleParentPermissions(permission, role);
}
});
Loading

0 comments on commit bbb988d

Please sign in to comment.