From eaf025ec372ea52121d702aeba4432d5901cc7c2 Mon Sep 17 00:00:00 2001 From: Marcelo Schmidt Date: Wed, 6 Jan 2016 18:10:37 -0200 Subject: [PATCH] Rework --- client/startup/startup.coffee | 3 + packages/rocketchat-authorization/README.md | 15 +- .../client/hasPermission.coffee | 44 +- .../client/hasRole.coffee | 11 +- .../ChatPermissions.coffee} | 2 +- .../client/lib/models/Roles.coffee | 14 + .../client/lib/models/Subscriptions.js | 95 +++ .../client/lib/models/Users.js | 80 ++ .../rocketchat-authorization/client/roles.js | 22 - .../client/startup.coffee | 3 + .../client/views/permissions.coffee | 5 +- .../client/views/permissionsRole.coffee | 2 +- .../rocketchat-authorization/lib/roles.js | 713 ------------------ packages/rocketchat-authorization/package.js | 25 +- .../server/functions/addUserRoles.coffee | 19 + .../server/functions/addUsersToRoles.coffee | 27 - .../functions/getPermissionsForRole.coffee | 6 +- .../server/functions/getRoles.coffee | 2 +- .../server/functions/getRolesForUser.coffee | 6 - .../server/functions/getUsersInRole.coffee | 6 +- .../server/functions/hasPermission.coffee | 11 +- .../server/functions/hasRole.coffee | 6 +- .../functions/removeUserFromRoles.coffee | 18 + .../functions/removeUsersFromRoles.coffee | 25 - .../server/methods/addUserToRole.coffee | 7 +- .../server/methods/deleteRole.coffee | 13 +- .../server/methods/removeUserFromRole.coffee | 2 +- .../server/methods/saveRole.coffee | 5 +- .../server/models/Permissions.coffee | 1 - .../server/models/Roles.coffee | 42 ++ .../server/models/Subscriptions.js | 91 +++ .../server/models/Users.js | 75 +- .../server/publication.coffee | 2 - .../server/publications/permissions.js | 3 + .../server/publications/roles.coffee | 4 +- .../server/publications/scopedRoles.js | 11 + .../rocketchat-authorization/server/roles.js | 28 - .../server/startup.coffee | 36 +- .../incoming/addIncomingIntegration.coffee | 2 +- .../incoming/updateIncomingIntegration.coffee | 2 +- .../server/methods/setAdminStatus.coffee | 4 +- packages/rocketchat-livechat/permissions.js | 8 +- .../server/methods/addAgent.js | 2 +- .../server/methods/addManager.js | 2 +- .../server/methods/removeAgent.js | 2 +- .../server/methods/removeManager.js | 2 +- .../server/models/Users.js | 9 +- packages/rocketchat-ui/lib/collections.coffee | 5 + server/lib/accounts.coffee | 2 +- server/methods/createChannel.coffee | 2 +- server/methods/createPrivateGroup.coffee | 2 +- server/methods/removeUserFromRoom.coffee | 2 +- server/startup/initialData.coffee | 4 +- server/startup/migrations/v27.coffee | 11 + 54 files changed, 572 insertions(+), 969 deletions(-) rename packages/rocketchat-authorization/client/{collection.coffee => lib/ChatPermissions.coffee} (96%) create mode 100644 packages/rocketchat-authorization/client/lib/models/Roles.coffee create mode 100644 packages/rocketchat-authorization/client/lib/models/Subscriptions.js create mode 100644 packages/rocketchat-authorization/client/lib/models/Users.js delete mode 100644 packages/rocketchat-authorization/client/roles.js delete mode 100644 packages/rocketchat-authorization/lib/roles.js create mode 100644 packages/rocketchat-authorization/server/functions/addUserRoles.coffee delete mode 100644 packages/rocketchat-authorization/server/functions/addUsersToRoles.coffee delete mode 100644 packages/rocketchat-authorization/server/functions/getRolesForUser.coffee create mode 100644 packages/rocketchat-authorization/server/functions/removeUserFromRoles.coffee delete mode 100644 packages/rocketchat-authorization/server/functions/removeUsersFromRoles.coffee create mode 100644 packages/rocketchat-authorization/server/models/Roles.coffee create mode 100644 packages/rocketchat-authorization/server/models/Subscriptions.js delete mode 100644 packages/rocketchat-authorization/server/publication.coffee create mode 100644 packages/rocketchat-authorization/server/publications/permissions.js create mode 100644 packages/rocketchat-authorization/server/publications/scopedRoles.js delete mode 100644 packages/rocketchat-authorization/server/roles.js create mode 100644 server/startup/migrations/v27.coffee diff --git a/client/startup/startup.coffee b/client/startup/startup.coffee index f7043af4946..ed209ee71b3 100644 --- a/client/startup/startup.coffee +++ b/client/startup/startup.coffee @@ -24,6 +24,9 @@ Meteor.startup -> loadedLaguages = [] @setLanguage = (language) -> + if !language + return + if loadedLaguages.indexOf(language) > -1 return diff --git a/packages/rocketchat-authorization/README.md b/packages/rocketchat-authorization/README.md index 239b085c856..c2365d83f69 100644 --- a/packages/rocketchat-authorization/README.md +++ b/packages/rocketchat-authorization/README.md @@ -1,6 +1,6 @@ -Supports role or permission based authorization, and defines the association between them. +Supports role or permission based authorization, and defines the association between them. -A user is associated with role(s), and a role is associated with permission(s). This package depends on alanning:roles for the role/user association, while the role/permission association is handled internally. Thus, the underlying alanning:roles has no concept of a permission or the association between a role and permission. +A user is associated with role(s), and a role is associated with permission(s). This package depends on alanning:roles for the role/user association, while the role/permission association is handled internally. Thus, the underlying alanning:roles has no concept of a permission or the association between a role and permission. Authorization checks can be done based on a role or permission. However, permission based checks are preferred because they loosely associate an action with a role. For example: @@ -19,13 +19,12 @@ if hasRole(userId, ['admin','site-moderator','moderator']) Usage: ``` -# assign user to moderator role. Permissions scoped globally -# user can moderate (e.g. edit channel name, delete private group message) for all rooms -RocketChat.authz.addUsersToRoles(userId, 'moderator') +# assign user to admin role. Permissions scoped globally +RocketChat.authz.addUserRoles(userId, 'admin') # assign user to moderator role. Permissions scoped to the specified room # user can moderate (e.g. edit channel name, delete private group message) for only one room specified by the roomId -RocketChat.authz.addUsersToRoles(userId, 'moderator', roomId ) +RocketChat.authz.addUserRoles(userId, 'moderator', roomId ) # check if user can modify message for any room RocketChat.authz.hasPermission(userId, 'edit-message') @@ -37,5 +36,5 @@ RocketChat.authz.hasPermission(userId, 'edit-message', roomId) Notes: 1. Roles are statically defined. UI needs to be implemented to dynamically assign permission(s) to a Role -2. 'admin', 'moderator', 'user' role identifiers should NOT be changed (unless you update the associated code) because they are referenced when creating users and creating rooms. -3. edit, delete message permissions are at either the global or room scope. i.e. role with edit-message with GLOBAL scope can edit ANY message regardless of the room type. However, role with edit-message with room scope can only edit messages for the room. The global scope is associated with the admin role while the "room-scoped" permission is assigned to the room "moderator" (room creator). If we want a middle ground that allows for edit-message for only channel/group/direct, then we need to create individual edit-c-message, edit-p-message, edit-d-message permissions. +2. 'admin', 'moderator', 'user' role identifiers should NOT be changed (unless you update the associated code) because they are referenced when creating users and creating rooms. +3. edit, delete message permissions are at either the global or room scope. i.e. role with edit-message with GLOBAL scope can edit ANY message regardless of the room type. However, role with edit-message with room scope can only edit messages for the room. The global scope is associated with the admin role while the "room-scoped" permission is assigned to the room "moderator" (room creator). If we want a middle ground that allows for edit-message for only channel/group/direct, then we need to create individual edit-c-message, edit-p-message, edit-d-message permissions. diff --git a/packages/rocketchat-authorization/client/hasPermission.coffee b/packages/rocketchat-authorization/client/hasPermission.coffee index 9d636b9552e..015ec854ee7 100644 --- a/packages/rocketchat-authorization/client/hasPermission.coffee +++ b/packages/rocketchat-authorization/client/hasPermission.coffee @@ -1,25 +1,29 @@ -atLeastOne = (toFind, toSearch) -> - console.log 'toFind: ', toFind if window.rocketDebug - console.log 'toSearch: ', toSearch if window.rocketDebug - return not _.isEmpty(_.intersection(toFind, toSearch)) - -all = (toFind, toSearch) -> - toFind = _.uniq(toFind) - toSearch = _.uniq(toSearch) - return _.isEmpty( _.difference( toFind, toSearch)) +atLeastOne = (permissions, scope) -> + return _.some permissions, (permissionId) -> + permission = ChatPermissions.findOne permissionId + return _.some permission.roles, (roleName) -> + role = RocketChat.models.Roles.findOne roleName + roleScope = role?.scope + return RocketChat.models[roleScope]?.isUserInRole?(Meteor.userId(), roleName, scope) + +all = (permissions, scope) -> + return _.every permissions, (permissionId) -> + permission = ChatPermissions.findOne permissionId + return _.some permission.roles, (roleName) -> + role = RocketChat.models.Roles.findOne roleName + roleScope = role?.scope + return RocketChat.models[roleScope]?.isUserInRole?(Meteor.userId(), roleName, scope) Template.registerHelper 'hasPermission', (permission, scope) -> - unless _.isString( scope ) - scope = Roles.GLOBAL_GROUP - return hasPermission( permission, scope, atLeastOne) + return hasPermission(permission, scope, atLeastOne) -RocketChat.authz.hasAllPermission = (permissions, scope=Roles.GLOBAL_GROUP) -> - return hasPermission( permissions, scope, all ) +RocketChat.authz.hasAllPermission = (permissions, scope) -> + return hasPermission(permissions, scope, all) -RocketChat.authz.hasAtLeastOnePermission = (permissions, scope=Roles.GLOBAL_GROUP) -> +RocketChat.authz.hasAtLeastOnePermission = (permissions, scope) -> return hasPermission(permissions, scope, atLeastOne) -hasPermission = (permissions, scope=Roles.GLOBAL_GROUP, strategy) -> +hasPermission = (permissions, scope, strategy) -> userId = Meteor.userId() unless userId @@ -30,10 +34,4 @@ hasPermission = (permissions, scope=Roles.GLOBAL_GROUP, strategy) -> permissions = [].concat permissions - roleNames = Roles.getRolesForUser(userId, scope) - - userPermissions = [] - for roleName in roleNames - userPermissions = userPermissions.concat(_.pluck(ChatPermissions.find({roles : roleName }).fetch(), '_id')) - - return strategy( permissions, userPermissions) + return strategy(permissions, scope) diff --git a/packages/rocketchat-authorization/client/hasRole.coffee b/packages/rocketchat-authorization/client/hasRole.coffee index 3140a74bfe6..aff335e48b2 100644 --- a/packages/rocketchat-authorization/client/hasRole.coffee +++ b/packages/rocketchat-authorization/client/hasRole.coffee @@ -1,8 +1,3 @@ -RocketChat.authz.hasRole = (userId, roleName, scope=Roles.GLOBAL_GROUP) -> - unless Meteor.userId() - return false - - roleName = [].concat roleName - - # per alanning:roles, returns true if user is in ANY roles - return Roles.userIsInRole(userId, roleName, scope) +RocketChat.authz.hasRole = (userId, roleNames, scope) -> + roleNames = [].concat roleNames + return RocketChat.models.Roles.isUserInRoles(userId, roleNames, scope) # true if user is in ANY role diff --git a/packages/rocketchat-authorization/client/collection.coffee b/packages/rocketchat-authorization/client/lib/ChatPermissions.coffee similarity index 96% rename from packages/rocketchat-authorization/client/collection.coffee rename to packages/rocketchat-authorization/client/lib/ChatPermissions.coffee index b719f7f9ed6..e9adaae10f2 100644 --- a/packages/rocketchat-authorization/client/collection.coffee +++ b/packages/rocketchat-authorization/client/lib/ChatPermissions.coffee @@ -1 +1 @@ -@ChatPermissions = new Meteor.Collection 'rocketchat_permissions' \ No newline at end of file +@ChatPermissions = new Meteor.Collection 'rocketchat_permissions' diff --git a/packages/rocketchat-authorization/client/lib/models/Roles.coffee b/packages/rocketchat-authorization/client/lib/models/Roles.coffee new file mode 100644 index 00000000000..5d94d584c61 --- /dev/null +++ b/packages/rocketchat-authorization/client/lib/models/Roles.coffee @@ -0,0 +1,14 @@ +RocketChat.models.Roles = new Meteor.Collection 'rocketchat_roles' + +RocketChat.models.Roles.findUsersInRole = (name, scope, options) -> + role = @findOne name + roleScope = role?.scope or 'Users' + RocketChat.models[roleScope]?.findUsersInRoles?(name, scope, options) + +RocketChat.models.Roles.isUserInRoles = (userId, roles, scope) -> + roles = [].concat roles + _.some roles, (roleName) => + role = @findOne roleName + roleScope = role?.scope or 'Users' + return RocketChat.models[roleScope]?.isUserInRole?(userId, roleName, scope) + diff --git a/packages/rocketchat-authorization/client/lib/models/Subscriptions.js b/packages/rocketchat-authorization/client/lib/models/Subscriptions.js new file mode 100644 index 00000000000..3c1f3a4819a --- /dev/null +++ b/packages/rocketchat-authorization/client/lib/models/Subscriptions.js @@ -0,0 +1,95 @@ +if (_.isUndefined(RocketChat.models.Subscriptions)) { + RocketChat.models.Subscriptions = {} +} + +RocketChat.models.Subscriptions.findRolesByUserId = function(userId) { + query = { + "u._id": userId + }; + + options = { fields: { roles: 1 } } + + return this.find(query, options); +} + +RocketChat.models.Subscriptions.isUserInRole = function(userId, roleName, roomId) { + query = { + "u._id": userId, + rid: roomId, + roles: roleName + }; + + return !_.isUndefined(this.findOne(query)); +} + +RocketChat.models.Subscriptions.setRolesByUserId = function(userId, roles, roomId) { + roles = [].concat(roles); + + var query = { + "u._id": userId, + rid: roomId + } + + var update = { + $set: { + roles: roles + } + } + + return this.update(query, update); +} + +RocketChat.models.Subscriptions.addRolesByUserId = function(userId, roles, roomId) { + roles = [].concat(roles); + + var query = { + "u._id": userId, + rid: roomId + } + + var update = { + $addToSet: { + roles: { $each: roles } + } + } + + return this.update(query, update); +} + +RocketChat.models.Subscriptions.removeRolesByUserId = function(userId, roles, roomId) { + roles = [].concat(roles); + + var query = { + "u._id": userId, + rid: roomId + } + + var update = { + $pullAll: { + roles: roles + } + } + + return this.update(query, update); +} + +RocketChat.models.Subscriptions.findUsersInRoles = function(roles, scope, options) { + roles = [].concat(roles); + + var query = { + roles: { $in: roles } + } + + if (scope) { + query.rid = scope; + } + + subscriptions = this.find(query).fetch(); + + users = _.compact(_.map(subscriptions, function(subscription) { + if ('undefined' !== typeof subscription.u && 'undefined' !== typeof subscription.u._id) + return subscription.u._id + })); + + return RocketChat.models.Users.find({ _id: { $in: users } }, options); +} diff --git a/packages/rocketchat-authorization/client/lib/models/Users.js b/packages/rocketchat-authorization/client/lib/models/Users.js new file mode 100644 index 00000000000..c00fb3cf9cf --- /dev/null +++ b/packages/rocketchat-authorization/client/lib/models/Users.js @@ -0,0 +1,80 @@ +if (_.isUndefined(RocketChat.models.Users)) { + RocketChat.models.Users = {} +} + +RocketChat.models.Users.findRolesByUserId = function(userId) { + var query = { + _id: userId + }; + + options = { fields: { roles: 1 } } + + return this.find(query, options); +}; + +RocketChat.models.Users.isUserInRole = function(userId, roleName) { + query = { + _id: userId, + roles: roleName + }; + + return !_.isUndefined(this.findOne(query)); +} + +RocketChat.models.Users.setRolesByUserId = function(userId, roles) { + roles = [].concat(roles); + + var query = { + _id: userId + } + + var update = { + $set: { + roles: roles + } + } + + return this.update(query, update); +} + +RocketChat.models.Users.addRolesByUserId = function(userId, roles) { + roles = [].concat(roles); + + var query = { + _id: userId + } + + var update = { + $addToSet: { + roles: { $each: roles } + } + } + + return this.update(query, update); +} + +RocketChat.models.Users.removeRolesByUserId = function(userId, roles) { + roles = [].concat(roles); + + var query = { + _id: userId + } + + var update = { + $pullAll: { + roles: roles + } + } + + return this.update(query, update); +} + +RocketChat.models.Users.findUsersInRoles = function(roles, scope, options) { + roles = [].concat(roles); + + var query = { + roles: { $in: roles } + } + + return this.find(query, options); +} diff --git a/packages/rocketchat-authorization/client/roles.js b/packages/rocketchat-authorization/client/roles.js deleted file mode 100644 index 30daddeed72..00000000000 --- a/packages/rocketchat-authorization/client/roles.js +++ /dev/null @@ -1,22 +0,0 @@ -"use strict" - -/** - * Subscription handle for the currently logged in user's permissions. - * - * NOTE: The corresponding publish function, `_roles`, depends on - * `this.userId` so it will automatically re-run when the currently - * logged-in user changes. - * - * @example - * - * `Roles.subscription.ready()` // => `true` if user roles have been loaded - * - * @property subscription - * @type Object - * @for Roles - */ - -Tracker.autorun(function () { - // Subscribe to global user roles - Meteor.subscribe("scope-roles", "Users") -}) diff --git a/packages/rocketchat-authorization/client/startup.coffee b/packages/rocketchat-authorization/client/startup.coffee index 144f7ea88f9..2c9482c5458 100644 --- a/packages/rocketchat-authorization/client/startup.coffee +++ b/packages/rocketchat-authorization/client/startup.coffee @@ -1,3 +1,6 @@ +Meteor.subscribe 'roles' +Meteor.subscribe 'scopedRoles', 'Users' + RocketChat.authz.subscription = Meteor.subscribe 'permissions' RocketChat.AdminBox.addOption diff --git a/packages/rocketchat-authorization/client/views/permissions.coffee b/packages/rocketchat-authorization/client/views/permissions.coffee index 0e134582401..a4366fd638f 100644 --- a/packages/rocketchat-authorization/client/views/permissions.coffee +++ b/packages/rocketchat-authorization/client/views/permissions.coffee @@ -31,11 +31,8 @@ Template.permissions.onCreated -> added: {} removed: {} - subs = @subscribe 'roles' - Tracker.autorun => - if subs.ready() - @roles.set Roles.getAllRoles().fetch() + @roles.set RocketChat.models.Roles.find().fetch() Tracker.autorun => if subs.ready() diff --git a/packages/rocketchat-authorization/client/views/permissionsRole.coffee b/packages/rocketchat-authorization/client/views/permissionsRole.coffee index bb754ced89c..a15e33587f5 100644 --- a/packages/rocketchat-authorization/client/views/permissionsRole.coffee +++ b/packages/rocketchat-authorization/client/views/permissionsRole.coffee @@ -108,4 +108,4 @@ Template.permissionsRole.onCreated -> @subscribe 'roles', FlowRouter.getParam('name') @subscribe 'usersInRole', FlowRouter.getParam('name') - @usersInRole = Roles.getUsersInRole(FlowRouter.getParam('name'), Roles.GLOBAL_GROUP, { sort: { username: 1 } }) + @usersInRole = RocketChat.models.Roles.findUsersInRole(FlowRouter.getParam('name'), null, { sort: { username: 1 } }) diff --git a/packages/rocketchat-authorization/lib/roles.js b/packages/rocketchat-authorization/lib/roles.js deleted file mode 100644 index daa8bf6d904..00000000000 --- a/packages/rocketchat-authorization/lib/roles.js +++ /dev/null @@ -1,713 +0,0 @@ -;(function () { - -/** - * Provides functions related to user authorization. Compatible with built-in Meteor accounts packages. - * - * @module Roles - */ - -/** - * Roles collection documents consist only of an id and a role name. - * ex: { _id:, name: "admin" } - */ -if (!Meteor.roles) { - Meteor.roles = new Mongo.Collection("roles") -} - -/** - * Authorization package compatible with built-in Meteor accounts system. - * - * Stores user's current roles in a 'roles' field on the user object. - * - * @class Roles - * @constructor - */ -if ('undefined' === typeof Roles) { - Roles = {} -} - -"use strict"; - -var mixingGroupAndNonGroupErrorMsg = "Roles error: Can't mix grouped and non-grouped roles for same user"; - -_.extend(Roles, { - - /** - * Constant used to reference the special 'global' group that - * can be used to apply blanket permissions across all groups. - * - * @example - * Roles.addUsersToRoles(user, 'admin', Roles.GLOBAL_GROUP) - * Roles.userIsInRole(user, 'admin') // => true - * - * Roles.setUserRoles(user, 'support-staff', Roles.GLOBAL_GROUP) - * Roles.userIsInRole(user, 'support-staff') // => true - * Roles.userIsInRole(user, 'admin') // => false - * - * @property GLOBAL_GROUP - * @type String - * @static - * @final - */ - GLOBAL_GROUP: '__global_roles__', - - - /** - * Create a new role. Whitespace will be trimmed. - * - * @method createRole - * @param {String} role Name of role - * @param {String} collection Name of collection storing roles - * @return {String} id of new role - */ - createRole: function (role, collection) { - var id, match; - - if (!role || 'string' !== typeof role || 'string' !== typeof collection || role.trim().length === 0 || collection.trim().length === 0) { - return - } - - try { - id = Meteor.roles.insert({'name': role.trim(), 'collection': collection.trim()}) - return id - } catch (e) { - // (from Meteor accounts-base package, insertUserDoc func) - // XXX string parsing sucks, maybe - // https://jira.mongodb.org/browse/SERVER-3069 will get fixed one day - if (e.name !== 'MongoError') throw e - match = e.err.match(/^E11000 duplicate key error index: ([^ ]+)/) - if (!match) throw e - if (match[1].indexOf('$name') !== -1) - throw new Meteor.Error(403, "Role already exists.") - throw e - } - }, - - /** - * Delete an existing role. Will throw "Role in use" error if any users - * are currently assigned to the target role. - * - * @method deleteRole - * @param {String} role Name of role - */ - deleteRole: function (role) { - if (!role) return - - var thisRole = Meteor.roles.findOne({name: role}) - if (thisRole) { - if (Meteor.isServer && "undefined" !== typeof RocketChat.models[thisRole.collection] && "function" === typeof RocketChat.models[thisRole.collection].getUsersInRole ) { - var users = RocketChat.models[thisRole.collection].getUsersInRole(role) - var foundExistingUser = users.length !== 0 - if (foundExistingUser) { - throw new Meteor.Error(403, 'Role in use') - } - } - Meteor.roles.remove({_id: thisRole._id}) - } - }, - - /** - * Add users to roles. Will create roles as needed. - * - * NOTE: Mixing grouped and non-grouped roles for the same user - * is not supported and will throw an error. - * - * Makes 2 calls to database: - * 1. retrieve list of all existing roles - * 2. update users' roles - * - * @example - * Roles.addUsersToRoles(userId, 'admin') - * Roles.addUsersToRoles(userId, ['view-secrets'], 'example.com') - * Roles.addUsersToRoles([user1, user2], ['user','editor']) - * Roles.addUsersToRoles([user1, user2], ['glorious-admin', 'perform-action'], 'example.org') - * Roles.addUsersToRoles(userId, 'admin', Roles.GLOBAL_GROUP) - * - * @method addUsersToRoles - * @param {Array|String} users User id(s) or object(s) with an _id field - * @param {Array|String} roles Name(s) of roles/permissions to add users to - * @param {String} [group] Optional group name. If supplied, roles will be - * specific to that group. - * Group names can not start with a '$' or contain - * null characters. Periods in names '.' are - * automatically converted to underscores. - * The special group Roles.GLOBAL_GROUP provides - * a convenient way to assign blanket roles/permissions - * across all groups. The roles/permissions in the - * Roles.GLOBAL_GROUP group will be automatically - * included in checks for any group. - */ - addUsersToRoles: function (users, roles, group) { - // use Template pattern to update user roles - Roles._updateUserRoles(users, roles, group, Roles._update_$addToSet_fn) - }, - - /** - * Set a users roles/permissions. - * - * @example - * Roles.setUserRoles(userId, 'admin') - * Roles.setUserRoles(userId, ['view-secrets'], 'example.com') - * Roles.setUserRoles([user1, user2], ['user','editor']) - * Roles.setUserRoles([user1, user2], ['glorious-admin', 'perform-action'], 'example.org') - * Roles.setUserRoles(userId, 'admin', Roles.GLOBAL_GROUP) - * - * @method setUserRoles - * @param {Array|String} users User id(s) or object(s) with an _id field - * @param {Array|String} roles Name(s) of roles/permissions to add users to - * @param {String} [group] Optional group name. If supplied, roles will be - * specific to that group. - * Group names can not start with '$'. - * Periods in names '.' are automatically converted - * to underscores. - * The special group Roles.GLOBAL_GROUP provides - * a convenient way to assign blanket roles/permissions - * across all groups. The roles/permissions in the - * Roles.GLOBAL_GROUP group will be automatically - * included in checks for any group. - */ - setUserRoles: function (users, roles, group) { - // use Template pattern to update user roles - Roles._updateUserRoles(users, roles, group, Roles._update_$set_fn) - }, - - /** - * Remove users from roles - * - * @example - * Roles.removeUsersFromRoles(users.bob, 'admin') - * Roles.removeUsersFromRoles([users.bob, users.joe], ['editor']) - * Roles.removeUsersFromRoles([users.bob, users.joe], ['editor', 'user']) - * Roles.removeUsersFromRoles(users.eve, ['user'], 'group1') - * - * @method removeUsersFromRoles - * @param {Array|String} users User id(s) or object(s) with an _id field - * @param {Array|String} roles Name(s) of roles to add users to - * @param {String} [group] Optional. Group name. If supplied, only that - * group will have roles removed. - */ - removeUsersFromRoles: function (users, roles, group) { - var update - - if (!users) throw new Error ("Missing 'users' param") - if (!roles) throw new Error ("Missing 'roles' param") - if (group) { - if ('string' !== typeof group) - throw new Error ("Roles error: Invalid parameter 'group'. Expected 'string' type") - if ('$' === group[0]) - throw new Error ("Roles error: groups can not start with '$'") - - // convert any periods to underscores - group = group.replace(/\./g, '_') - } - - // ensure arrays - if (!_.isArray(users)) users = [users] - if (!_.isArray(roles)) roles = [roles] - - // ensure users is an array of user ids - users = _.reduce(users, function (memo, user) { - var _id - if ('string' === typeof user) { - memo.push(user) - } else if ('object' === typeof user) { - _id = user._id - if ('string' === typeof _id) { - memo.push(_id) - } - } - return memo - }, []) - - // update all users, remove from roles set - - if (group) { - update = {$pullAll: {}} - update.$pullAll['roles.'+group] = roles - } else { - update = {$pullAll: {roles: roles}} - } - - try { - if (Meteor.isClient) { - // Iterate over each user to fulfill Meteor's 'one update per ID' policy - _.each(users, function (user) { - Meteor.users.update({_id:user}, update) - }) - } else { - // On the server we can leverage MongoDB's $in operator for performance - Meteor.users.update({_id:{$in:users}}, update, {multi: true}) - } - } - catch (ex) { - if (ex.name === 'MongoError' && isMongoMixError(ex.err)) { - throw new Error (mixingGroupAndNonGroupErrorMsg) - } - - throw ex - } - }, - - /** - * Check if user has specified permissions/roles - * - * @example - * // non-group usage - * Roles.userIsInRole(user, 'admin') - * Roles.userIsInRole(user, ['admin','editor']) - * Roles.userIsInRole(userId, 'admin') - * Roles.userIsInRole(userId, ['admin','editor']) - * - * // per-group usage - * Roles.userIsInRole(user, ['admin','editor'], 'group1') - * Roles.userIsInRole(userId, ['admin','editor'], 'group1') - * Roles.userIsInRole(userId, ['admin','editor'], Roles.GLOBAL_GROUP) - * - * // this format can also be used as short-hand for Roles.GLOBAL_GROUP - * Roles.userIsInRole(user, 'admin') - * - * @method userIsInRole - * @param {String|Object} user User Id or actual user object - * @param {String|Array} roles Name of role/permission or Array of - * roles/permissions to check against. If array, - * will return true if user is in _any_ role. - * @param {String} [group] Optional. Name of group. If supplied, limits check - * to just that group. - * The user's Roles.GLOBAL_GROUP will always be checked - * whether group is specified or not. - * @return {Boolean} true if user is in _any_ of the target roles - */ - userIsInRole: function (user, roles, group) { - var id, - userRoles, - query, - groupQuery, - found = false - - // ensure array to simplify code - if (!_.isArray(roles)) { - roles = [roles] - } - - if (!user) return false - if (group) { - if ('string' !== typeof group) return false - if ('$' === group[0]) return false - - // convert any periods to underscores - group = group.replace(/\./g, '_') - } - - if ('object' === typeof user) { - userRoles = user.roles - if (_.isArray(userRoles)) { - return _.some(roles, function (role) { - return _.contains(userRoles, role) - }) - } else if ('object' === typeof userRoles) { - // roles field is dictionary of groups - found = _.isArray(userRoles[group]) && _.some(roles, function (role) { - return _.contains(userRoles[group], role) - }) - if (!found) { - // not found in regular group or group not specified. - // check Roles.GLOBAL_GROUP, if it exists - found = _.isArray(userRoles[Roles.GLOBAL_GROUP]) && _.some(roles, function (role) { - return _.contains(userRoles[Roles.GLOBAL_GROUP], role) - }) - } - return found - } - - // missing roles field, try going direct via id - id = user._id - } else if ('string' === typeof user) { - id = user - } - - if (!id) return false - - - query = {_id: id, $or: []} - - // always check Roles.GLOBAL_GROUP - groupQuery = {} - groupQuery['roles.'+Roles.GLOBAL_GROUP] = {$in: roles} - query.$or.push(groupQuery) - - if (group) { - // structure of query, when group specified including Roles.GLOBAL_GROUP - // {_id: id, - // $or: [ - // {'roles.group1':{$in: ['admin']}}, - // {'roles.__global_roles__':{$in: ['admin']}} - // ]} - groupQuery = {} - groupQuery['roles.'+group] = {$in: roles} - query.$or.push(groupQuery) - } else { - // structure of query, where group not specified. includes - // Roles.GLOBAL_GROUP - // {_id: id, - // $or: [ - // {roles: {$in: ['admin']}}, - // {'roles.__global_roles__': {$in: ['admin']}} - // ]} - query.$or.push({roles: {$in: roles}}) - } - - found = Meteor.users.findOne(query, {fields: {_id: 1}}) - return found ? true : false - }, - - /** - * Retrieve users roles - * - * @method getRolesForUser - * @param {String|Object} user User Id or actual user object - * @param {String} [group] Optional name of group to restrict roles to. - * User's Roles.GLOBAL_GROUP will also be included. - * @return {Array} Array of user's roles, unsorted. - */ - getRolesForUser: function (user, group) { - if (!user) return [] - if (group) { - if ('string' !== typeof group) return [] - if ('$' === group[0]) return [] - - // convert any periods to underscores - group = group.replace(/\./g, '_') - } - - if ('string' === typeof user) { - user = Meteor.users.findOne( - {_id: user}, - {fields: {roles: 1}}) - - } else if ('object' !== typeof user) { - // invalid user object - return [] - } - - if (!user || !user.roles) return [] - - if (group) { - return _.union(user.roles[group] || [], user.roles[Roles.GLOBAL_GROUP] || []) - } - - if (_.isArray(user.roles)) - return user.roles - - // using groups but group not specified. return global group, if exists - return user.roles[Roles.GLOBAL_GROUP] || [] - }, - - /** - * Retrieve set of all existing roles - * - * @method getAllRoles - * @return {Cursor} cursor of existing roles - */ - getAllRoles: function () { - return Meteor.roles.find({}, {sort: {name: 1}}) - }, - - /** - * Retrieve all users who are in target role. - * - * NOTE: This is an expensive query; it performs a full collection scan - * on the users collection since there is no index set on the 'roles' field. - * This is by design as most queries will specify an _id so the _id index is - * used automatically. - * - * @method getUsersInRole - * @param {Array|String} role Name of role/permission. If array, users - * returned will have at least one of the roles - * specified but need not have _all_ roles. - * @param {String} [group] Optional name of group to restrict roles to. - * User's Roles.GLOBAL_GROUP will also be checked. - * @param {Object} [options] Optional options which are passed directly - * through to `Meteor.users.find(query, options)` - * @return {Cursor} cursor of users in role - */ - getUsersInRole: function (role, group, options) { - var query, - roles = role, - groupQuery - - // ensure array to simplify query logic - if (!_.isArray(roles)) roles = [roles] - - if (group) { - if ('string' !== typeof group) - throw new Error ("Roles error: Invalid parameter 'group'. Expected 'string' type") - if ('$' === group[0]) - throw new Error ("Roles error: groups can not start with '$'") - - // convert any periods to underscores - group = group.replace(/\./g, '_') - } - - query = {$or: []} - - // always check Roles.GLOBAL_GROUP - groupQuery = {} - groupQuery['roles.'+Roles.GLOBAL_GROUP] = {$in: roles} - query.$or.push(groupQuery) - - if (group) { - // structure of query, when group specified including Roles.GLOBAL_GROUP - // { - // $or: [ - // {'roles.group1':{$in: ['admin']}}, - // {'roles.__global_roles__':{$in: ['admin']}} - // ]} - groupQuery = {} - groupQuery['roles.'+group] = {$in: roles} - query.$or.push(groupQuery) - } else { - // structure of query, where group not specified. includes - // Roles.GLOBAL_GROUP - // { - // $or: [ - // {roles: {$in: ['admin']}}, - // {'roles.__global_roles__': {$in: ['admin']}} - // ]} - query.$or.push({roles: {$in: roles}}) - } - - return Meteor.users.find(query, options); - }, // end getUsersInRole - - /** - * Retrieve users groups, if any - * - * @method getGroupsForUser - * @param {String|Object} user User Id or actual user object - * @param {String} [role] Optional name of roles to restrict groups to. - * - * @return {Array} Array of user's groups, unsorted. Roles.GLOBAL_GROUP will be omitted - */ - getGroupsForUser: function (user, role) { - var userGroups = []; - - if (!user) return [] - if (role) { - if ('string' !== typeof role) return [] - if ('$' === role[0]) return [] - - // convert any periods to underscores - role = role.replace('.', '_') - } - - if ('string' === typeof user) { - user = Meteor.users.findOne( - {_id: user}, - {fields: {roles: 1}}) - - }else if ('object' !== typeof user) { - // invalid user object - return [] - } - - //User has no roles or is not using groups - if (!user || !user.roles || _.isArray(user.roles)) return [] - - if (role) { - _.each(user.roles, function(groupRoles, groupName) { - if (_.contains(groupRoles, role) && groupName !== Roles.GLOBAL_GROUP) { - userGroups.push(groupName); - } - }); - return userGroups; - }else { - return _.without(_.keys(user.roles), Roles.GLOBAL_GROUP); - } - - }, //End getGroupsForUser - - - /** - * Private function 'template' that uses $set to construct an update object - * for MongoDB. Passed to _updateUserRoles - * - * @method _update_$set_fn - * @protected - * @param {Array} roles - * @param {String} [group] - * @return {Object} update object for use in MongoDB update command - */ - _update_$set_fn: function (roles, group) { - var update = {} - - if (group) { - // roles is a key/value dict object - update.$set = {} - update.$set['roles.' + group] = roles - } else { - // roles is an array of strings - update.$set = {roles: roles} - } - - return update - }, // end _update_$set_fn - - /** - * Private function 'template' that uses $addToSet to construct an update - * object for MongoDB. Passed to _updateUserRoles - * - * @method _update_$addToSet_fn - * @protected - * @param {Array} roles - * @param {String} [group] - * @return {Object} update object for use in MongoDB update command - */ - _update_$addToSet_fn: function (roles, group) { - var update = {} - - if (group) { - // roles is a key/value dict object - update.$addToSet = {} - update.$addToSet['roles.' + group] = {$each: roles} - } else { - // roles is an array of strings - update.$addToSet = {roles: {$each: roles}} - } - - return update - }, // end _update_$addToSet_fn - - - /** - * Internal function that uses the Template pattern to adds or sets roles - * for users. - * - * @method _updateUserRoles - * @protected - * @param {Array|String} users user id(s) or object(s) with an _id field - * @param {Array|String} roles name(s) of roles/permissions to add users to - * @param {String} group Group name. If not null or undefined, roles will be - * specific to that group. - * Group names can not start with '$'. - * Periods in names '.' are automatically converted - * to underscores. - * The special group Roles.GLOBAL_GROUP provides - * a convenient way to assign blanket roles/permissions - * across all groups. The roles/permissions in the - * Roles.GLOBAL_GROUP group will be automatically - * included in checks for any group. - * @param {Function} updateFactory Func which returns an update object that - * will be passed to Mongo. - * @param {Array} roles - * @param {String} [group] - */ - _updateUserRoles: function (users, roles, group, updateFactory) { - if (!users) throw new Error ("Missing 'users' param") - if (!roles) throw new Error ("Missing 'roles' param") - if (group) { - if ('string' !== typeof group) - throw new Error ("Roles error: Invalid parameter 'group'. Expected 'string' type") - if ('$' === group[0]) - throw new Error ("Roles error: groups can not start with '$'") - - // convert any periods to underscores - group = group.replace(/\./g, '_') - } - - var existingRoles, - query, - update - - // ensure arrays to simplify code - if (!_.isArray(users)) users = [users] - if (!_.isArray(roles)) roles = [roles] - - // remove invalid roles - roles = _.reduce(roles, function (memo, role) { - if (role - && 'string' === typeof role - && role.trim().length > 0) { - memo.push(role.trim()) - } - return memo - }, []) - - // empty roles array is ok, since it might be a $set operation to clear roles - //if (roles.length === 0) return - - // ensure all roles exist in 'roles' collection - existingRoles = _.reduce(Meteor.roles.find({}).fetch(), function (memo, role) { - memo[role.name] = true - return memo - }, {}) - _.each(roles, function (role) { - if (!existingRoles[role]) { - Roles.createRole(role) - } - }) - - // ensure users is an array of user ids - users = _.reduce(users, function (memo, user) { - var _id - if ('string' === typeof user) { - memo.push(user) - } else if ('object' === typeof user) { - _id = user._id - if ('string' === typeof _id) { - memo.push(_id) - } - } - return memo - }, []) - - // update all users - update = updateFactory(roles, group) - - try { - if (Meteor.isClient) { - // On client, iterate over each user to fulfill Meteor's - // 'one update per ID' policy - _.each(users, function (user) { - Meteor.users.update({_id: user}, update) - }) - } else { - // On the server we can use MongoDB's $in operator for - // better performance - Meteor.users.update( - {_id: {$in: users}}, - update, - {multi: true}) - } - } - catch (ex) { - if (ex.name === 'MongoError' && isMongoMixError(ex.err)) { - throw new Error (mixingGroupAndNonGroupErrorMsg) - } - - throw ex - } - } // end _updateUserRoles - -}) // end _.extend(Roles ...) - - -function isMongoMixError (errorMsg) { - var expectedMessages = [ - 'Cannot apply $addToSet modifier to non-array', - 'Cannot apply $addToSet to a non-array field', - 'Can only apply $pullAll to an array', - 'Cannot apply $pull/$pullAll modifier to non-array', - "can't append to array using string field name", - 'to traverse the element' - ] - - return _.some(expectedMessages, function (snippet) { - return strContains(errorMsg, snippet) - }) -} - -function strContains (haystack, needle) { - return -1 !== haystack.indexOf(needle) -} - -}()); diff --git a/packages/rocketchat-authorization/package.js b/packages/rocketchat-authorization/package.js index 01c08fcba8e..bc0361b701d 100644 --- a/packages/rocketchat-authorization/package.js +++ b/packages/rocketchat-authorization/package.js @@ -21,14 +21,12 @@ Package.onUse(function(api) { api.use('templating', 'client'); - // roles - api.addFiles('server/roles.js', ['server']); - api.addFiles('lib/roles.js', ['client', 'server']); - api.addFiles('client/roles.js', ['client']); - api.addFiles('server/models/Users.js', ['server']); - api.addFiles('lib/rocketchat.coffee', ['server','client']); - api.addFiles('client/collection.coffee', ['client']); + api.addFiles('client/lib/ChatPermissions.coffee', ['client']); + api.addFiles('client/lib/models/Roles.coffee', ['client']); + api.addFiles('client/lib/models/Users.js', ['client']); + api.addFiles('client/lib/models/Subscriptions.js', ['client']); + api.addFiles('client/startup.coffee', ['client']); api.addFiles('client/hasPermission.coffee', ['client']); api.addFiles('client/hasRole.coffee', ['client']); @@ -45,19 +43,22 @@ Package.onUse(function(api) { api.addFiles('client/stylesheets/permissions.less', 'client'); api.addFiles('server/models/Permissions.coffee', ['server']); + api.addFiles('server/models/Roles.coffee', ['server']); + api.addFiles('server/models/Users.js', ['server']); + api.addFiles('server/models/Subscriptions.js', ['server']); - api.addFiles('server/functions/addUsersToRoles.coffee', ['server']); + api.addFiles('server/functions/addUserRoles.coffee', ['server']); api.addFiles('server/functions/getPermissionsForRole.coffee', ['server']); api.addFiles('server/functions/getRoles.coffee', ['server']); - api.addFiles('server/functions/getRolesForUser.coffee', ['server']); api.addFiles('server/functions/getUsersInRole.coffee', ['server']); api.addFiles('server/functions/hasPermission.coffee', ['server']); api.addFiles('server/functions/hasRole.coffee', ['server']); - api.addFiles('server/functions/removeUsersFromRoles.coffee', ['server']); + api.addFiles('server/functions/removeUserFromRoles.coffee', ['server']); // publications - api.addFiles('server/publication.coffee', ['server']); + api.addFiles('server/publications/permissions.js', 'server'); api.addFiles('server/publications/roles.coffee', 'server'); + api.addFiles('server/publications/scopedRoles.js', 'server'); api.addFiles('server/publications/usersInRole.coffee', 'server'); // methods @@ -79,6 +80,4 @@ Package.onUse(function(api) { })); api.use('tap:i18n'); api.addFiles(tapi18nFiles); - - api.export('Roles', 'server'); }); diff --git a/packages/rocketchat-authorization/server/functions/addUserRoles.coffee b/packages/rocketchat-authorization/server/functions/addUserRoles.coffee new file mode 100644 index 00000000000..e3635f6b4ef --- /dev/null +++ b/packages/rocketchat-authorization/server/functions/addUserRoles.coffee @@ -0,0 +1,19 @@ +RocketChat.authz.addUserRoles = (userId, roleNames, scope) -> + if not userId or not roleNames + return false + + user = RocketChat.models.Users.findOneById(userId) + if not user + throw new Meteor.Error 'invalid-user' + + roleNames = [].concat roleNames + + existingRoleNames = _.pluck(RocketChat.authz.getRoles(), 'name') + invalidRoleNames = _.difference(roleNames, existingRoleNames) + unless _.isEmpty(invalidRoleNames) + for role in invalidRoleNames + RocketChat.models.Roles.createOrUpdate role + + RocketChat.models.Roles.addUserRoles(userId, roleNames, scope) + + return true diff --git a/packages/rocketchat-authorization/server/functions/addUsersToRoles.coffee b/packages/rocketchat-authorization/server/functions/addUsersToRoles.coffee deleted file mode 100644 index ac2be57a546..00000000000 --- a/packages/rocketchat-authorization/server/functions/addUsersToRoles.coffee +++ /dev/null @@ -1,27 +0,0 @@ -RocketChat.authz.addUsersToRoles = (userIds, roleNames, scope ) -> - if not userIds or not roleNames - return false - - unless _.isArray(userIds) - userIds = [userIds] - - users = Meteor.users.find({_id: {$in : userIds}}).fetch() - unless userIds.length is users.length - throw new Meteor.Error 'invalid-user' - - unless _.isArray(roleNames) - roleNames = [roleNames] - - existingRoleNames = _.pluck(RocketChat.authz.getRoles().fetch(), 'name') - invalidRoleNames = _.difference( roleNames, existingRoleNames) - unless _.isEmpty(invalidRoleNames) - # throw new Meteor.Error 'invalid-role' - for role in invalidRoleNames - Roles.createRole role - - unless _.isString(scope) - scope = Roles.GLOBAL_GROUP - - Roles.addUsersToRoles( userIds, roleNames, scope) - - return true diff --git a/packages/rocketchat-authorization/server/functions/getPermissionsForRole.coffee b/packages/rocketchat-authorization/server/functions/getPermissionsForRole.coffee index 7023d120265..dd55acb226c 100644 --- a/packages/rocketchat-authorization/server/functions/getPermissionsForRole.coffee +++ b/packages/rocketchat-authorization/server/functions/getPermissionsForRole.coffee @@ -2,8 +2,8 @@ RocketChat.authz.getPermissionsForRole = (roleName) -> unless roleName throw new Meteor.Error 'invalid-role' - roleNames = _.pluck(RocketChat.authz.getRoles().fetch(), 'name') - unless roleName in roleNames + role = RocketChat.models.Roles.findOne roleName + if not role throw new Meteor.Error 'invalid-role', "Role #{roleName} not found" - return _.pluck(RocketChat.models.Permissions.findByRole( roleName ).fetch(), '_id') + return _.pluck(RocketChat.models.Permissions.findByRole(roleName).fetch(), '_id') diff --git a/packages/rocketchat-authorization/server/functions/getRoles.coffee b/packages/rocketchat-authorization/server/functions/getRoles.coffee index 37c3d2537fd..895dcc532c1 100644 --- a/packages/rocketchat-authorization/server/functions/getRoles.coffee +++ b/packages/rocketchat-authorization/server/functions/getRoles.coffee @@ -1,2 +1,2 @@ RocketChat.authz.getRoles = -> - return Roles.getAllRoles() \ No newline at end of file + return RocketChat.models.Roles.find().fetch() diff --git a/packages/rocketchat-authorization/server/functions/getRolesForUser.coffee b/packages/rocketchat-authorization/server/functions/getRolesForUser.coffee deleted file mode 100644 index 800473a08bf..00000000000 --- a/packages/rocketchat-authorization/server/functions/getRolesForUser.coffee +++ /dev/null @@ -1,6 +0,0 @@ -RocketChat.authz.getRolesForUser = (userId, scope) -> - # returns roles for the given scope as well as the global scope - unless scope - scope = Roles.GLOBAL_GROUP - - return Roles.getRolesForUser(userId, scope) diff --git a/packages/rocketchat-authorization/server/functions/getUsersInRole.coffee b/packages/rocketchat-authorization/server/functions/getUsersInRole.coffee index ee416c3e902..b8fc11a51fb 100644 --- a/packages/rocketchat-authorization/server/functions/getUsersInRole.coffee +++ b/packages/rocketchat-authorization/server/functions/getUsersInRole.coffee @@ -1,6 +1,2 @@ RocketChat.authz.getUsersInRole = (roleName, scope, options) -> - # alanning:roles doc says this is an expensive operation - unless _.isString(scope) - scope = Roles.GLOBAL_GROUP - - return Roles.getUsersInRole(roleName, scope, options) + return RocketChat.models.Roles.findUsersInRole(roleName, scope, options) diff --git a/packages/rocketchat-authorization/server/functions/hasPermission.coffee b/packages/rocketchat-authorization/server/functions/hasPermission.coffee index b7c39299c7e..7ae014453d0 100644 --- a/packages/rocketchat-authorization/server/functions/hasPermission.coffee +++ b/packages/rocketchat-authorization/server/functions/hasPermission.coffee @@ -1,10 +1,3 @@ RocketChat.authz.hasPermission = (userId, permissionId, scope) -> - # get user's roles - roles = RocketChat.authz.getRolesForUser(userId, scope) - - # get permissions for user's roles - permissions = [] - for role in roles - permissions = permissions.concat( RocketChat.authz.getPermissionsForRole( role )) - # may contain duplicate, but doesn't matter - return permissionId in permissions + permission = RocketChat.models.Permissions.findOne permissionId + return RocketChat.models.Roles.isUserInRoles(userId, permission.roles, scope) diff --git a/packages/rocketchat-authorization/server/functions/hasRole.coffee b/packages/rocketchat-authorization/server/functions/hasRole.coffee index 10ac10df442..aff335e48b2 100644 --- a/packages/rocketchat-authorization/server/functions/hasRole.coffee +++ b/packages/rocketchat-authorization/server/functions/hasRole.coffee @@ -1,3 +1,3 @@ -RocketChat.authz.hasRole = (userId, roleName, scope) -> - # per alanning:roles, returns true if user is in ANY roles - return Roles.userIsInRole(userId, [roleName], scope) +RocketChat.authz.hasRole = (userId, roleNames, scope) -> + roleNames = [].concat roleNames + return RocketChat.models.Roles.isUserInRoles(userId, roleNames, scope) # true if user is in ANY role diff --git a/packages/rocketchat-authorization/server/functions/removeUserFromRoles.coffee b/packages/rocketchat-authorization/server/functions/removeUserFromRoles.coffee new file mode 100644 index 00000000000..f05bcf70471 --- /dev/null +++ b/packages/rocketchat-authorization/server/functions/removeUserFromRoles.coffee @@ -0,0 +1,18 @@ +RocketChat.authz.removeUserFromRoles = (userId, roleNames, scope) -> + if not userId or not roleNames + return false + + user = RocketChat.models.Users.findOneById(userId) + if not user? + throw new Meteor.Error 'invalid-user' + + roleNames = [].concat roleNames + + existingRoleNames = _.pluck(RocketChat.authz.getRoles(), 'name') + invalidRoleNames = _.difference(roleNames, existingRoleNames) + unless _.isEmpty(invalidRoleNames) + throw new Meteor.Error 'invalid-role' + + RocketChat.models.Roles.removeUserFromRoles(userIds, roleNames, scope) + + return true diff --git a/packages/rocketchat-authorization/server/functions/removeUsersFromRoles.coffee b/packages/rocketchat-authorization/server/functions/removeUsersFromRoles.coffee deleted file mode 100644 index 93f78b6ccad..00000000000 --- a/packages/rocketchat-authorization/server/functions/removeUsersFromRoles.coffee +++ /dev/null @@ -1,25 +0,0 @@ -RocketChat.authz.removeUsersFromRoles = (userIds, roleNames, scope ) -> - if not userIds or not roleNames - return false - - unless _.isArray(userIds) - userIds = [userIds] - - users = Meteor.users.find({_id: {$in : userIds}}).fetch() - unless userIds.length is users.length - throw new Meteor.Error 'invalid-user' - - unless _.isArray(roleNames) - roleNames = [roleNames] - - existingRoleNames = _.pluck(RocketChat.authz.getRoles().fetch(), 'name') - invalidRoleNames = _.difference( roleNames, existingRoleNames) - unless _.isEmpty(invalidRoleNames) - throw new Meteor.Error 'invalid-role' - - unless _.isString(scope) - scope = Roles.GLOBAL_GROUP - - Roles.removeUsersFromRoles( userIds, roleNames, scope) - - return true diff --git a/packages/rocketchat-authorization/server/methods/addUserToRole.coffee b/packages/rocketchat-authorization/server/methods/addUserToRole.coffee index a5340768b51..74ba6bbea53 100644 --- a/packages/rocketchat-authorization/server/methods/addUserToRole.coffee +++ b/packages/rocketchat-authorization/server/methods/addUserToRole.coffee @@ -1,15 +1,14 @@ Meteor.methods - 'authorization:addUserToRole': (roleName, username) -> + 'authorization:addUserToRole': (roleName, username, scope) -> if not Meteor.userId() or not RocketChat.authz.hasPermission Meteor.userId(), 'access-permissions' throw new Meteor.Error "not-authorized" if not roleName or not _.isString(roleName) or not username or not _.isString(username) throw new Meteor.Error 'invalid-arguments' - user = Meteor.users.findOne { username: username }, { fields: { _id: 1 } } + user = RocketChat.models.Users.findOneByUsername username, { fields: { _id: 1 } } if not user?._id? throw new Meteor.Error 'user-not-found', 'User_not_found' - # return Roles.addUsersToRoles user._id, roleName - return Roles.addUsersToRoles user._id, roleName, Roles.GLOBAL_GROUP + return RocketChat.models.Roles.addUserRoles user._id, roleName, scope diff --git a/packages/rocketchat-authorization/server/methods/deleteRole.coffee b/packages/rocketchat-authorization/server/methods/deleteRole.coffee index 0a928cb252b..39ca53a3502 100644 --- a/packages/rocketchat-authorization/server/methods/deleteRole.coffee +++ b/packages/rocketchat-authorization/server/methods/deleteRole.coffee @@ -1,16 +1,19 @@ Meteor.methods - 'authorization:deleteRole': (_id) -> + 'authorization:deleteRole': (roleName) -> if not Meteor.userId() or not RocketChat.authz.hasPermission Meteor.userId(), 'access-permissions' throw new Meteor.Error "not-authorized" - role = Meteor.roles.findOne _id + role = RocketChat.models.Roles.findOne roleName + if not role? + throw new Meteor.Error 'invalid-role' if role.protected throw new Meteor.Error 'protected-role', 'Cannot_delete_a_protected_role' - someone = Meteor.users.findOne { "roles.#{Roles.GLOBAL_GROUP}": role.name } + roleScope = role.scope or 'Users' + existingUsers = RocketChat.models[roleScope]?.findUsersInRoles?(roleName) - if someone? + if existingUsers?.count() > 0 throw new Meteor.Error 'role-in-use', 'Cannot_delete_role_because_its_in_use' - return Roles.deleteRole role.name + return RocketChat.models.Roles.remove role.name diff --git a/packages/rocketchat-authorization/server/methods/removeUserFromRole.coffee b/packages/rocketchat-authorization/server/methods/removeUserFromRole.coffee index 6510c2f8b2b..bc5e0504266 100644 --- a/packages/rocketchat-authorization/server/methods/removeUserFromRole.coffee +++ b/packages/rocketchat-authorization/server/methods/removeUserFromRole.coffee @@ -11,4 +11,4 @@ Meteor.methods if not user?._id? throw new Meteor.Error 'user-not-found' - return Roles.removeUsersFromRoles user._id, roleName, Roles.GLOBAL_GROUP + return RocketChat.models.Roles.removeUserRole user._id, roleName diff --git a/packages/rocketchat-authorization/server/methods/saveRole.coffee b/packages/rocketchat-authorization/server/methods/saveRole.coffee index 7a30d9ecec6..2fe6f208e9a 100644 --- a/packages/rocketchat-authorization/server/methods/saveRole.coffee +++ b/packages/rocketchat-authorization/server/methods/saveRole.coffee @@ -9,7 +9,4 @@ Meteor.methods if not _id? and roleData.name? saveData.name = roleData.name - if _id? - return Meteor.roles.update _id, { $set: saveData } - else - return Meteor.roles.insert saveData + return RocketChat.models.Roles.createOrUpdate saveData.name, 'Users', roleData.description diff --git a/packages/rocketchat-authorization/server/models/Permissions.coffee b/packages/rocketchat-authorization/server/models/Permissions.coffee index 3e1c6eef96a..ec763d94b71 100644 --- a/packages/rocketchat-authorization/server/models/Permissions.coffee +++ b/packages/rocketchat-authorization/server/models/Permissions.coffee @@ -2,7 +2,6 @@ RocketChat.models.Permissions = new class extends RocketChat.models._Base constructor: -> @_initModel 'permissions' - # FIND findByRole: (role, options) -> query = diff --git a/packages/rocketchat-authorization/server/models/Roles.coffee b/packages/rocketchat-authorization/server/models/Roles.coffee new file mode 100644 index 00000000000..0ad0caee122 --- /dev/null +++ b/packages/rocketchat-authorization/server/models/Roles.coffee @@ -0,0 +1,42 @@ +RocketChat.models.Roles = new class extends RocketChat.models._Base + constructor: -> + @_initModel 'roles' + @tryEnsureIndex { 'name': 1 } + @tryEnsureIndex { 'scope': 1 } + + findUsersInRole: (name, scope, options) -> + role = @findOne name + roleScope = role?.scope or 'Users' + RocketChat.models[roleScope]?.findUsersInRoles?(name, scope, options) + + isUserInRoles: (userId, roles, scope) -> + roles = [].concat roles + _.some roles, (roleName) => + role = @findOne roleName + roleScope = role?.scope or 'Users' + return RocketChat.models[roleScope]?.isUserInRole?(userId, roleName, scope) + + createOrUpdate: (name, scope, description, protectedRole) -> + scope ?= 'Users' + updateData = {} + updateData.scope = scope + if description? + updateData.description = description + if protectedRole? + updateData.protected = protectedRole + + @upsert { _id: name }, { $set: updateData } + + addUserRoles: (userId, roles, scope) -> + roles = [].concat roles + for roleName in roles + role = @findOne roleName + roleScope = role?.scope or 'Users' + RocketChat.models[roleScope]?.addRolesByUserId?(userId, roleName, scope) + + removeUserRoles: (userId, roles, scope) -> + roles = [].concat roles + for roleName in roles + role = @findOne roleName + roleScope = role?.scope os 'Users' + RocketChat.models[roleScope]?.removeRolesByUserId?(userId, roleName, scope) diff --git a/packages/rocketchat-authorization/server/models/Subscriptions.js b/packages/rocketchat-authorization/server/models/Subscriptions.js new file mode 100644 index 00000000000..3d3048a10d3 --- /dev/null +++ b/packages/rocketchat-authorization/server/models/Subscriptions.js @@ -0,0 +1,91 @@ +RocketChat.models.Subscriptions.findRolesByUserId = function(userId) { + query = { + "u._id": userId + }; + + options = { fields: { roles: 1 } } + + return this.find(query, options); +} + +RocketChat.models.Subscriptions.isUserInRole = function(userId, roleName, roomId) { + query = { + "u._id": userId, + rid: roomId, + roles: roleName + }; + + return !_.isUndefined(this.findOne(query)); +} + +RocketChat.models.Subscriptions.setRolesByUserId = function(userId, roles, roomId) { + roles = [].concat(roles); + + var query = { + "u._id": userId, + rid: roomId + } + + var update = { + $set: { + roles: roles + } + } + + return this.update(query, update); +} + +RocketChat.models.Subscriptions.addRolesByUserId = function(userId, roles, roomId) { + roles = [].concat(roles); + + var query = { + "u._id": userId, + rid: roomId + } + + var update = { + $addToSet: { + roles: { $each: roles } + } + } + + return this.update(query, update); +} + +RocketChat.models.Subscriptions.removeRolesByUserId = function(userId, roles, roomId) { + roles = [].concat(roles); + + var query = { + "u._id": userId, + rid: roomId + } + + var update = { + $pullAll: { + roles: roles + } + } + + return this.update(query, update); +} + +RocketChat.models.Subscriptions.findUsersInRoles = function(roles, scope, options) { + roles = [].concat(roles); + + var query = { + roles: { $in: roles } + } + + if (scope) { + query.rid = scope; + } + + subscriptions = this.find(query).fetch(); + + users = _.compact(_.map(subscriptions, function(subscription) { + if ('undefined' !== typeof subscription.u && 'undefined' !== typeof subscription.u._id) + return subscription.u._id + })); + + return RocketChat.models.Users.find({ _id: { $in: users } }, options); +} diff --git a/packages/rocketchat-authorization/server/models/Users.js b/packages/rocketchat-authorization/server/models/Users.js index 0d04ea7ad14..ed88b6dbd20 100644 --- a/packages/rocketchat-authorization/server/models/Users.js +++ b/packages/rocketchat-authorization/server/models/Users.js @@ -1,13 +1,76 @@ -RocketChat.models.Users.findRolesByUserId = function(userId, options) { - query = { +RocketChat.models.Users.findRolesByUserId = function(userId) { + var query = { _id: userId }; - if ("object" !== typeof options) { - options = {} + options = { fields: { roles: 1 } } + + return this.find(query, options); +}; + +RocketChat.models.Users.isUserInRole = function(userId, roleName) { + query = { + _id: userId, + roles: roleName + }; + + return !_.isUndefined(this.findOne(query)); +} + +RocketChat.models.Users.setRolesByUserId = function(userId, roles) { + roles = [].concat(roles); + + var query = { + _id: userId + } + + var update = { + $set: { + roles: roles + } } - options.fields = { roles: 1 } + return this.update(query, update); +} + +RocketChat.models.Users.addRolesByUserId = function(userId, roles) { + roles = [].concat(roles); + + var query = { + _id: userId + } + + var update = { + $addToSet: { + roles: { $each: roles } + } + } + + return this.update(query, update); +} + +RocketChat.models.Users.removeRolesByUserId = function(userId, roles) { + roles = [].concat(roles); + + var query = { + _id: userId + } + + var update = { + $pullAll: { + roles: roles + } + } + + return this.update(query, update); +} + +RocketChat.models.Users.findUsersInRoles = function(roles, scope, options) { + roles = [].concat(roles); + + var query = { + roles: { $in: roles } + } return this.find(query, options); -}; +} diff --git a/packages/rocketchat-authorization/server/publication.coffee b/packages/rocketchat-authorization/server/publication.coffee deleted file mode 100644 index 9a4fa8cbfc1..00000000000 --- a/packages/rocketchat-authorization/server/publication.coffee +++ /dev/null @@ -1,2 +0,0 @@ -Meteor.publish 'permissions', -> - return RocketChat.models.Permissions.find {} diff --git a/packages/rocketchat-authorization/server/publications/permissions.js b/packages/rocketchat-authorization/server/publications/permissions.js new file mode 100644 index 00000000000..0872d3b4db0 --- /dev/null +++ b/packages/rocketchat-authorization/server/publications/permissions.js @@ -0,0 +1,3 @@ +Meteor.publish('permissions', function () { + return RocketChat.models.Permissions.find({}); +}); diff --git a/packages/rocketchat-authorization/server/publications/roles.coffee b/packages/rocketchat-authorization/server/publications/roles.coffee index 6357a62510e..b3eafb56d3e 100644 --- a/packages/rocketchat-authorization/server/publications/roles.coffee +++ b/packages/rocketchat-authorization/server/publications/roles.coffee @@ -2,7 +2,5 @@ Meteor.publish 'roles', -> unless @userId return @ready() - if not RocketChat.authz.hasPermission @userId, 'access-permissions' - throw new Meteor.Error "not-authorized" + return RocketChat.models.Roles.find() - return RocketChat.authz.getRoles() diff --git a/packages/rocketchat-authorization/server/publications/scopedRoles.js b/packages/rocketchat-authorization/server/publications/scopedRoles.js new file mode 100644 index 00000000000..bd6247697b0 --- /dev/null +++ b/packages/rocketchat-authorization/server/publications/scopedRoles.js @@ -0,0 +1,11 @@ +/** + * Publish logged-in user's roles so client-side checks can work. + */ +Meteor.publish('scopedRoles', function (scope) { + if (!this.userId || _.isUndefined(RocketChat.models[scope]) || !_.isFunction(RocketChat.models[scope].findRolesByUserId)) { + this.ready() + return + } + + return RocketChat.models[scope].findRolesByUserId(this.userId); +}); diff --git a/packages/rocketchat-authorization/server/roles.js b/packages/rocketchat-authorization/server/roles.js deleted file mode 100644 index fe98a742d34..00000000000 --- a/packages/rocketchat-authorization/server/roles.js +++ /dev/null @@ -1,28 +0,0 @@ -"use strict" - - -/** - * Roles collection documents consist only of an id and a role name. - * ex: { _id: "123", name: "admin" } - */ -if (!Meteor.roles) { - Meteor.roles = new Mongo.Collection("roles") - - // Create default indexes for roles collection - Meteor.roles._ensureIndex('name', {unique: 1}) -} - - -/** - * Publish logged-in user's roles (global) so client-side checks can work. - * - * Use a named publish function so clients can check `ready()` state. - */ -Meteor.publish('scope-roles', function (scope) { - if (!this.userId || "undefined" === typeof RocketChat.models[scope] || "function" !== typeof RocketChat.models[scope].findRolesByUserId) { - this.ready() - return - } - - return RocketChat.models[scope].findRolesByUserId(this.userId); -}); diff --git a/packages/rocketchat-authorization/server/startup.coffee b/packages/rocketchat-authorization/server/startup.coffee index 4bc8c430310..cd37963dcd4 100644 --- a/packages/rocketchat-authorization/server/startup.coffee +++ b/packages/rocketchat-authorization/server/startup.coffee @@ -7,7 +7,7 @@ Meteor.startup -> permissions = [ { _id: 'view-statistics', - roles : ['admin', 'temp-role']} + roles : ['admin']} { _id: 'view-privileged-setting', roles : ['admin']} @@ -34,7 +34,7 @@ Meteor.startup -> roles : ['admin']} { _id: 'edit-other-user-active-status', - roles : ['admin', 'site-moderator']} + roles : ['admin']} { _id: 'delete-user', roles : ['admin']} @@ -49,37 +49,37 @@ Meteor.startup -> roles : ['admin']} { _id: 'create-c', - roles : ['admin', 'site-moderator', 'user']} + roles : ['admin', 'user']} { _id: 'delete-c', - roles : ['admin', 'site-moderator']} + roles : ['admin']} { _id: 'edit-room', - roles : ['admin', 'site-moderator', 'moderator']} + roles : ['admin', 'moderator']} { _id: 'edit-message', - roles : ['admin', 'site-moderator', 'moderator']} + roles : ['admin', 'moderator']} { _id: 'delete-message', - roles : ['admin', 'site-moderator', 'moderator']} + roles : ['admin', 'moderator']} { _id: 'remove-user', - roles : ['admin', 'site-moderator', 'moderator']} + roles : ['admin', 'moderator']} { _id: 'mute-user', - roles : ['admin', 'site-moderator', 'moderator']} + roles : ['admin', 'moderator']} { _id: 'ban-user', - roles : ['admin', 'site-moderator', 'moderator']} + roles : ['admin', 'moderator']} { _id: 'create-p', - roles : ['admin', 'site-moderator', 'user']} + roles : ['admin', 'user']} { _id: 'delete-p', - roles : ['admin', 'site-moderator']} + roles : ['admin']} { _id: 'delete-d', - roles : ['admin', 'site-moderator']} + roles : ['admin']} { _id: 'bulk-register-user', roles : ['admin']} @@ -88,13 +88,13 @@ Meteor.startup -> roles : ['admin']} { _id: 'view-c-room', - roles : ['admin', 'site-moderator', 'user']} + roles : ['admin', 'user']} { _id: 'view-p-room', - roles : ['admin', 'site-moderator', 'user']} + roles : ['admin', 'user']} { _id: 'view-d-room', - roles : ['admin', 'site-moderator', 'user']} + roles : ['admin', 'user']} { _id: 'access-permissions', roles : ['admin']} @@ -109,7 +109,6 @@ Meteor.startup -> for permission in permissions RocketChat.models.Permissions.upsert( permission._id, {$set: permission }) - roles = _.pluck(Roles.getAllRoles().fetch(), 'name'); defaultRoles = [ { name: 'admin', scope: 'Users' } { name: 'moderator', scope: 'Subscriptions' } @@ -118,5 +117,4 @@ Meteor.startup -> ] for role in defaultRoles - unless role.name in roles - Roles.createRole role + RocketChat.models.Roles.createOrUpdate role.name, role.scope diff --git a/packages/rocketchat-integrations/server/methods/incoming/addIncomingIntegration.coffee b/packages/rocketchat-integrations/server/methods/incoming/addIncomingIntegration.coffee index 67c33d156f6..54efaf76006 100644 --- a/packages/rocketchat-integrations/server/methods/incoming/addIncomingIntegration.coffee +++ b/packages/rocketchat-integrations/server/methods/incoming/addIncomingIntegration.coffee @@ -61,7 +61,7 @@ Meteor.methods RocketChat.models.Users.update {_id: user._id}, updateObj - Roles.addUsersToRoles user._id, 'bot', 'bot' + RocketChat.models.Roles.addUserRoles user._id, 'bot' integration._id = RocketChat.models.Integrations.insert integration diff --git a/packages/rocketchat-integrations/server/methods/incoming/updateIncomingIntegration.coffee b/packages/rocketchat-integrations/server/methods/incoming/updateIncomingIntegration.coffee index da7aa3dd15f..59a93bdbbaf 100644 --- a/packages/rocketchat-integrations/server/methods/incoming/updateIncomingIntegration.coffee +++ b/packages/rocketchat-integrations/server/methods/incoming/updateIncomingIntegration.coffee @@ -38,7 +38,7 @@ Meteor.methods throw new Meteor.Error 'channel_does_not_exists', "[methods] updateIncomingIntegration -> The channel does not exists" user = RocketChat.models.Users.findOne({username: currentIntegration.username}) - Roles.addUsersToRoles user._id, 'bot', 'bot' + RocketChat.models.Roles.addUserRoles user._id, 'bot' RocketChat.models.Integrations.update integrationId, $set: diff --git a/packages/rocketchat-lib/server/methods/setAdminStatus.coffee b/packages/rocketchat-lib/server/methods/setAdminStatus.coffee index b81d6d9f04f..542c5853fd0 100644 --- a/packages/rocketchat-lib/server/methods/setAdminStatus.coffee +++ b/packages/rocketchat-lib/server/methods/setAdminStatus.coffee @@ -7,8 +7,8 @@ Meteor.methods throw new Meteor.Error 'not-authorized', '[methods] setAdminStatus -> Not authorized' if admin - RocketChat.authz.addUsersToRoles( userId, 'admin') + RocketChat.authz.addUserRoles( userId, 'admin') else - RocketChat.authz.removeUsersFromRoles( userId, 'admin') + RocketChat.authz.removeUserFromRoles( userId, 'admin') return true diff --git a/packages/rocketchat-livechat/permissions.js b/packages/rocketchat-livechat/permissions.js index ea678f9abe6..ad5a5a3c843 100644 --- a/packages/rocketchat-livechat/permissions.js +++ b/packages/rocketchat-livechat/permissions.js @@ -1,13 +1,13 @@ Meteor.startup(() => { - var roles = _.pluck(Roles.getAllRoles().fetch(), 'name'); + var roles = _.pluck(RocketChat.models.Roles.find().fetch(), 'name'); if (roles.indexOf('livechat-agent') === -1) { - Roles.createRole('livechat-agent'); + RocketChat.models.Roles.createOrUpdate('livechat-agent'); } if (roles.indexOf('livechat-manager') === -1) { - Roles.createRole('livechat-manager'); + RocketChat.models.Roles.createOrUpdate('livechat-manager'); } if (roles.indexOf('livechat-guest') === -1) { - Roles.createRole('livechat-guest'); + RocketChat.models.Roles.createOrUpdate('livechat-guest'); } if (RocketChat.models && RocketChat.models.Permissions) { RocketChat.models.Permissions.createOrUpdate('view-l-room', ['livechat-agent', 'livechat-manager', 'admin']); diff --git a/packages/rocketchat-livechat/server/methods/addAgent.js b/packages/rocketchat-livechat/server/methods/addAgent.js index cd924c14ce9..e236bc2c41a 100644 --- a/packages/rocketchat-livechat/server/methods/addAgent.js +++ b/packages/rocketchat-livechat/server/methods/addAgent.js @@ -14,7 +14,7 @@ Meteor.methods({ throw new Meteor.Error('user-not-found', 'Username_not_found'); } - if (RocketChat.authz.addUsersToRoles(user._id, 'livechat-agent')) { + if (RocketChat.authz.addUserRoles(user._id, 'livechat-agent')) { return RocketChat.models.Users.setOperator(user._id, true); } diff --git a/packages/rocketchat-livechat/server/methods/addManager.js b/packages/rocketchat-livechat/server/methods/addManager.js index eb0f905ec40..9f4af3713a4 100644 --- a/packages/rocketchat-livechat/server/methods/addManager.js +++ b/packages/rocketchat-livechat/server/methods/addManager.js @@ -14,6 +14,6 @@ Meteor.methods({ throw new Meteor.Error('user-not-found', 'Username_not_found'); } - return RocketChat.authz.addUsersToRoles(user._id, 'livechat-manager'); + return RocketChat.authz.addUserRoles(user._id, 'livechat-manager'); } }); diff --git a/packages/rocketchat-livechat/server/methods/removeAgent.js b/packages/rocketchat-livechat/server/methods/removeAgent.js index 141b7d3bf94..adbf4ae4773 100644 --- a/packages/rocketchat-livechat/server/methods/removeAgent.js +++ b/packages/rocketchat-livechat/server/methods/removeAgent.js @@ -14,7 +14,7 @@ Meteor.methods({ throw new Meteor.Error('user-not-found', 'Username_not_found'); } - if (RocketChat.authz.removeUsersFromRoles(user._id, 'livechat-agent')) { + if (RocketChat.authz.removeUserFromRoles(user._id, 'livechat-agent')) { return RocketChat.models.Users.setOperator(user._id, false); } diff --git a/packages/rocketchat-livechat/server/methods/removeManager.js b/packages/rocketchat-livechat/server/methods/removeManager.js index 32db557fd80..31ad6cd337b 100644 --- a/packages/rocketchat-livechat/server/methods/removeManager.js +++ b/packages/rocketchat-livechat/server/methods/removeManager.js @@ -12,6 +12,6 @@ Meteor.methods({ throw new Meteor.Error('user-not-found', 'Username_not_found'); } - return RocketChat.authz.removeUsersFromRoles(user._id, 'livechat-manager'); + return RocketChat.authz.removeUserFromRoles(user._id, 'livechat-manager'); } }); diff --git a/packages/rocketchat-livechat/server/models/Users.js b/packages/rocketchat-livechat/server/models/Users.js index 0c3d85af0a0..be4011d4f50 100644 --- a/packages/rocketchat-livechat/server/models/Users.js +++ b/packages/rocketchat-livechat/server/models/Users.js @@ -20,11 +20,9 @@ RocketChat.models.Users.setOperator = function(_id, operator) { RocketChat.models.Users.findOnlineAgents = function() { var query = { status: 'online', - roles: {} + roles: 'livechat-agent' }; - query.roles[Roles.GLOBAL_GROUP] = 'livechat-agent'; - return this.find(query); }; @@ -50,11 +48,10 @@ RocketChat.models.Users.findOnlineUserFromList = function(userList) { */ RocketChat.models.Users.getNextAgent = function() { var query = { - status: 'online' + status: 'online', + roles: 'livechat-agent' }; - query['roles.' + Roles.GLOBAL_GROUP] = 'livechat-agent'; - var collectionObj = this.model.rawCollection(); var findAndModify = Meteor.wrapAsync(collectionObj.findAndModify, collectionObj); diff --git a/packages/rocketchat-ui/lib/collections.coffee b/packages/rocketchat-ui/lib/collections.coffee index 4c77cee3d1a..a1f8bd13879 100644 --- a/packages/rocketchat-ui/lib/collections.coffee +++ b/packages/rocketchat-ui/lib/collections.coffee @@ -3,3 +3,8 @@ @ChatSubscription = new Meteor.Collection 'rocketchat_subscription' @UserAndRoom = new Meteor.Collection null @CachedChannelList = new Meteor.Collection null + +RocketChat.models.Users = _.extend {}, RocketChat.models.Users, Meteor.users +RocketChat.models.Subscriptions = _.extend {}, RocketChat.models.Subscriptions, @ChatSubscription +RocketChat.models.Rooms = _.extend {}, RocketChat.models.Rooms, @ChatRoom +RocketChat.models.Messages = _.extend {}, RocketChat.models.Messages, @ChatMessage diff --git a/server/lib/accounts.coffee b/server/lib/accounts.coffee index e68658e7dec..cc51761cb35 100644 --- a/server/lib/accounts.coffee +++ b/server/lib/accounts.coffee @@ -86,7 +86,7 @@ Accounts.insertUserDoc = _.wrap Accounts.insertUserDoc, (insertUserDoc, options, else roles.push 'user' - RocketChat.authz.addUsersToRoles(_id, roles) + RocketChat.authz.addUserRoles(_id, roles) RocketChat.callbacks.run 'afterCreateUser', options, user return _id diff --git a/server/methods/createChannel.coffee b/server/methods/createChannel.coffee index c2e8db8ce1d..98291882421 100644 --- a/server/methods/createChannel.coffee +++ b/server/methods/createChannel.coffee @@ -42,7 +42,7 @@ Meteor.methods ts: now # set creator as channel moderator. permission limited to channel by scoping to rid - RocketChat.authz.addUsersToRoles(Meteor.userId(), 'moderator', room._id) + RocketChat.authz.addUserRoles(Meteor.userId(), 'moderator', room._id) for username in members member = RocketChat.models.Users.findOneByUsername username diff --git a/server/methods/createPrivateGroup.coffee b/server/methods/createPrivateGroup.coffee index 8bfa8304d98..3a0dd23fa4c 100644 --- a/server/methods/createPrivateGroup.coffee +++ b/server/methods/createPrivateGroup.coffee @@ -34,7 +34,7 @@ Meteor.methods ts: now # set creator as group moderator. permission limited to group by scoping to rid - RocketChat.authz.addUsersToRoles(Meteor.userId(), 'moderator', room._id) + RocketChat.authz.addUserRoles(Meteor.userId(), 'moderator', room._id) for username in members member = RocketChat.models.Users.findOneByUsername(username, { fields: { username: 1 }}) diff --git a/server/methods/removeUserFromRoom.coffee b/server/methods/removeUserFromRoom.coffee index 78a5137fe2a..f45e43a5bab 100644 --- a/server/methods/removeUserFromRoom.coffee +++ b/server/methods/removeUserFromRoom.coffee @@ -18,7 +18,7 @@ Meteor.methods RocketChat.models.Subscriptions.removeByRoomIdAndUserId data.rid, removedUser._id if room.t in [ 'c', 'p' ] - RocketChat.authz.removeUsersFromRoles(removedUser._id; 'moderator', data.rid) + RocketChat.authz.removeUserFromRoles(removedUser._id; 'moderator', data.rid) fromUser = RocketChat.models.Users.findOneById fromId RocketChat.models.Messages.createUserRemovedWithRoomIdAndUser data.rid, removedUser, diff --git a/server/startup/initialData.coffee b/server/startup/initialData.coffee index 47ba296a93f..045b6d0673a 100644 --- a/server/startup/initialData.coffee +++ b/server/startup/initialData.coffee @@ -41,7 +41,7 @@ Meteor.startup -> name: 'Admin' Accounts.setPassword id, process.env.ADMIN_PASS - RocketChat.authz.addUsersToRoles( id, 'admin') + RocketChat.authz.addUserRoles( id, 'admin') else console.log 'E-mail exists; ignoring environment variables ADMIN_EMAIL and ADMIN_PASS'.red @@ -55,5 +55,5 @@ Meteor.startup -> # get oldest user oldestUser = RocketChat.models.Users.findOne({ _id: { $ne: 'rocket.cat' }}, { fields: { username: 1 }, sort: {createdAt: 1}}) if oldestUser - RocketChat.authz.addUsersToRoles( oldestUser._id, 'admin') + RocketChat.authz.addUserRoles( oldestUser._id, 'admin') console.log "No admins are found. Set #{oldestUser.username} as admin for being the oldest user" diff --git a/server/startup/migrations/v27.coffee b/server/startup/migrations/v27.coffee new file mode 100644 index 00000000000..3333a8896c7 --- /dev/null +++ b/server/startup/migrations/v27.coffee @@ -0,0 +1,11 @@ +Meteor.startup -> + Migrations.add + version: 27 + up: -> + RocketChat.models.Users.update({}, { $rename: { roles: '_roles' } }, { multi: true }) + + RocketChat.models.Users.find({ _roles: { $exists: 1 } }).forEach (user) -> + for scope, roles of user._roles + RocketChat.models.Roles.addUserRoles(user._id, roles, scope) + + # RocketChat.models.Users.update({}, { $unset: { _roles: 1 } }, { multi: true })