You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

role.rb 9.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. # frozen_string_literal: true
  2. # Redmine - project management software
  3. # Copyright (C) 2006-2023 Jean-Philippe Lang
  4. #
  5. # This program is free software; you can redistribute it and/or
  6. # modify it under the terms of the GNU General Public License
  7. # as published by the Free Software Foundation; either version 2
  8. # of the License, or (at your option) any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with this program; if not, write to the Free Software
  17. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18. class Role < ApplicationRecord
  19. include Redmine::SafeAttributes
  20. # Custom coder for the permissions attribute that should be an
  21. # array of symbols. Rails 3 uses Psych which can be *unbelievably*
  22. # slow on some platforms (eg. mingw32).
  23. class PermissionsAttributeCoder
  24. def self.load(str)
  25. str.to_s.scan(/:([a-z0-9_]+)/).flatten.map(&:to_sym)
  26. end
  27. def self.dump(value)
  28. YAML.dump(value)
  29. end
  30. end
  31. # Built-in roles
  32. BUILTIN_NON_MEMBER = 1
  33. BUILTIN_ANONYMOUS = 2
  34. ISSUES_VISIBILITY_OPTIONS = [
  35. ['all', :label_issues_visibility_all],
  36. ['default', :label_issues_visibility_public],
  37. ['own', :label_issues_visibility_own]
  38. ]
  39. TIME_ENTRIES_VISIBILITY_OPTIONS = [
  40. ['all', :label_time_entries_visibility_all],
  41. ['own', :label_time_entries_visibility_own]
  42. ]
  43. USERS_VISIBILITY_OPTIONS = [
  44. ['all', :label_users_visibility_all],
  45. ['members_of_visible_projects', :label_users_visibility_members_of_visible_projects]
  46. ]
  47. scope :sorted, lambda {order(:builtin, :position)}
  48. scope :givable, lambda {order(:position).where(:builtin => 0)}
  49. scope :builtin, (lambda do |*args|
  50. compare = (args.first == true ? 'not' : '')
  51. where("#{compare} builtin = 0")
  52. end)
  53. belongs_to :default_time_entry_activity, :class_name => 'TimeEntryActivity'
  54. before_destroy :check_deletable
  55. has_many :workflow_rules, :dependent => :delete_all
  56. has_and_belongs_to_many :custom_fields, :join_table => "#{table_name_prefix}custom_fields_roles#{table_name_suffix}", :foreign_key => "role_id"
  57. has_and_belongs_to_many :managed_roles, :class_name => 'Role',
  58. :join_table => "#{table_name_prefix}roles_managed_roles#{table_name_suffix}",
  59. :association_foreign_key => "managed_role_id"
  60. has_and_belongs_to_many :queries, :join_table => "#{table_name_prefix}queries_roles#{table_name_suffix}", :foreign_key => "role_id"
  61. has_many :member_roles, :dependent => :destroy
  62. has_many :members, :through => :member_roles
  63. acts_as_positioned :scope => :builtin
  64. serialize :permissions, coder: ::Role::PermissionsAttributeCoder
  65. store :settings, :accessors => [:permissions_all_trackers, :permissions_tracker_ids]
  66. validates_presence_of :name
  67. validates_uniqueness_of :name, :case_sensitive => true
  68. validates_length_of :name, :maximum => 255
  69. validates_inclusion_of(
  70. :issues_visibility,
  71. :in => ISSUES_VISIBILITY_OPTIONS.collect(&:first),
  72. :if => lambda {|role| role.respond_to?(:issues_visibility) && role.issues_visibility_changed?})
  73. validates_inclusion_of(
  74. :users_visibility,
  75. :in => USERS_VISIBILITY_OPTIONS.collect(&:first),
  76. :if => lambda {|role| role.respond_to?(:users_visibility) && role.users_visibility_changed?})
  77. validates_inclusion_of(
  78. :time_entries_visibility,
  79. :in => TIME_ENTRIES_VISIBILITY_OPTIONS.collect(&:first),
  80. :if => lambda {|role| role.respond_to?(:time_entries_visibility) && role.time_entries_visibility_changed?})
  81. safe_attributes(
  82. 'name',
  83. 'assignable',
  84. 'position',
  85. 'issues_visibility',
  86. 'users_visibility',
  87. 'time_entries_visibility',
  88. 'all_roles_managed',
  89. 'managed_role_ids',
  90. 'permissions',
  91. 'permissions_all_trackers',
  92. 'permissions_tracker_ids',
  93. 'default_time_entry_activity_id'
  94. )
  95. # Copies attributes from another role, arg can be an id or a Role
  96. def copy_from(arg, options={})
  97. return unless arg.present?
  98. role = arg.is_a?(Role) ? arg : Role.find_by_id(arg.to_s)
  99. self.attributes = role.attributes.dup.except("id", "name", "position", "builtin", "permissions")
  100. self.permissions = role.permissions.dup
  101. self.managed_role_ids = role.managed_role_ids.dup
  102. self
  103. end
  104. def permissions=(perms)
  105. perms = perms.filter_map {|p| p.to_sym unless p.blank?}.uniq if perms
  106. write_attribute(:permissions, perms)
  107. end
  108. def add_permission!(*perms)
  109. self.permissions = [] unless permissions.is_a?(Array)
  110. permissions_will_change!
  111. perms.each do |p|
  112. p = p.to_sym
  113. permissions << p unless permissions.include?(p)
  114. end
  115. save!
  116. end
  117. def remove_permission!(*perms)
  118. return unless permissions.is_a?(Array)
  119. permissions_will_change!
  120. perms.each {|p| permissions.delete(p.to_sym)}
  121. save!
  122. end
  123. # Returns true if the role has the given permission
  124. def has_permission?(perm)
  125. !permissions.nil? && permissions.include?(perm.to_sym)
  126. end
  127. def consider_workflow?
  128. has_permission?(:add_issues) || has_permission?(:edit_issues)
  129. end
  130. def <=>(role)
  131. # returns -1 for nil since r2726
  132. return -1 if role.nil?
  133. return nil unless role.is_a?(Role)
  134. if builtin == role.builtin
  135. position <=> role.position
  136. else
  137. builtin <=> role.builtin
  138. end
  139. end
  140. def to_s
  141. name
  142. end
  143. def name
  144. case builtin
  145. when 1 then l(:label_role_non_member, :default => read_attribute(:name))
  146. when 2 then l(:label_role_anonymous, :default => read_attribute(:name))
  147. else
  148. read_attribute(:name)
  149. end
  150. end
  151. # Return true if the role is a builtin role
  152. def builtin?
  153. self.builtin != 0
  154. end
  155. # Return true if the role is the anonymous role
  156. def anonymous?
  157. builtin == 2
  158. end
  159. # Return true if the role is a project member role
  160. def member?
  161. !self.builtin?
  162. end
  163. # Return true if role is allowed to do the specified action
  164. # action can be:
  165. # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
  166. # * a permission Symbol (eg. :edit_project)
  167. def allowed_to?(action)
  168. if action.is_a? Hash
  169. allowed_actions.include? "#{action[:controller]}/#{action[:action]}"
  170. else
  171. allowed_permissions.include? action
  172. end
  173. end
  174. # Return all the permissions that can be given to the role
  175. def setable_permissions
  176. setable_permissions = Redmine::AccessControl.permissions - Redmine::AccessControl.public_permissions
  177. setable_permissions -= Redmine::AccessControl.members_only_permissions if self.builtin == BUILTIN_NON_MEMBER
  178. setable_permissions -= Redmine::AccessControl.loggedin_only_permissions if self.builtin == BUILTIN_ANONYMOUS
  179. setable_permissions
  180. end
  181. def permissions_tracker_ids(*args)
  182. if args.any?
  183. Array(permissions_tracker_ids[args.first.to_s]).map(&:to_i)
  184. else
  185. super || {}
  186. end
  187. end
  188. def permissions_tracker_ids=(arg)
  189. h = arg.to_hash
  190. h.each_value {|v| v.reject!(&:blank?)}
  191. super(h)
  192. end
  193. # Returns true if tracker_id belongs to the list of
  194. # trackers for which permission is given
  195. def permissions_tracker_ids?(permission, tracker_id)
  196. return false unless has_permission?(permission)
  197. permissions_tracker_ids(permission).include?(tracker_id)
  198. end
  199. def permissions_all_trackers
  200. super || {}
  201. end
  202. def permissions_all_trackers=(arg)
  203. super(arg.to_hash)
  204. end
  205. # Returns true if permission is given for all trackers
  206. def permissions_all_trackers?(permission)
  207. return false unless has_permission?(permission)
  208. permissions_all_trackers[permission.to_s].to_s != '0'
  209. end
  210. # Returns true if permission is given for the tracker
  211. # (explicitly or for all trackers)
  212. def permissions_tracker?(permission, tracker)
  213. permissions_all_trackers?(permission) ||
  214. permissions_tracker_ids?(permission, tracker.try(:id))
  215. end
  216. # Sets the trackers that are allowed for a permission.
  217. # tracker_ids can be an array of tracker ids or :all for
  218. # no restrictions.
  219. #
  220. # Examples:
  221. # role.set_permission_trackers :add_issues, [1, 3]
  222. # role.set_permission_trackers :add_issues, :all
  223. def set_permission_trackers(permission, tracker_ids)
  224. h = {permission.to_s => (tracker_ids == :all ? '1' : '0')}
  225. self.permissions_all_trackers = permissions_all_trackers.merge(h)
  226. h = {permission.to_s => (tracker_ids == :all ? [] : tracker_ids)}
  227. self.permissions_tracker_ids = permissions_tracker_ids.merge(h)
  228. self
  229. end
  230. def copy_workflow_rules(source_role)
  231. WorkflowRule.copy(nil, source_role, nil, self)
  232. end
  233. # Find all the roles that can be given to a project member
  234. def self.find_all_givable
  235. Role.givable.to_a
  236. end
  237. # Return the builtin 'non member' role. If the role doesn't exist,
  238. # it will be created on the fly.
  239. def self.non_member
  240. find_or_create_system_role(BUILTIN_NON_MEMBER, 'Non member')
  241. end
  242. # Return the builtin 'anonymous' role. If the role doesn't exist,
  243. # it will be created on the fly.
  244. def self.anonymous
  245. find_or_create_system_role(BUILTIN_ANONYMOUS, 'Anonymous')
  246. end
  247. private
  248. def allowed_permissions
  249. @allowed_permissions ||= permissions + Redmine::AccessControl.public_permissions.collect {|p| p.name}
  250. end
  251. def allowed_actions
  252. @actions_allowed ||=
  253. allowed_permissions.inject([]) do |actions, permission|
  254. actions += Redmine::AccessControl.allowed_actions(permission)
  255. end.flatten
  256. end
  257. def check_deletable
  258. raise "Cannot delete role" if members.any?
  259. raise "Cannot delete builtin role" if builtin?
  260. end
  261. def self.find_or_create_system_role(builtin, name)
  262. role = unscoped.find_by(:builtin => builtin)
  263. if role.nil?
  264. role = unscoped.create(:name => name) do |r|
  265. r.builtin = builtin
  266. end
  267. raise "Unable to create the #{name} role (#{role.errors.full_messages.join(',')})." if role.new_record?
  268. end
  269. role
  270. end
  271. private_class_method :find_or_create_system_role
  272. end