Complement Guest role restrictions for Enterprise (#17393)

Co-authored-by: Diego Sampaio <chinello@gmail.com>
pull/17482/head
pierre-lehnen-rc 6 years ago committed by GitHub
parent 9de05043b7
commit c871ebe304
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 15
      app/authorization/client/hasPermission.js
  2. 2
      app/authorization/client/index.js
  3. 10
      app/authorization/client/lib/AuthorizationUtils.ts
  4. 4
      app/authorization/client/views/permissions.html
  5. 7
      app/authorization/client/views/permissions.js
  6. 56
      app/authorization/lib/AuthorizationUtils.ts
  7. 2
      app/authorization/lib/index.js
  8. 5
      app/authorization/server/functions/hasPermission.js
  9. 2
      app/authorization/server/index.js
  10. 10
      app/authorization/server/lib/AuthorizationUtils.ts
  11. 7
      app/authorization/server/methods/addPermissionToRole.js
  12. 8
      app/authorization/server/methods/removeRoleFromPermission.js
  13. 8
      app/models/client/models/Users.js
  14. 21
      app/ui-flextab/client/tabs/userEdit.js
  15. 12
      ee/app/authorization/client/AuthorizationUtils.ts
  16. 16
      ee/app/authorization/client/index.ts
  17. 6
      ee/app/authorization/lib/addRoleRestrictions.js
  18. 6
      ee/app/authorization/lib/guestPermissions.js
  19. 12
      ee/app/authorization/server/AuthorizationUtils.ts
  20. 2
      ee/app/authorization/server/index.ts
  21. 6
      ee/app/authorization/server/resetEnterprisePermissions.js
  22. 13
      ee/app/license/client/index.ts
  23. 9
      ee/app/license/server/license.ts
  24. 3
      packages/rocketchat-i18n/i18n/en.i18n.json
  25. 2
      server/publications/settings/index.js

@ -3,11 +3,19 @@ import { Template } from 'meteor/templating';
import { ChatPermissions } from './lib/ChatPermissions';
import * as Models from '../../models';
import { AuthorizationUtils } from '../lib/AuthorizationUtils';
function atLeastOne(permissions = [], scope, userId) {
userId = userId || Meteor.userId();
const user = Models.Users.findOneById(userId, { fields: { roles: 1 } });
return permissions.some((permissionId) => {
if (user && user.roles) {
if (AuthorizationUtils.isPermissionRestrictedForRoleList(permissionId, user.roles)) {
return false;
}
}
const permission = ChatPermissions.findOne(permissionId, { fields: { roles: 1 } });
const roles = (permission && permission.roles) || [];
@ -23,8 +31,15 @@ function atLeastOne(permissions = [], scope, userId) {
function all(permissions = [], scope, userId) {
userId = userId || Meteor.userId();
const user = Models.Users.findOneById(userId, { fields: { roles: 1 } });
return permissions.every((permissionId) => {
if (user && user.roles) {
if (AuthorizationUtils.isPermissionRestrictedForRoleList(permissionId, user.roles)) {
return false;
}
}
const permission = ChatPermissions.findOne(permissionId, { fields: { roles: 1 } });
const roles = (permission && permission.roles) || [];

@ -1,6 +1,6 @@
import { hasAllPermission, hasAtLeastOnePermission, hasPermission, userHasAllPermission } from './hasPermission';
import { hasRole } from './hasRole';
import { AuthorizationUtils } from './lib/AuthorizationUtils';
import { AuthorizationUtils } from '../lib/AuthorizationUtils';
import './usersNameChanged';
import './requiresPermission.html';
import './route';

@ -1,10 +0,0 @@
import { Meteor } from 'meteor/meteor';
export const AuthorizationUtils = class {
static isRoleReadOnly(roleId: string): boolean {
if (!roleId) {
throw new Meteor.Error('invalid-param');
}
return false;
}
};

@ -27,7 +27,9 @@
<td class="permission-name border-component-color" title="{{permissionDescription permission}}">{{permissionName permission}}<br><span class = "id-styler">[ID: {{permission._id}}]</span></td>
{{#each role in allRoles}}
<td class="permission-checkbox 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}}" disabled="{{disabled role}}">
{{#if isRolePermissionEnabled role permission}}
<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}}">
{{/if}}
</td>
{{else}}
<tr class="table-no-click">

@ -10,8 +10,7 @@ import { ChatPermissions } from '../lib/ChatPermissions';
import { hasAllPermission } from '../hasPermission';
import { t } from '../../../utils/client';
import { SideNav } from '../../../ui-utils/client/lib/SideNav';
import { CONSTANTS } from '../../lib';
import { AuthorizationUtils } from '../lib/AuthorizationUtils';
import { CONSTANTS, AuthorizationUtils } from '../../lib';
import { hasAtLeastOnePermission } from '..';
@ -181,8 +180,8 @@ Template.permissionsTable.helpers({
return t(`${ permission._id }_description`);
},
disabled(role) {
return AuthorizationUtils.isRoleReadOnly(role._id);
isRolePermissionEnabled(role, permission) {
return !AuthorizationUtils.isPermissionRestrictedForRole(permission._id, role._id);
},
});

@ -0,0 +1,56 @@
import { Meteor } from 'meteor/meteor';
const restrictedRolePermissions = new Map();
export const AuthorizationUtils = class {
static addRolePermissionWhiteList(roleId: string, list: [string]): void {
if (!roleId) {
throw new Meteor.Error('invalid-param');
}
if (!list) {
throw new Meteor.Error('invalid-param');
}
if (!restrictedRolePermissions.has(roleId)) {
restrictedRolePermissions.set(roleId, new Set());
}
const rules = restrictedRolePermissions.get(roleId);
for (const permissionId of list) {
rules.add(permissionId);
}
}
static isPermissionRestrictedForRole(permissionId: string, roleId: string): boolean {
if (!roleId || !permissionId) {
throw new Meteor.Error('invalid-param');
}
if (!restrictedRolePermissions.has(roleId)) {
return false;
}
const rules = restrictedRolePermissions.get(roleId);
if (!rules || !rules.size) {
return false;
}
return !rules.has(permissionId);
}
static isPermissionRestrictedForRoleList(permissionId: string, roleList: [string]): boolean {
if (!roleList || !permissionId) {
throw new Meteor.Error('invalid-param');
}
for (const roleId of roleList) {
if (this.isPermissionRestrictedForRole(permissionId, roleId)) {
return true;
}
}
return false;
}
};

@ -6,3 +6,5 @@ export const getSettingPermissionId = function(settingId) {
export const CONSTANTS = {
SETTINGS_LEVEL: 'settings',
};
export { AuthorizationUtils } from './AuthorizationUtils';

@ -1,8 +1,13 @@
import mem from 'mem';
import { Permissions, Users, Subscriptions } from '../../../models/server/raw';
import { AuthorizationUtils } from '../../lib/AuthorizationUtils';
const rolesHasPermission = mem(async (permission, roles) => {
if (AuthorizationUtils.isPermissionRestrictedForRoleList(permission, roles)) {
return false;
}
const result = await Permissions.findOne({ _id: permission, roles: { $in: roles } }, { projection: { _id: 1 } });
return !!result;
}, {

@ -14,7 +14,7 @@ import {
} from './functions/hasPermission';
import { hasRole } from './functions/hasRole';
import { removeUserFromRoles } from './functions/removeUserFromRoles';
import { AuthorizationUtils } from './lib/AuthorizationUtils';
import { AuthorizationUtils } from '../lib/AuthorizationUtils';
import './methods/addPermissionToRole';
import './methods/addUserToRole';
import './methods/deleteRole';

@ -1,10 +0,0 @@
import { Meteor } from 'meteor/meteor';
export const AuthorizationUtils = class {
static isRoleReadOnly(roleId: string): boolean {
if (!roleId) {
throw new Meteor.Error('invalid-param');
}
return false;
}
};

@ -2,13 +2,12 @@ import { Meteor } from 'meteor/meteor';
import { Permissions } from '../../../models/server';
import { hasPermission } from '../functions/hasPermission';
import { AuthorizationUtils } from '../lib/AuthorizationUtils';
import { CONSTANTS } from '../../lib';
import { CONSTANTS, AuthorizationUtils } from '../../lib';
Meteor.methods({
'authorization:addPermissionToRole'(permissionId, role) {
if (AuthorizationUtils.isRoleReadOnly(role)) {
throw new Meteor.Error('error-action-not-allowed', 'Role is readonly', {
if (AuthorizationUtils.isPermissionRestrictedForRole(permissionId, role)) {
throw new Meteor.Error('error-action-not-allowed', 'Permission is restricted', {
method: 'authorization:addPermissionToRole',
action: 'Adding_permission',
});

@ -2,18 +2,10 @@ import { Meteor } from 'meteor/meteor';
import { Permissions } from '../../../models/server';
import { hasPermission } from '../functions/hasPermission';
import { AuthorizationUtils } from '../lib/AuthorizationUtils';
import { CONSTANTS } from '../../lib';
Meteor.methods({
'authorization:removeRoleFromPermission'(permissionId, role) {
if (AuthorizationUtils.isRoleReadOnly(role)) {
throw new Meteor.Error('error-action-not-allowed', 'Role is readonly', {
method: 'authorization:removeRoleFromPermission',
action: 'Removing_permission',
});
}
const uid = Meteor.userId();
const permission = Permissions.findOneById(permissionId);

@ -2,12 +2,16 @@ import { Meteor } from 'meteor/meteor';
import { Mongo } from 'meteor/mongo';
export const Users = {
isUserInRole(userId, roleName) {
findOneById(userId, options = {}) {
const query = {
_id: userId,
};
const user = this.findOne(query, { fields: { roles: 1 } });
return this.findOne(query, options);
},
isUserInRole(userId, roleName) {
const user = this.findOneById(userId, { fields: { roles: 1 } });
return user && Array.isArray(user.roles) && user.roles.includes(roleName);
},

@ -10,8 +10,9 @@ import { t, handleError } from '../../../utils';
import { Roles } from '../../../models';
import { Notifications } from '../../../notifications';
import { hasAtLeastOnePermission } from '../../../authorization';
import { settings } from '../../../settings';
import { callbacks } from '../../../callbacks';
import { settings } from '../../../settings/client';
import { callbacks } from '../../../callbacks/client';
import { modal } from '../../../ui-utils/client';
Template.userEdit.helpers({
@ -290,6 +291,22 @@ Template.userEdit.onCreated(function() {
Meteor.call('insertOrUpdateUser', userData, (error) => {
if (error) {
if (error.error === 'error-max-guests-number-reached') {
const message = TAPi18n.__('You_reached_the_maximum_number_of_guest_users_allowed_by_your_license.');
const url = 'https://go.rocket.chat/i/guest-limit-exceeded';
const email = 'sales@rocket.chat';
const linkText = TAPi18n.__('Click_here_for_more_details_or_contact_sales_for_a_new_license', { url, email });
modal.open({
type: 'error',
title: TAPi18n.__('Maximum_number_of_guests_reached'),
text: `${ message } ${ linkText }`,
html: true,
});
return true;
}
return handleError(error);
}
toastr.success(userData._id ? t('User_updated_successfully') : t('User_added_successfully'));

@ -1,12 +0,0 @@
import { AuthorizationUtils } from '../../../../app/authorization/client/lib/AuthorizationUtils';
import { isEnterprise } from '../../license/client';
const { isRoleReadOnly: oldIsRoleReadOnly } = AuthorizationUtils;
AuthorizationUtils.isRoleReadOnly = function(roleId: string): boolean {
if (isEnterprise() && roleId === 'guest') {
return true;
}
return oldIsRoleReadOnly(roleId);
};

@ -1 +1,15 @@
import './AuthorizationUtils';
import { Meteor } from 'meteor/meteor';
import { addRoleRestrictions } from '../lib/addRoleRestrictions';
Meteor.startup(() => {
Meteor.call('license:isEnterprise', (err, result) => {
if (err) {
throw err;
}
if (result) {
addRoleRestrictions();
}
});
});

@ -0,0 +1,6 @@
import { AuthorizationUtils } from '../../../../app/authorization/lib/AuthorizationUtils';
import { guestPermissions } from './guestPermissions';
export const addRoleRestrictions = function() {
AuthorizationUtils.addRolePermissionWhiteList('guest', guestPermissions);
};

@ -0,0 +1,6 @@
export const guestPermissions = [
'view-d-room',
'view-joined-room',
'view-p-room',
'start-discussion',
];

@ -1,12 +0,0 @@
import { AuthorizationUtils } from '../../../../app/authorization/server/lib/AuthorizationUtils';
import { isEnterprise } from '../../license/server';
const { isRoleReadOnly: oldIsRoleReadOnly } = AuthorizationUtils;
AuthorizationUtils.isRoleReadOnly = function(roleId: string): boolean {
if (isEnterprise() && roleId === 'guest') {
return true;
}
return oldIsRoleReadOnly(roleId);
};

@ -1 +1 @@
import './AuthorizationUtils';
import '../lib/addRoleRestrictions';

@ -0,0 +1,6 @@
import { Permissions } from '../../../../app/models/server';
import { guestPermissions } from '../lib/guestPermissions';
export const resetEnterprisePermissions = function() {
Permissions.update({ _id: { $nin: guestPermissions } }, { $pull: { roles: 'guest' } }, { multi: true });
};

@ -13,15 +13,6 @@ const allModules = new Promise<Set<string>>((resolve, reject) => {
});
});
let isEnterpriseServer = false;
CachedCollectionManager.onLogin(async () => {
try {
isEnterpriseServer = await callMethod('license:isEnterprise');
} catch (e) {
console.error('Error checking if server is Enterprise', e);
}
});
export async function hasLicense(feature: string): Promise<boolean> {
try {
const features = await allModules;
@ -30,7 +21,3 @@ export async function hasLicense(feature: string): Promise<boolean> {
return false;
}
}
export function isEnterprise(): boolean {
return isEnterpriseServer;
}

@ -1,6 +1,8 @@
import EventEmitter from 'events';
import { Users } from '../../../../app/models/server';
import { resetEnterprisePermissions } from '../../authorization/server/resetEnterprisePermissions';
import { addRoleRestrictions } from '../../authorization/lib/addRoleRestrictions';
import decrypt from './decrypt';
import { getBundleModules, isBundle } from './bundles';
@ -21,6 +23,7 @@ export interface IValidLicense {
}
let maxGuestUsers = 0;
let addedRoleRestrictions = false;
class LicenseClass {
private url: string|null = null;
@ -76,6 +79,12 @@ class LicenseClass {
});
this.validate();
if (!addedRoleRestrictions && this.hasAnyValidLicense()) {
addRoleRestrictions();
resetEnterprisePermissions();
addedRoleRestrictions = true;
}
}
hasModule(module: string): boolean {

@ -675,6 +675,7 @@
"Clear_filters": "Clear filters",
"clear_history": "Clear History",
"Click_here": "Click here",
"Click_here_for_more_details_or_contact_sales_for_a_new_license": "<a target=\"_blank\" href=\"__url__\">Click here</a> for more details or contact <strong>__email__</strong> for a new license.",
"Click_here_for_more_info": "Click here for more info",
"Click_here_to_enter_your_encryption_password": "Click here to enter your encryption password",
"Click_here_to_view_and_copy_your_password": "Click here to view and copy your password.",
@ -2279,6 +2280,7 @@
"Max_number_incoming_livechats_displayed_description": "(Optional) Max number of items displayed in the incoming Omnichannel queue.",
"Max_number_of_uses": "Max number of uses",
"Maximum": "Maximum",
"Maximum_number_of_guests_reached": "Maximum number of guests reached",
"Media": "Media",
"Medium": "Medium",
"Members": "Members",
@ -3738,6 +3740,7 @@
"You_need_to_type_in_your_username_in_order_to_do_this": "You need to type in your username in order to do this!",
"You_need_to_verifiy_your_email_address_to_get_notications": "You need to verify your email address to get notifications",
"You_need_to_write_something": "You need to write something!",
"You_reached_the_maximum_number_of_guest_users_allowed_by_your_license": "You reached the maximum number of guest users allowed by your license.",
"You_should_inform_one_url_at_least": "You should define at least one URL.",
"You_should_name_it_to_easily_manage_your_integrations": "You should name it to easily manage your integrations.",
"You_will_not_be_able_to_recover": "You will not be able to recover this message!",

@ -3,7 +3,7 @@ import { Meteor } from 'meteor/meteor';
import { Settings } from '../../../app/models/server';
import { Notifications } from '../../../app/notifications/server';
import { hasPermission, hasAtLeastOnePermission } from '../../../app/authorization/server';
import { getSettingPermissionId } from '../../../app/authorization/lib.js';
import { getSettingPermissionId } from '../../../app/authorization/lib';
Meteor.methods({
'public-settings/get'(updatedAt) {

Loading…
Cancel
Save