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