pull/1828/head
Marcelo Schmidt 10 years ago
parent ef83ce39dd
commit eaf025ec37
  1. 3
      client/startup/startup.coffee
  2. 15
      packages/rocketchat-authorization/README.md
  3. 44
      packages/rocketchat-authorization/client/hasPermission.coffee
  4. 11
      packages/rocketchat-authorization/client/hasRole.coffee
  5. 2
      packages/rocketchat-authorization/client/lib/ChatPermissions.coffee
  6. 14
      packages/rocketchat-authorization/client/lib/models/Roles.coffee
  7. 95
      packages/rocketchat-authorization/client/lib/models/Subscriptions.js
  8. 80
      packages/rocketchat-authorization/client/lib/models/Users.js
  9. 22
      packages/rocketchat-authorization/client/roles.js
  10. 3
      packages/rocketchat-authorization/client/startup.coffee
  11. 5
      packages/rocketchat-authorization/client/views/permissions.coffee
  12. 2
      packages/rocketchat-authorization/client/views/permissionsRole.coffee
  13. 713
      packages/rocketchat-authorization/lib/roles.js
  14. 25
      packages/rocketchat-authorization/package.js
  15. 19
      packages/rocketchat-authorization/server/functions/addUserRoles.coffee
  16. 27
      packages/rocketchat-authorization/server/functions/addUsersToRoles.coffee
  17. 6
      packages/rocketchat-authorization/server/functions/getPermissionsForRole.coffee
  18. 2
      packages/rocketchat-authorization/server/functions/getRoles.coffee
  19. 6
      packages/rocketchat-authorization/server/functions/getRolesForUser.coffee
  20. 6
      packages/rocketchat-authorization/server/functions/getUsersInRole.coffee
  21. 11
      packages/rocketchat-authorization/server/functions/hasPermission.coffee
  22. 6
      packages/rocketchat-authorization/server/functions/hasRole.coffee
  23. 18
      packages/rocketchat-authorization/server/functions/removeUserFromRoles.coffee
  24. 25
      packages/rocketchat-authorization/server/functions/removeUsersFromRoles.coffee
  25. 7
      packages/rocketchat-authorization/server/methods/addUserToRole.coffee
  26. 13
      packages/rocketchat-authorization/server/methods/deleteRole.coffee
  27. 2
      packages/rocketchat-authorization/server/methods/removeUserFromRole.coffee
  28. 5
      packages/rocketchat-authorization/server/methods/saveRole.coffee
  29. 1
      packages/rocketchat-authorization/server/models/Permissions.coffee
  30. 42
      packages/rocketchat-authorization/server/models/Roles.coffee
  31. 91
      packages/rocketchat-authorization/server/models/Subscriptions.js
  32. 75
      packages/rocketchat-authorization/server/models/Users.js
  33. 2
      packages/rocketchat-authorization/server/publication.coffee
  34. 3
      packages/rocketchat-authorization/server/publications/permissions.js
  35. 4
      packages/rocketchat-authorization/server/publications/roles.coffee
  36. 11
      packages/rocketchat-authorization/server/publications/scopedRoles.js
  37. 28
      packages/rocketchat-authorization/server/roles.js
  38. 36
      packages/rocketchat-authorization/server/startup.coffee
  39. 2
      packages/rocketchat-integrations/server/methods/incoming/addIncomingIntegration.coffee
  40. 2
      packages/rocketchat-integrations/server/methods/incoming/updateIncomingIntegration.coffee
  41. 4
      packages/rocketchat-lib/server/methods/setAdminStatus.coffee
  42. 8
      packages/rocketchat-livechat/permissions.js
  43. 2
      packages/rocketchat-livechat/server/methods/addAgent.js
  44. 2
      packages/rocketchat-livechat/server/methods/addManager.js
  45. 2
      packages/rocketchat-livechat/server/methods/removeAgent.js
  46. 2
      packages/rocketchat-livechat/server/methods/removeManager.js
  47. 9
      packages/rocketchat-livechat/server/models/Users.js
  48. 5
      packages/rocketchat-ui/lib/collections.coffee
  49. 2
      server/lib/accounts.coffee
  50. 2
      server/methods/createChannel.coffee
  51. 2
      server/methods/createPrivateGroup.coffee
  52. 2
      server/methods/removeUserFromRoom.coffee
  53. 4
      server/startup/initialData.coffee
  54. 11
      server/startup/migrations/v27.coffee

@ -24,6 +24,9 @@ Meteor.startup ->
loadedLaguages = []
@setLanguage = (language) ->
if !language
return
if loadedLaguages.indexOf(language) > -1
return

@ -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.

@ -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)

@ -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

@ -1 +1 @@
@ChatPermissions = new Meteor.Collection 'rocketchat_permissions'
@ChatPermissions = new Meteor.Collection 'rocketchat_permissions'

@ -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)

@ -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);
}

@ -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);
}

@ -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")
})

@ -1,3 +1,6 @@
Meteor.subscribe 'roles'
Meteor.subscribe 'scopedRoles', 'Users'
RocketChat.authz.subscription = Meteor.subscribe 'permissions'
RocketChat.AdminBox.addOption

@ -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()

@ -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 } })

@ -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:<uuid>, 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)
}
}());

@ -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');
});

@ -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

@ -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

@ -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')

@ -1,2 +1,2 @@
RocketChat.authz.getRoles = ->
return Roles.getAllRoles()
return RocketChat.models.Roles.find().fetch()

@ -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)

@ -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)

@ -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)

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -2,7 +2,6 @@ RocketChat.models.Permissions = new class extends RocketChat.models._Base
constructor: ->
@_initModel 'permissions'
# FIND
findByRole: (role, options) ->
query =

@ -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)

@ -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);
}

@ -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);
};
}

@ -1,2 +0,0 @@
Meteor.publish 'permissions', ->
return RocketChat.models.Permissions.find {}

@ -0,0 +1,3 @@
Meteor.publish('permissions', function () {
return RocketChat.models.Permissions.find({});
});

@ -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()

@ -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);
});

@ -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);
});

@ -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

@ -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

@ -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:

@ -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

@ -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']);

@ -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);
}

@ -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');
}
});

@ -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);
}

@ -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');
}
});

@ -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);

@ -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

@ -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

@ -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

@ -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 }})

@ -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,

@ -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"

@ -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 })
Loading…
Cancel
Save