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