Create RocketChat authorization package that handles role and permission

based authorization

Leverages alanning:roles package to associate a user to a role.  Uses
alanning:roles optional "group" parameter to limit the role's scope to
either the global level or room level.  The global level is applicable
to users that can perform administrative functions.  The room level is
applicable to users that can perform room specific administrative
functions (like a moderator).

A role can have zero or more permissions.  Permissions and their
association to roles are defined by this package

Authorization checks are based on whether or not the user has a role or permission.

The roles, permissions, and their association are statically defined at
this time.  Eventually, there should be an API to dynamically create a
role and associate it to static permission(s).

Old 'isAdmin' and '.admin is true'  checks have been replaced with
corresponding hasPermission authorization checks.  Additionally, code
that automatically assigned admin privileges are updated to assign
'admin' role instead.

channel/direct message/private group code checks authorization to edit
properties (e.g. title) and edit/delete messages (regardless of the
system level allow edit/delete settings).
- user with 'admin' role are authorized to do anything
- room creator is assigned 'moderator' role that can edit the room and
  edit/delete messages
- members can only edit/delete their own messages IF system wide
  settings permit them to.

v19 migration will
- add 'admin' role to users with admin:true property
- add 'moderator' role scoped to room for room creators
- add 'user' role to all users.

There are known issues unrelated to the changes made
- If a user with edit/delete message room permissions logs out then a user without
  edit/delete message room permissions logs in, then they will see
edit/delete icons.  The server will deny execution
- edit/delete icons are not reactive   Thus if the system level allow
  edit/delete message setting is toggled, the icons will not reflect it.
The server will deny execution.
pull/818/head
Reid Wakida 10 years ago
parent 4fab550ad6
commit c2e6e0fa2c
  1. 1
      .meteor/packages
  2. 2
      .meteor/versions
  3. 10
      client/lib/chatMessages.coffee
  4. 7
      client/methods/deleteMessage.coffee
  5. 8
      client/methods/updateMessage.coffee
  6. 4
      client/stylesheets/base.less
  7. 2
      client/views/admin/admin.coffee
  8. 2
      client/views/admin/admin.html
  9. 41
      client/views/admin/adminFlex.html
  10. 2
      client/views/admin/adminStatistics.coffee
  11. 2
      client/views/admin/adminStatistics.html
  12. 3
      client/views/admin/rooms/adminRoomInfo.coffee
  13. 30
      client/views/admin/rooms/adminRoomInfo.html
  14. 2
      client/views/admin/rooms/adminRooms.coffee
  15. 2
      client/views/admin/rooms/adminRooms.html
  16. 10
      client/views/admin/users/adminUserChannels.html
  17. 38
      client/views/admin/users/adminUserEdit.html
  18. 5
      client/views/admin/users/adminUserInfo.coffee
  19. 22
      client/views/admin/users/adminUserInfo.html
  20. 2
      client/views/admin/users/adminUsers.coffee
  21. 2
      client/views/admin/users/adminUsers.html
  22. 11
      client/views/app/message.coffee
  23. 8
      client/views/app/room.coffee
  24. 7
      client/views/app/sideNav/channels.coffee
  25. 4
      client/views/app/sideNav/channels.html
  26. 3
      client/views/app/sideNav/listChannelsFlex.coffee
  27. 2
      client/views/app/sideNav/listChannelsFlex.html
  28. 7
      client/views/app/sideNav/privateGroups.coffee
  29. 4
      client/views/app/sideNav/privateGroups.html
  30. 3
      client/views/app/sideNav/sideNav.coffee
  31. 4
      client/views/app/sideNav/userStatus.coffee
  32. 2
      client/views/app/sideNav/userStatus.html
  33. 2
      client/views/app/userInfo.coffee
  34. 2
      client/views/app/userInfo.html
  35. 41
      packages/rocketchat-authorization/README.md
  36. 40
      packages/rocketchat-authorization/client/hasPermission.coffee
  37. 6
      packages/rocketchat-authorization/client/hasRole.coffee
  38. 2
      packages/rocketchat-authorization/client/startup.coffee
  39. 1
      packages/rocketchat-authorization/lib/permissions.coffee
  40. 1
      packages/rocketchat-authorization/lib/rocketchat.coffee
  41. 38
      packages/rocketchat-authorization/package.js
  42. 26
      packages/rocketchat-authorization/server/functions/addUsersToRoles.coffee
  43. 9
      packages/rocketchat-authorization/server/functions/getPermissionsForRole.coffee
  44. 2
      packages/rocketchat-authorization/server/functions/getRoles.coffee
  45. 7
      packages/rocketchat-authorization/server/functions/getRolesForUser.coffee
  46. 6
      packages/rocketchat-authorization/server/functions/getUsersInRole.coffee
  47. 12
      packages/rocketchat-authorization/server/functions/hasPermission.coffee
  48. 4
      packages/rocketchat-authorization/server/functions/hasRole.coffee
  49. 26
      packages/rocketchat-authorization/server/functions/removeUsersFromRoles.coffee
  50. 3
      packages/rocketchat-authorization/server/publication.coffee
  51. 87
      packages/rocketchat-authorization/server/startup.coffee
  52. 7
      packages/rocketchat-lib/server/methods/setAdminStatus.coffee
  53. 3
      packages/rocketchat-lib/server/methods/updateUser.coffee
  54. 2
      packages/rocketchat-lib/settings/server/addOAuthService.coffee
  55. 2
      packages/rocketchat-lib/settings/server/methods.coffee
  56. 3
      packages/rocketchat-lib/settings/server/publication.coffee
  57. 2
      packages/rocketchat-statistics/server/methods/getStatistics.coffee
  58. 8
      server/lib/accounts.coffee
  59. 5
      server/methods/createChannel.coffee
  60. 6
      server/methods/createPrivateGroup.coffee
  61. 13
      server/methods/deleteMessage.coffee
  62. 2
      server/methods/deleteUser.coffee
  63. 7
      server/methods/eraseRoom.coffee
  64. 3
      server/methods/migrate.coffee
  65. 6
      server/methods/removeUserFromRoom.coffee
  66. 6
      server/methods/saveRoomName.coffee
  67. 3
      server/methods/setUserActiveStatus.coffee
  68. 17
      server/methods/updateMessage.coffee
  69. 3
      server/publications/adminRooms.coffee
  70. 4
      server/publications/fullUserData.coffee
  71. 3
      server/publications/userChannels.coffee
  72. 1
      server/publications/userData.coffee
  73. 10
      server/startup/initialData.coffee
  74. 28
      server/startup/migrations/v19.coffee

@ -85,3 +85,4 @@ todda00:friendly-slugs
underscorestring:underscore.string
yasaricli:slugify
yasinuslu:blaze-meta
rocketchat:authorization

@ -6,6 +6,7 @@ accounts-meteor-developer@1.0.4
accounts-oauth@1.1.5
accounts-password@1.1.1
accounts-twitter@1.0.4
alanning:roles@1.2.13
aldeed:simple-schema@1.3.3
arunoda:streams@0.1.17
autoupdate@1.2.1
@ -100,6 +101,7 @@ reactive-dict@1.1.0
reactive-var@1.0.5
reload@1.1.3
retry@1.0.3
rocketchat:authorization@0.0.1
rocketchat:autolinker@0.0.1
rocketchat:colors@0.0.1
rocketchat:custom-oauth@1.0.0

@ -40,11 +40,15 @@ class @ChatMessages
return -1
edit: (element, index) ->
return unless RocketChat.settings.get 'Message_AllowEditing'
id = element.getAttribute("id")
message = ChatMessage.findOne { _id: id }
hasPermission = RocketChat.authz.hasAtLeastOnePermission('edit-message', message.rid)
editAllowed = RocketChat.settings.get 'Message_AllowEditing'
editOwn = message?.u?._id is Meteor.userId()
return unless hasPermission or (editAllowed and editOwn)
return if element.classList.contains("system")
this.clearEditing()
id = element.getAttribute("id")
message = ChatMessage.findOne { _id: id, 'u._id': Meteor.userId() }
this.input.value = message.msg
this.editing.element = element
this.editing.index = index or this.getEditingIndex(element)

@ -3,9 +3,14 @@ Meteor.methods
if not Meteor.userId()
throw new Meteor.Error 203, t('general.User_logged_out')
if not RocketChat.settings.get 'Message_AllowDeleting'
hasPermission = RocketChat.authz.hasAtLeastOnePermission('delete-message', message.rid)
deleteAllowed = RocketChat.settings.get 'Message_AllowDeleting'
deleteOwn = message?.u?._id is Meteor.userId()
unless hasPermission or (deleteAllowed and deleteOwn)
throw new Meteor.Error 'message-deleting-not-allowed', t('Message_deleting_not_allowed')
Tracker.nonreactive ->
ChatMessage.remove
_id: message._id

@ -3,7 +3,13 @@ Meteor.methods
if not Meteor.userId()
throw new Meteor.Error 203, t('User_logged_out')
if not RocketChat.settings.get 'Message_AllowEditing'
originalMessage = ChatMessage.findOne message._id
hasPermission = RocketChat.authz.hasAtLeastOnePermission('edit-message', message.rid)
editAllowed = RocketChat.settings.get 'Message_AllowEditing'
editOwn = originalMessage?.u?._id is Meteor.userId()
unless hasPermission or (editAllowed and editOwn)
throw new Meteor.Error 'message-editing-not-allowed', t('Message_editing_not_allowed')
Tracker.nonreactive ->

@ -2375,14 +2375,14 @@ a.github-fork {
display: none;
cursor: pointer;
}
&.own:hover:not(.system) .edit-message {
&:hover:not(.system) .edit-message {
display: inline-block;
}
.delete-message {
display: none;
cursor: pointer;
}
&.own:hover:not(.system) .delete-message {
&:hover:not(.system) .delete-message {
display: inline-block;
}
.user {

@ -1,6 +1,4 @@
Template.admin.helpers
isAdmin: ->
return Meteor.user().admin is true
group: ->
group = FlowRouter.getParam('group')
group ?= Settings.findOne({ type: 'group' })?._id

@ -7,7 +7,7 @@
</h2>
</head>
<div class="content">
{{#unless isAdmin}}
{{#unless hasPermission 'view-privileged-setting'}}
<p>You are not authorized to view this page.</p>
{{else}}
{{#with group}}

@ -7,24 +7,35 @@
<div class="content">
<div class="wrapper">
<ul>
<li>
<a href="{{pathFor 'admin-statistics'}}" class="admin-link">{{_ "Statistics"}}</a>
</li>
<li>
<a href="{{pathFor 'admin-rooms'}}" class="admin-link">{{_ "Rooms"}}</a>
</li>
<li>
<a href="{{pathFor 'admin-users'}}" class="admin-link">{{_ "Users"}}</a>
</li>
{{#if hasPermission 'view-statistics'}}
<li>
<a href="{{pathFor 'admin-statistics'}}" class="admin-link">{{_ "Statistics"}}</a>
</li>
{{/if}}
{{#if hasPermission 'view-room-administration'}}
<li>
<a href="{{pathFor 'admin-rooms'}}" class="admin-link">{{_ "Rooms"}}</a>
</li>
{{/if}}
{{#if hasPermission 'view-user-administration'}}
<li>
<a href="{{pathFor 'admin-users'}}" class="admin-link">{{_ "Users"}}</a>
</li>
{{/if}}
<h3 class="add-room">
{{_ "Settings"}}
</h3>
{{#each groups}}
<li>
<a href="{{pathFor 'admin' group=_id}}" class="admin-link">{{_ i18nLabel}}</a>
</li>
{{/each}}
{{#if hasPermission 'view-privileged-setting'}}
{{#each groups}}
<li>
<a href="{{pathFor 'admin' group=_id}}" class="admin-link">{{_ i18nLabel}}</a>
</li>
{{/each}}
{{/if}}
</ul>
</div>
</div>

@ -1,6 +1,4 @@
Template.adminStatistics.helpers
isAdmin: ->
return Meteor.user().admin is true
isReady: ->
return Template.instance().ready.get()
statistics: ->

@ -7,7 +7,7 @@
</h2>
</head>
<div class="content">
{{#unless isAdmin}}
{{#unless hasPermission 'view-statistics'}}
<p>You are not authorized to view this page.</p>
{{else}}
{{#if isReady}}

@ -1,4 +1,7 @@
Template.adminRoomInfo.helpers
canDeleteRoom: ->
return RocketChat.authz.hasAtLeastOnePermission("delete-#{@t}")
type: ->
return if @t is 'd' then 'at' else if @t is 'p' then 'lock' else 'hash'
name: ->

@ -1,14 +1,20 @@
<template name="adminRoomInfo">
<div>
<h3><a href="{{route}}"><i class="icon-{{type}}"></i> {{name}}</a></h3>
</div>
<div>
<h3>{{_ "Users"}}:</h3>
{{#each usernames}}
{{.}}<br />
{{/each}}
</div>
<nav>
<button class='button delete red'><span><i class='icon-trash'></i> {{_ "Delete"}}</span></button>
</nav>
{{#unless hasPermission 'view-room-administration'}}
<p>You are not authorized to view this page.</p>
{{else}}
<div>
<h3><a href="{{route}}"><i class="icon-{{type}}"></i> {{name}}</a></h3>
</div>
<div>
<h3>{{_ "Users"}}:</h3>
{{#each usernames}}
{{.}}<br />
{{/each}}
</div>
{{#if canDeleteRoom}}
<nav>
<button class='button delete red'><span><i class='icon-trash'></i> {{_ "Delete"}}</span></button>
</nav>
{{/if}}
{{/unless}}
</template>

@ -1,6 +1,4 @@
Template.adminRooms.helpers
isAdmin: ->
return Meteor.user().admin is true
isReady: ->
return Template.instance().ready?.get()
rooms: ->

@ -7,7 +7,7 @@
</h2>
</head>
<div class="content">
{{#unless isAdmin}}
{{#unless hasPermission 'view-room-administration'}}
<p>You are not authorized to view this page.</p>
{{else}}
<form class="search-form" role="form">

@ -1,5 +1,9 @@
<template name="adminUserChannels">
<div class="user-info-channel">
<h3><a href="{{route}}"><i class="icon-{{type}}"></i> {{name}}</a></h3>
</div>
{{#unless hasPermission 'view-full-other-user-info'}}
<p>You are not authorized to view this page.</p>
{{else}}
<div class="user-info-channel">
<h3><a href="{{route}}"><i class="icon-{{type}}"></i> {{name}}</a></h3>
</div>
{{/unless}}
</template>

@ -1,19 +1,23 @@
<template name="adminUserEdit">
<div class="about clearfix">
<form class="edit-form">
<h3>{{name}}</h3>
<div class="input-line">
<label for="name">{{_ "Name"}}</label>
<input type="text" id="name" autocomplete="off" value="{{name}}">
</div>
<div class="input-line">
<label for="username">{{_ "Username"}}</label>
<input type="text" id="username" autocomplete="off" value="{{username}}">
</div>
</form>
</div>
<nav>
<button class='button button-block cancel secondary'><span>{{_ "Cancel"}}</span></button>
<button class='button button-block blue save'><span>{{_ "Save"}}</span></button>
</nav>
{{#unless hasPermission 'edit-other-user-info'}}
<p>You are not authorized to view this page.</p>
{{else}}
<div class="about clearfix">
<form class="edit-form">
<h3>{{name}}</h3>
<div class="input-line">
<label for="name">{{_ "Name"}}</label>
<input type="text" id="name" autocomplete="off" value="{{name}}">
</div>
<div class="input-line">
<label for="username">{{_ "Username"}}</label>
<input type="text" id="username" autocomplete="off" value="{{username}}">
</div>
</form>
</div>
<nav>
<button class='button button-block cancel secondary'><span>{{_ "Cancel"}}</span></button>
<button class='button button-block blue save'><span>{{_ "Save"}}</span></button>
</nav>
{{/unless}}
</template>

@ -1,6 +1,4 @@
Template.adminUserInfo.helpers
isAdmin: ->
return Meteor.user()?.admin is true
name: ->
return if @name then @name else TAPi18next.t 'project:Unnamed'
email: ->
@ -20,6 +18,9 @@ Template.adminUserInfo.helpers
@utcOffset = "+#{@utcOffset}"
return "UTC #{@utcOffset}"
hasAdminRole: ->
console.log 'hasAdmin: ', RocketChat.authz.hasRole(@_id, 'admin')
return RocketChat.authz.hasRole(@_id, 'admin')
Template.adminUserInfo.events
'click .deactivate': (e) ->

@ -1,19 +1,25 @@
<template name="adminUserInfo">
{{#if isAdmin}}
{{> userInfo user=.}}
<nav>
<button class='button lightblue edit-user button-block'><span><i class='icon-edit'></i> {{_ "Edit"}}</span></button>
{{#if admin}}
{{> userInfo user=.}}
<nav>
{{#if hasPermission 'edit-other-user-info'}}
<button class='button lightblue edit-user button-block'><span><i class='icon-edit'></i> {{_ "Edit"}}</span></button>
{{/if}}
{{#if hasPermission 'assign-admin-role'}}
{{#if hasAdminRole}}
<button class='button lightblue remove-admin button-block'><span><i class='icon-shield'></i> {{_ "Remove_Admin"}}</span></button>
{{else}}
<button class='button lightblue make-admin button-block'><span><i class='icon-shield'></i> {{_ "Make_Admin"}}</span></button>
{{/if}}
{{/if}}
{{#if hasPermission 'edit-other-user-active-status'}}
{{#if active}}
<button class='button deactivate button-block'><span><i class='icon-block'></i> {{_ "Deactivate"}}</span></button>
{{else}}
<button class='button activate button-block'><span><i class='icon-ok-circled'></i> {{_ "Activate"}}</span></button>
{{/if}}
<button class='button delete red button-block'><span><i class='icon-trash'></i> {{_ "Delete"}}</span></button>
</nav>
{{/if}}
{{/if}}
{{#if hasPermission 'delete-user'}}
<button class='button delete red button-block'><span><i class='icon-trash'></i> {{_ "Delete"}}</span></button>
{{/if}}
</nav>
</template>

@ -1,6 +1,4 @@
Template.adminUsers.helpers
isAdmin: ->
return Meteor.user().admin is true
isReady: ->
return Template.instance().ready?.get()
users: ->

@ -7,7 +7,7 @@
</h2>
</head>
<div class="content">
{{#unless isAdmin}}
{{#unless hasPermission 'view-user-administration'}}
<p>You are not authorized to view this page.</p>
{{else}}
<form class="search-form" role="form">

@ -40,9 +40,16 @@ Template.message.helpers
pinned: ->
return this.pinned
canEdit: ->
return RocketChat.settings.get 'Message_AllowEditing'
if RocketChat.authz.hasAtLeastOnePermission('edit-message', this.rid )
return true
return RocketChat.settings.get('Message_AllowEditing') and this.u?._id is Meteor.userId()
canDelete: ->
return RocketChat.settings.get 'Message_AllowDeleting'
if RocketChat.authz.hasAtLeastOnePermission('delete-message', this.rid )
return true
return RocketChat.settings.get('Message_AllowDeleting') and this.u?._id is Meteor.userId()
canPin: ->
return RocketChat.settings.get 'Message_AllowPinning'
showEditedStatus: ->

@ -104,7 +104,10 @@ Template.room.helpers
canEditName: ->
roomData = Session.get('roomData' + this._id)
return '' unless roomData
return roomData.u?._id is Meteor.userId() and roomData.t in ['c', 'p']
if roomData.t in ['c', 'p']
return RocketChat.authz.hasAtLeastOnePermission('edit-room', this._id)
else
return ''
canDirectMessage: ->
return Meteor.user()?.username isnt this.username
@ -183,9 +186,6 @@ Template.room.helpers
maxMessageLength: ->
return RocketChat.settings.get('Message_MaxAllowedSize')
isAdmin: ->
return Meteor.user()?.admin is true
utc: ->
if @utcOffset?
return "UTC #{@utcOffset}"

@ -10,8 +10,11 @@ Template.channels.helpers
Template.channels.events
'click .add-room': (e, instance) ->
SideNav.setFlex "createChannelFlex"
SideNav.openFlex()
if RocketChat.authz.hasAtLeastOnePermission('create-c')
SideNav.setFlex "createChannelFlex"
SideNav.openFlex()
else
e.preventDefault()
'click .more-channels': ->
SideNav.setFlex "listChannelsFlex"

@ -1,7 +1,9 @@
<template name="channels">
<h3 class="add-room {{isActive}}">
{{_ "Channels"}}
<i class="a-plus"></i>
{{#if hasPermission 'create-c'}}
<i class="a-plus"></i>
{{/if}}
</h3>
<ul>
{{#each rooms}}

@ -10,7 +10,8 @@ Template.listChannelsFlex.events
SideNav.closeFlex()
'click footer .create': ->
SideNav.setFlex "createChannelFlex"
if RocketChat.authz.hasAtLeastOnePermission( 'create-c')
SideNav.setFlex "createChannelFlex"
'mouseenter header': ->
SideNav.overArrow()

@ -21,7 +21,9 @@
</div>
<footer>
<div>
{{#if hasPermission 'create-c'}}
<button class="button clean create">{{_ "Create_new"}}</button>
{{/if}}
</div>
</footer>
</template>

@ -16,8 +16,11 @@ Template.privateGroups.helpers
Template.privateGroups.events
'click .add-room': (e, instance) ->
SideNav.setFlex "privateGroupsFlex"
SideNav.openFlex()
if RocketChat.authz.hasAtLeastOnePermission('create-p')
SideNav.setFlex "privateGroupsFlex"
SideNav.openFlex()
else
e.preventDefault()
'click .more-groups': ->
SideNav.setFlex "listPrivateGroupsFlex"

@ -1,7 +1,9 @@
<template name="privateGroups">
<h3 class="add-room {{isActive}}">
{{_ "Private_Groups"}}
<i class="a-plus"></i>
{{#if hasPermission 'create-p'}}
<i class="a-plus"></i>
{{/if}}
</h3>
<ul>
{{#each rooms}}

@ -1,6 +1,5 @@
Template.sideNav.helpers
isAdmin: ->
return Meteor.user()?.admin is true
flexTemplate: ->
return SideNav.getFlex().template
flexData: ->

@ -17,8 +17,8 @@ Template.userStatus.helpers
username: username
}
isAdmin: ->
return Meteor.user()?.admin is true
showAdminOption: ->
return RocketChat.authz.hasAtLeastOnePermission( ['view-statistics', 'view-room-administration', 'view-user-administration', 'view-privileged-setting'])
Template.userStatus.events
'click .options .status': (event) ->

@ -18,7 +18,7 @@
<a href="" data-status="busy" class="status busy"><span>{{_ "Busy" context="male"}}</span></a>
<a href="" data-status="offline" class="status offline"><span>{{_ "Invisible"}}</span></a>
<a href="" id="account" class='account-link'><i class="icon-sliders"></i><span>{{_ "My_Account"}}</span></a>
{{#if isAdmin}}
{{#if showAdminOption }}
<a href="" id="admin" class='account-link'><i class="icon-wrench"></i><span>{{_ "Administration"}}</span></a>
{{/if}}
<a href="" id="logout"><i class="icon-logout"></i><span>{{_ "Logout"}}</span></a>

@ -1,6 +1,4 @@
Template.userInfo.helpers
isAdmin: ->
return Meteor.user()?.admin is true
utc: ->
if @utcOffset?
if @utcOffset > 0

@ -37,7 +37,7 @@
<h3 title="{{username}}"><i class="status-{{status}}"></i> {{username}}</h3>
<p>{{name}}</p>
{{#if utc}}<p><i class="icon-clock"></i>{{userTime}} (UTC {{utc}})</p>{{/if}}
{{#if isAdmin}}
{{#if hasPermission 'view-full-other-user-info'}}
{{#each emails}} <p><i class="icon-mail"></i> {{address}}{{#if verified}}&nbsp;<i class="icon-ok"></i>{{/if}}</p> {{/each}}
{{#each phone}} <p><i class="icon-phone"></i> {{phoneNumber}}</p> {{/each}}
{{#if lastLogin}} <p><i class="icon-calendar"></i> {{_ "Created_at"}}: {{createdAt}}</p> {{/if}}

@ -0,0 +1,41 @@
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.
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:
```
# permission based check
if hasPermission(userId, 'edit-message') ...
# action is loosely associated to role via permission. Thus action can be revoked
# at runtime by removing the permission for user's role instead of modifying the action code.
# role based check
if hasRole(userId, ['admin','site-moderator','moderator'])
# action is statically associated with the role
# action code has to be modified to add/remove role authorization
```
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 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 )
# check if user can modify message for any room
RocketChat.authz.hasPermission(userId, 'edit-message')
# check if user can modify message for the specified room. Also returns true if user
# has 'edit-message' at global scope.
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.

@ -0,0 +1,40 @@
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))
Template.registerHelper 'hasPermission', (permission, scope) ->
unless _.isString( scope )
scope = Roles.GLOBAL_GROUP
return hasPermission( permission, scope, atLeastOne)
RocketChat.authz.hasAllPermission = (permissions, scope=Roles.GLOBAL_GROUP) ->
return hasPermission( permissions, scope, all )
RocketChat.authz.hasAtLeastOnePermission = (permissions, scope=Roles.GLOBAL_GROUP) ->
return hasPermission(permissions, scope, atLeastOne)
hasPermission = (permissions, scope=Roles.GLOBAL_GROUP, strategy) ->
userId = Meteor.userId()
unless userId
return false
unless RocketChat.authz.subscription.ready()
return false
unless _.isArray(permissions)
permissions = [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)

@ -0,0 +1,6 @@
RocketChat.authz.hasRole = (userId, roleName, scope=Roles.GLOBAL_GROUP) ->
unless Meteor.userId()
return false
# per alanning:roles, returns true if user is in ANY roles
return Roles.userIsInRole(userId, [roleName], scope)

@ -0,0 +1,2 @@
Meteor.startup ->
RocketChat.authz.subscription = Meteor.subscribe 'permissions'

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

@ -0,0 +1,38 @@
Package.describe({
name: 'rocketchat:authorization',
version: '0.0.1',
summary: 'Role based authorization of actions',
git: '',
documentation: 'README.md'
});
Package.onUse(function(api) {
api.versionsFrom('1.0');
api.use([
'coffeescript',
'rocketchat:lib@0.0.1',
'alanning:roles@1.2.12'
]);
api.use('templating', 'client');
api.addFiles('lib/permissions.coffee', ['server', 'client']);
api.addFiles('lib/rocketchat.coffee', ['server','client']);
api.addFiles('client/startup.coffee', ['client']);
api.addFiles('client/hasPermission.coffee', ['client']);
api.addFiles('client/hasRole.coffee', ['client']);
api.addFiles('server/functions/addUsersToRoles.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/methods.coffee', ['server']);
api.addFiles('server/publication.coffee', ['server']);
api.addFiles('server/startup.coffee', ['server']);
});

@ -0,0 +1,26 @@
RocketChat.authz.addUsersToRoles = (userIds, roleNames, scope ) ->
console.log '[methods] addUserToRoles -> '.green, 'arguments:', arguments
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.addUsersToRoles( userIds, roleNames, scope)
return true

@ -0,0 +1,9 @@
RocketChat.authz.getPermissionsForRole = (roleName) ->
unless roleName
throw new Meteor.Error 'invalid-role'
roleNames = _.pluck(RocketChat.authz.getRoles().fetch(), 'name')
unless roleName in roleNames
throw new Meteor.Error 'invalid-role'
return _.pluck(ChatPermissions.find({roles : roleName }).fetch(), '_id')

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

@ -0,0 +1,7 @@
RocketChat.authz.getRolesForUser = (userId, scope) ->
console.log '[methods] getRolesForUser -> '.green, 'arguments:', arguments
# returns roles for the given scope as well as the global scope
unless scope
scope = Roles.GLOBAL_GROUP
return Roles.getRolesForUser(userId, scope)

@ -0,0 +1,6 @@
RocketChat.authz.getUsersInRole = (roleName, scope) ->
# alanning:roles doc says this is an expensive operation
unless _.isString(scope)
scope = Roles.GLOBAL_GROUP
return Roles.getUsersInRole(roleName, scope)

@ -0,0 +1,12 @@
RocketChat.authz.hasPermission = (userId, permissionId, scope) ->
console.log '[methods] hasPermission -> '.green, 'arguments:', arguments
# 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

@ -0,0 +1,4 @@
RocketChat.authz.hasRole = (userId, roleName, scope) ->
console.log '[methods] hasRoles -> '.green, 'arguments:', arguments
# per alanning:roles, returns true if user is in ANY roles
return Roles.userIsInRole(userId, [roleName], scope)

@ -0,0 +1,26 @@
RocketChat.authz.removeUsersFromRoles = (userIds, roleNames, scope ) ->
console.log '[methods] removeUsersFromRoles -> '.green, 'arguments:', arguments
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

@ -0,0 +1,3 @@
Meteor.publish 'permissions', ->
console.log '[publish] permissions'.green
return ChatPermissions.find {}

@ -0,0 +1,87 @@
Meteor.startup ->
# Note:
# 1.if we need to create a role that can only edit channel message, but not edit group message
# then we can define edit-<type>-message instead of edit-message
# 2. admin, moderator, and user roles should not be deleted as they are referened in the code.
permissions = [
{ _id: 'view-statistics',
roles : ['admin', 'temp-role']}
{ _id: 'view-privileged-setting',
roles : ['admin']}
{ _id: 'edit-privileged-setting',
roles : ['admin']}
{ _id: 'view-room-administration',
roles : ['admin']}
{ _id: 'view-user-administration',
roles : ['admin']}
{ _id: 'view-full-other-user-info',
roles : ['admin']}
{ _id: 'edit-other-user-info',
roles : ['admin']}
{ _id: 'assign-admin-role',
roles : ['admin']}
{ _id: 'edit-other-user-active-status',
roles : ['admin', 'site-moderator']}
{ _id: 'delete-user',
roles : ['admin']}
{ _id: 'view-other-user-channels',
roles : ['admin']}
{ _id: 'add-oath-service',
roles : ['admin']}
{ _id: 'run-migration',
roles : ['admin']}
{ _id: 'create-c',
roles : ['admin', 'site-moderator', 'user']}
{ _id: 'delete-c',
roles : ['admin', 'site-moderator']}
{ _id: 'edit-room',
roles : ['admin', 'site-moderator', 'moderator']}
{ _id: 'edit-message',
roles : ['admin', 'site-moderator', 'moderator']}
{ _id: 'delete-message',
roles : ['admin', 'site-moderator', 'moderator']}
{ _id: 'ban-user',
roles : ['admin', 'site-moderator', 'moderator']}
{ _id: 'create-p',
roles : ['admin', 'site-moderator', 'user']}
{ _id: 'delete-p',
roles : ['admin', 'site-moderator']}
{ _id: 'delete-d',
roles : ['admin', 'site-moderator']}
]
#alanning:roles
roles = _.pluck(Roles.getAllRoles().fetch(), 'name');
for permission in permissions
ChatPermissions.upsert( permission._id, {$setOnInsert : permission })
for role in permission.roles
unless role in roles
Roles.createRole role
roles.push(role)

@ -3,9 +3,12 @@ Meteor.methods
if not Meteor.userId()
throw new Meteor.Error 'invalid-user', "[methods] setAdminStatus -> Invalid user"
unless Meteor.user()?.admin is true
unless RocketChat.authz.hasPermission( Meteor.userId(), 'assign-admin-role') is true
throw new Meteor.Error 'not-authorized', '[methods] setAdminStatus -> Not authorized'
Meteor.users.update userId, { $set: { admin: admin } }
if admin
RocketChat.authz.addUsersToRoles( userId, 'admin')
else
RocketChat.authz.removeUsersFromRoles( userId, 'admin')
return true

@ -7,7 +7,8 @@ Meteor.methods
user = Meteor.user()
if user._id isnt userData._id and user.admin isnt true
canEditUserPermission = RocketChat.authz.hasPermission( user._id, 'edit-other-user-info')
if user._id isnt userData._id and canEditUserPermission isnt true
throw new Meteor.Error 'not-authorized', '[methods] updateUser -> Not authorized'
unless userData._id

@ -5,7 +5,7 @@ Meteor.methods
console.log '[methods] addOAuthService -> '.green, 'userId:', Meteor.userId(), 'arguments:', arguments
unless Meteor.user()?.admin is true
unless RocketChat.authz.hasALeastOnePermission( Meteor.userId(), 'add-oath-service') is true
throw new Meteor.Error 'not-authorized', '[methods] addOAuthService -> Not authorized'
name = s.capitalize(name)

@ -50,7 +50,7 @@ Meteor.methods
if Meteor.userId()?
user = Meteor.users.findOne Meteor.userId()
unless user?.admin is true
unless RocketChat.authz.hasPermission(Meteor.userId(), 'edit-privileged-setting') is true
throw new Meteor.Error 503, 'Not authorized'
# console.log "saveSetting -> ".green, _id, value

@ -8,8 +8,7 @@ Meteor.publish 'admin-settings', ->
unless @userId
return @ready()
user = Meteor.users.findOne @userId
if user.admin
if RocketChat.authz.hasPermission( @userId, 'view-privileged-setting')
return Settings.find()
else
return @ready()

@ -5,7 +5,7 @@ Meteor.methods
console.log '[methods] getStatistics -> '.green, 'userId:', Meteor.userId(), 'arguments:', arguments
unless Meteor.user()?.admin is true
unless RocketChat.authz.hasPermission(Meteor.userId(), 'view-statistics') is true
throw new Meteor.Error 'not-authorized', '[methods] getStatistics -> Not authorized'
return RocketChat.statistics.get()

@ -24,9 +24,8 @@ Accounts.onCreateUser (options, user) ->
user.status = 'offline'
user.active = not RocketChat.settings.get 'Accounts_ManuallyApproveNewUsers'
# when inserting first user, set admin: true
unless Meteor.users.findOne()
user.admin = true
# when inserting first user give them admin privileges otherwise make a regular user
roleName = if Meteor.users.findOne() then 'user' else 'admin'
if not user?.name? or user.name is ''
if options.profile?.name?
@ -47,6 +46,9 @@ Accounts.onCreateUser (options, user) ->
]
Meteor.defer ->
# need to defer role assignment because underlying alanning:roles requires user
# to exist in users collection
RocketChat.authz.addUsersToRoles( user._id, roleName)
RocketChat.callbacks.run 'afterCreateUser', options, user
return user

@ -6,6 +6,9 @@ Meteor.methods
if not /^[0-9a-z-_]+$/.test name
throw new Meteor.Error 'name-invalid'
if RocketChat.authz.hasPermission(Meteor.userId(), 'create-c') isnt true
throw new Meteor.Error 'not-authorized', '[methods] createChannel -> Not authorized'
console.log '[methods] createChannel -> '.green, 'userId:', Meteor.userId(), 'arguments:', arguments
now = new Date()
@ -33,6 +36,8 @@ Meteor.methods
# create new room
rid = ChatRoom.insert room
# set creator as channel moderator. permission limited to channel by scoping to rid
RocketChat.authz.addUsersToRoles(Meteor.userId(), 'moderator', rid)
for username in members
member = Meteor.users.findOne({username: username})

@ -3,6 +3,9 @@ Meteor.methods
if not Meteor.userId()
throw new Meteor.Error 'invalid-user', "[methods] createPrivateGroup -> Invalid user"
unless RocketChat.authz.hasPermission(Meteor.userId(), 'create-p')
throw new Meteor.Error 'not-authorized', '[methods] createPrivateGroup -> Not authorized'
console.log '[methods] createPrivateGroup -> '.green, 'userId:', Meteor.userId(), 'arguments:', arguments
if not /^[0-9a-z-_]+$/.test name
@ -31,6 +34,9 @@ Meteor.methods
name: name
msgs: 0
# set creator as group moderator. permission limited to group by scoping to rid
RocketChat.authz.addUsersToRoles(Meteor.userId(), 'moderator', rid)
for username in members
member = Meteor.users.findOne({ username: username },{ fields: { username: 1 }})
if not member?

@ -3,13 +3,12 @@ Meteor.methods
if not Meteor.userId()
throw new Meteor.Error('invalid-user', "[methods] deleteMessage -> Invalid user")
if not RocketChat.settings.get 'Message_AllowDeleting'
throw new Meteor.Error 'message-deleting-not-allowed', "[methods] updateMessage -> Message deleting not allowed"
hasPermission = RocketChat.authz.hasPermission(Meteor.userId(), 'delete-message', message.rid)
deleteAllowed = RocketChat.settings.get 'Message_AllowDeleting'
deleteOwn = message?.u?._id is Meteor.userId()
user = Meteor.users.findOne Meteor.userId()
unless user?.admin is true or message.u._id is Meteor.userId()
throw new Meteor.Error 'not-authorized', '[methods] deleteMessage -> Not authorized'
unless hasPermission or (deleteAllowed and deleteOwn)
throw new Meteor.Error 'message-deleting-not-allowed', "[methods] deleteMessage -> Message deleting not allowed"
console.log '[methods] deleteMessage -> '.green, 'userId:', Meteor.userId(), 'arguments:', arguments
@ -18,7 +17,7 @@ Meteor.methods
deleteQuery =
_id: message._id
deleteQuery['u._id'] = Meteor.userId() if user?.admin isnt true
#deleteQuery['u._id'] = Meteor.userId() if user?.admin isnt true
if keepHistory
if showDeletedStatus

@ -4,7 +4,7 @@ Meteor.methods
throw new Meteor.Error('invalid-user', "[methods] deleteUser -> Invalid user")
user = Meteor.users.findOne Meteor.userId()
unless user?.admin is true
unless RocketChat.authz.hasPermission(Meteor.userId(), 'delete-user') is true
throw new Meteor.Error 'not-authorized', '[methods] deleteUser -> Not authorized'
user = Meteor.users.findOne userId

@ -2,10 +2,9 @@ Meteor.methods
eraseRoom: (rid) ->
fromId = Meteor.userId()
user = Meteor.users.findOne Meteor.userId()
if user.admin is true
roomType = ChatRoom.findOne(rid)?.t
if RocketChat.authz.hasPermission( fromId, "delete-#{roomType}", rid )
# console.log '[methods] eraseRoom -> '.green, 'fromId:', fromId, 'rid:', rid
# ChatRoom.update({ _id: rid}, {'$pull': { userWatching: Meteor.userId(), userIn: Meteor.userId() }})
@ -18,3 +17,5 @@ Meteor.methods
ChatSubscription.remove({rid: rid})
ChatRoom.remove(rid)
# @TODO remove das mensagens lidas do usuário
else
throw new Meteor.Error 'unauthorized'

@ -2,7 +2,8 @@ Meteor.methods
migrateTo: (version) ->
user = Meteor.user()
if not user? or user.admin isnt true
if not user? or RocketChat.authz.hasPermission(user._id, 'run-migration') isnt true
console.log '[methods] createChannel -> Not authorized'
return
this.unblock()

@ -18,6 +18,12 @@ Meteor.methods
ChatSubscription.remove { 'u._id': data.username, rid: data.rid }
switch room.t
when 'c'
RocketChat.authz.removeUsersFromRole(removedUser._id; 'channel-moderator', data.rid)
when 'p'
RocketChat.authz.removeUsersFromRole(removedUser._id; 'group-moderator', data.rid)
ChatMessage.insert
rid: data.rid
ts: (new Date)

@ -5,7 +5,11 @@ Meteor.methods
room = ChatRoom.findOne rid
if room.u._id isnt Meteor.userId() or room.t not in ['c', 'p']
if room.t not in ['c', 'p']
throw new Meteor.Error 403, 'Not allowed'
unless RocketChat.authz.hasPermission(Meteor.userId(), 'edit-room', rid)
#if room.u._id isnt Meteor.userId() and not hasPermission
throw new Meteor.Error 403, 'Not allowed'
if not /^[0-9a-z-_]+$/.test name

@ -3,8 +3,7 @@ Meteor.methods
if not Meteor.userId()
throw new Meteor.Error 'invalid-user', '[methods] setUserActiveStatus -> Invalid user'
user = Meteor.users.findOne Meteor.userId()
unless user?.admin is true
unless RocketChat.authz.hasPermission( Meteor.userId(), 'edit-other-user-active-status') is true
throw new Meteor.Error 'not-authorized', '[methods] setUserActiveStatus -> Not authorized'
Meteor.users.update userId, { $set: { active: active } }

@ -1,17 +1,18 @@
updateMessage = (originalMessage, message) ->
Meteor.methods
updateMessage: (message) ->
if not Meteor.userId()
throw new Meteor.Error('invalid-user', "[methods] updateMessage -> Invalid user")
if not RocketChat.settings.get 'Message_AllowEditing'
throw new Meteor.Error 'message-editing-not-allowed', "[methods] updateMessage -> Message editing not allowed"
user = Meteor.users.findOne Meteor.userId()
originalMessage = ChatMessage.findOne message._id
unless user?.admin is true or originalMessage?.u?._id is Meteor.userId()
throw new Meteor.Error 'not-authorized', '[methods] updateMessage -> Not authorized'
hasPermission = RocketChat.authz.hasPermission(Meteor.userId(), 'edit-message', message.rid)
editAllowed = RocketChat.settings.get 'Message_AllowEditing'
editOwn = originalMessage?.u?._id is Meteor.userId()
unless hasPermission or (editAllowed and editOwn)
throw new Meteor.Error 'message-editing-not-allowed', "[methods] updateMessage -> Message editing not allowed"
console.log '[methods] updateMessage -> '.green, 'userId:', Meteor.userId(), 'arguments:', arguments
@ -35,9 +36,9 @@ Meteor.methods
ChatMessage.update
_id: tempid
'u._id': Meteor.userId()
,
$set: message
# Meteor.defer ->
# RocketChat.callbacks.run 'afterSaveMessage', ChatMessage.findOne(message.id)

@ -2,8 +2,7 @@ Meteor.publish 'adminRooms', (filter, types, limit) ->
unless this.userId
return this.ready()
user = Meteor.users.findOne this.userId
if user.admin isnt true
if RocketChat.authz.hasPermission(@userId, 'view-room-administration') isnt true
return this.ready()
unless _.isArray types

@ -10,16 +10,16 @@ Meteor.publish 'fullUserData', (filter, limit) ->
status: 1
utcOffset: 1
if user.admin is true
if RocketChat.authz.hasPermission( @userId, 'view-full-other-user-info') is true
fields = _.extend fields,
emails: 1
phone: 1
statusConnection: 1
admin: 1
createdAt: 1
lastLogin: 1
active: 1
services: 1
roles : 1
else
limit = 1

@ -2,8 +2,7 @@ Meteor.publish 'userChannels', (userId) ->
unless this.userId
return this.ready()
user = Meteor.users.findOne this.userId
if user.admin isnt true
if RocketChat.authz.hasPermission( @userId, 'view-other-user-channels') isnt true
return this.ready()
query = { "u._id": userId }

@ -12,7 +12,6 @@ Meteor.publish 'userData', ->
statusDefault: 1
statusConnection: 1
avatarOrigin: 1
admin: 1
utcOffset: 1
language: 1
settings: 1

@ -19,7 +19,7 @@ Meteor.startup ->
if process.env.ADMIN_EMAIL? and process.env.ADMIN_PASS?
re = /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i
if re.test process.env.ADMIN_EMAIL
if not Meteor.users.findOne({ admin: true })?
if _.isEmpty(RocketChat.authz.getUsersInRole( 'admin' ).fetch())
if not Meteor.users.findOne({ "emails.address": process.env.ADMIN_EMAIL })
console.log 'Inserting admin user'.red
console.log "email: #{process.env.ADMIN_EMAIL} | password: #{process.env.ADMIN_PASS}".red
@ -32,9 +32,10 @@ Meteor.startup ->
],
name: 'Admin'
avatarOrigin: 'none'
admin: true
Accounts.setPassword id, process.env.ADMIN_PASS
RocketChat.authz.addUsersToRoles( id, 'admin')
else
console.log 'E-mail exists; ignoring environment variables ADMIN_EMAIL and ADMIN_PASS'.red
else
@ -43,10 +44,9 @@ Meteor.startup ->
console.log 'E-mail provided is invalid; ignoring environment variables ADMIN_EMAIL and ADMIN_PASS'.red
# Set oldest user as admin, if none exists yet
admin = Meteor.users.findOne { admin: true }, { fields: { _id: 1 } }
unless admin
if _.isEmpty( RocketChat.authz.getUsersInRole( 'admin' ).fetch())
# get oldest user
oldestUser = Meteor.users.findOne({}, { fields: { username: 1 }, sort: {createdAt: 1}})
if oldestUser
Meteor.users.update {_id: oldestUser._id}, {$set: {admin: true}}
RocketChat.authz.addUsersToRoles( oldestUser._id, 'admin')
console.log "No admins are found. Set #{oldestUser.username} as admin for being the oldest user"

@ -0,0 +1,28 @@
Meteor.startup ->
Migrations.add
version: 19
up: ->
###
# Migrate existing admin users to Role based admin functionality
# 'admin' role applies to global scope
###
admins = Meteor.users.find({ admin: true }, { fields: { _id: 1, username:1 } }).fetch()
RocketChat.authz.addUsersToRoles( _.pluck(admins, '_id'), ['admin'])
Meteor.users.update({}, { $unset :{admin:''}}, {multi:true})
usernames = _.pluck( admins, 'username').join(', ')
console.log "Migrate #{usernames} from admin field to 'admin' role".green
# Add 'user' role to all users
users = Meteor.users.find().fetch()
RocketChat.authz.addUsersToRoles( _.pluck(users, '_id'), ['user'])
usernames = _.pluck( users, 'username').join(', ')
console.log "Add #{usernames} to 'user' role".green
# Add 'moderator' role to channel/group creators
rooms = ChatRoom.find({t: {$in : ['c','p']}}).fetch()
_.each( rooms, (room) ->
creator = room?.u?._id
if creator
RocketChat.authz.addUsersToRoles( creator, ['moderator'], room._id)
console.log "Add #{room.u.username} to 'moderator' role".green
)
Loading…
Cancel
Save