Chris@441: # Redmine - project management software Chris@1494: # Copyright (C) 2006-2014 Jean-Philippe Lang Chris@0: # Chris@0: # This program is free software; you can redistribute it and/or Chris@0: # modify it under the terms of the GNU General Public License Chris@0: # as published by the Free Software Foundation; either version 2 Chris@0: # of the License, or (at your option) any later version. Chris@909: # Chris@0: # This program is distributed in the hope that it will be useful, Chris@0: # but WITHOUT ANY WARRANTY; without even the implied warranty of Chris@0: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the Chris@0: # GNU General Public License for more details. Chris@909: # Chris@0: # You should have received a copy of the GNU General Public License Chris@0: # along with this program; if not, write to the Free Software Chris@0: # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Chris@0: Chris@0: class Role < ActiveRecord::Base Chris@1115: # Custom coder for the permissions attribute that should be an Chris@1115: # array of symbols. Rails 3 uses Psych which can be *unbelievably* Chris@1115: # slow on some platforms (eg. mingw32). Chris@1115: class PermissionsAttributeCoder Chris@1115: def self.load(str) Chris@1115: str.to_s.scan(/:([a-z0-9_]+)/).flatten.map(&:to_sym) Chris@1115: end Chris@1115: Chris@1115: def self.dump(value) Chris@1115: YAML.dump(value) Chris@1115: end Chris@1115: end Chris@1115: Chris@0: # Built-in roles Chris@0: BUILTIN_NON_MEMBER = 1 Chris@0: BUILTIN_ANONYMOUS = 2 Chris@909: Chris@441: ISSUES_VISIBILITY_OPTIONS = [ Chris@441: ['all', :label_issues_visibility_all], Chris@441: ['default', :label_issues_visibility_public], Chris@441: ['own', :label_issues_visibility_own] Chris@441: ] Chris@0: Chris@1464: scope :sorted, lambda { order("#{table_name}.builtin ASC, #{table_name}.position ASC") } Chris@1464: scope :givable, lambda { order("#{table_name}.position ASC").where(:builtin => 0) } Chris@1115: scope :builtin, lambda { |*args| Chris@1115: compare = (args.first == true ? 'not' : '') Chris@1115: where("#{compare} builtin = 0") Chris@0: } Chris@909: Chris@0: before_destroy :check_deletable Chris@1115: has_many :workflow_rules, :dependent => :delete_all do Chris@0: def copy(source_role) Chris@1115: WorkflowRule.copy(nil, source_role, nil, proxy_association.owner) Chris@0: end Chris@0: end Chris@1464: has_and_belongs_to_many :custom_fields, :join_table => "#{table_name_prefix}custom_fields_roles#{table_name_suffix}", :foreign_key => "role_id" Chris@909: Chris@0: has_many :member_roles, :dependent => :destroy Chris@0: has_many :members, :through => :member_roles Chris@0: acts_as_list Chris@909: Chris@1115: serialize :permissions, ::Role::PermissionsAttributeCoder Chris@0: attr_protected :builtin Chris@0: Chris@0: validates_presence_of :name Chris@0: validates_uniqueness_of :name Chris@0: validates_length_of :name, :maximum => 30 Chris@441: validates_inclusion_of :issues_visibility, Chris@441: :in => ISSUES_VISIBILITY_OPTIONS.collect(&:first), Chris@441: :if => lambda {|role| role.respond_to?(:issues_visibility)} Chris@909: Chris@1115: # Copies attributes from another role, arg can be an id or a Role Chris@1115: def copy_from(arg, options={}) Chris@1115: return unless arg.present? Chris@1115: role = arg.is_a?(Role) ? arg : Role.find_by_id(arg.to_s) Chris@1115: self.attributes = role.attributes.dup.except("id", "name", "position", "builtin", "permissions") Chris@1115: self.permissions = role.permissions.dup Chris@1115: self Chris@0: end Chris@909: Chris@0: def permissions=(perms) Chris@0: perms = perms.collect {|p| p.to_sym unless p.blank? }.compact.uniq if perms Chris@0: write_attribute(:permissions, perms) Chris@0: end Chris@0: Chris@0: def add_permission!(*perms) Chris@0: self.permissions = [] unless permissions.is_a?(Array) Chris@0: Chris@0: permissions_will_change! Chris@0: perms.each do |p| Chris@0: p = p.to_sym Chris@0: permissions << p unless permissions.include?(p) Chris@0: end Chris@0: save! Chris@0: end Chris@0: Chris@0: def remove_permission!(*perms) Chris@0: return unless permissions.is_a?(Array) Chris@0: permissions_will_change! Chris@0: perms.each { |p| permissions.delete(p.to_sym) } Chris@0: save! Chris@0: end Chris@909: Chris@0: # Returns true if the role has the given permission Chris@0: def has_permission?(perm) Chris@0: !permissions.nil? && permissions.include?(perm.to_sym) Chris@0: end Chris@909: Chris@0: def <=>(role) Chris@1115: if role Chris@1115: if builtin == role.builtin Chris@1115: position <=> role.position Chris@1115: else Chris@1115: builtin <=> role.builtin Chris@1115: end Chris@1115: else Chris@1115: -1 Chris@1115: end Chris@0: end Chris@909: Chris@0: def to_s Chris@0: name Chris@0: end Chris@909: Chris@441: def name Chris@441: case builtin Chris@441: when 1; l(:label_role_non_member, :default => read_attribute(:name)) Chris@441: when 2; l(:label_role_anonymous, :default => read_attribute(:name)) Chris@441: else; read_attribute(:name) Chris@441: end Chris@441: end Chris@909: Chris@0: # Return true if the role is a builtin role Chris@0: def builtin? Chris@0: self.builtin != 0 Chris@0: end Chris@909: Chris@1115: # Return true if the role is the anonymous role Chris@1115: def anonymous? Chris@1115: builtin == 2 Chris@1115: end Chris@1464: Chris@0: # Return true if the role is a project member role Chris@0: def member? Chris@0: !self.builtin? Chris@0: end Chris@909: Chris@0: # Return true if role is allowed to do the specified action Chris@0: # action can be: Chris@0: # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit') Chris@0: # * a permission Symbol (eg. :edit_project) Chris@0: def allowed_to?(action) Chris@0: if action.is_a? Hash Chris@0: allowed_actions.include? "#{action[:controller]}/#{action[:action]}" Chris@0: else Chris@0: allowed_permissions.include? action Chris@0: end Chris@0: end Chris@909: Chris@0: # Return all the permissions that can be given to the role Chris@0: def setable_permissions Chris@0: setable_permissions = Redmine::AccessControl.permissions - Redmine::AccessControl.public_permissions Chris@0: setable_permissions -= Redmine::AccessControl.members_only_permissions if self.builtin == BUILTIN_NON_MEMBER Chris@0: setable_permissions -= Redmine::AccessControl.loggedin_only_permissions if self.builtin == BUILTIN_ANONYMOUS Chris@0: setable_permissions Chris@0: end Chris@0: Chris@0: # Find all the roles that can be given to a project member Chris@0: def self.find_all_givable Chris@1115: Role.givable.all Chris@0: end Chris@0: Chris@0: # Return the builtin 'non member' role. If the role doesn't exist, Chris@0: # it will be created on the fly. Chris@0: def self.non_member Chris@909: find_or_create_system_role(BUILTIN_NON_MEMBER, 'Non member') Chris@0: end Chris@0: Chris@0: # Return the builtin 'anonymous' role. If the role doesn't exist, Chris@0: # it will be created on the fly. Chris@0: def self.anonymous Chris@909: find_or_create_system_role(BUILTIN_ANONYMOUS, 'Anonymous') Chris@0: end Chris@0: Chris@0: private Chris@909: Chris@0: def allowed_permissions Chris@0: @allowed_permissions ||= permissions + Redmine::AccessControl.public_permissions.collect {|p| p.name} Chris@0: end Chris@0: Chris@0: def allowed_actions Chris@0: @actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten Chris@0: end Chris@909: Chris@0: def check_deletable Chris@0: raise "Can't delete role" if members.any? Chris@0: raise "Can't delete builtin role" if builtin? Chris@0: end Chris@909: Chris@909: def self.find_or_create_system_role(builtin, name) Chris@1115: role = where(:builtin => builtin).first Chris@909: if role.nil? Chris@909: role = create(:name => name, :position => 0) do |r| Chris@909: r.builtin = builtin Chris@909: end Chris@909: raise "Unable to create the #{name} role." if role.new_record? Chris@909: end Chris@909: role Chris@909: end Chris@0: end