Skip to content

Commit

Permalink
Setting based permissions - downport (RocketChat#158)
Browse files Browse the repository at this point in the history
* Allow maintenance of per-setting permissions

(cherry picked from commit eed869a)

* Implicitly assign and revoke setting group permissions

(cherry picked from commit 28b769b)

* Improve Display of setting permissions

(cherry picked from commit 8523456)

* Add path to permission title

(cherry picked from commit c87a30d)

* Permission to access setting permissions

(cherry picked from commit 48b1076)

* Adapt wording

(cherry picked from commit daccad8)

* UI-adaptation: Allow users with permission 'manage-selected-permissions' to see and change the affected settings.
However, this is not reactive: Once the permissions for a particular setting are changed, the user needs to log  off and on again before it becomes effective in the UI.
This is most probably a consequence of the CachedCollection. This collection needed to be changed on permission-change.
In the backend however, the permissions become effective immediately.

(cherry picked from commit 00e4bb5)

* Don't adapt sorting on the client side

(cherry picked from commit 9b71b62)

* Fix: Apply changed setting permissions reactively

(cherry picked from commit 293ad73)

* Move setting-based permissions to own collection

(cherry picked from commit 8f59f1c)

* Unify collections for setting and other permissions again into one

(cherry picked from commit 8d923c2)

* Get rid of frontend exceptions on changing selected settings

(cherry picked from commit a7fdc87)

* - Sort permissions by group
- Do not try to create permissions for hidden settings in higher-level-callbacks
- Remove `setting-permissions` collection - fully integrated into `permissions`

(cherry picked from commit f007231)

* Harmonize wording in German

(cherry picked from commit 5cf5df2)
  • Loading branch information
mrsimpson authored and vickyokrm committed Dec 4, 2017
1 parent 41244a5 commit 75d1491
Show file tree
Hide file tree
Showing 19 changed files with 510 additions and 177 deletions.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@
"chat"
],
"scripts": {
"start": "meteor npm i && meteor",
"start": "meteor npm i && meteor run",
"debug": "meteor run --inspect",
"debug-brk": "meteor run --inspect-brk",
"lint": "eslint .",
"lint-fix": "eslint . --fix",
"stylelint": "stylelint packages/**/*.css",
Expand Down
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
108 changes: 74 additions & 34 deletions packages/rocketchat-authorization/client/views/permissions.html
Original file line number Diff line number Diff line change
@@ -1,36 +1,76 @@
<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[{{_id}}][{{../_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 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}}
{{else}}
{{_ "Not_authorized"}}
{{/if}}
</div>
</section>
</template>
115 changes: 89 additions & 26 deletions packages/rocketchat-authorization/client/views/permissions.js
Original file line number Diff line number Diff line change
@@ -1,66 +1,121 @@
/* 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
},

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

hasSettingPermission() {
return RocketChat.authz.hasAllPermission('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 +125,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 75d1491

Please sign in to comment.