##// END OF EJS Templates
Perf: use a custom decoder for Role#permissions instead of YAML.load....
Jean-Philippe Lang -
r9733:a1f17b982c72
parent child
Show More
@@ -1,185 +1,194
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class Role < ActiveRecord::Base
18 class Role < ActiveRecord::Base
19 # Custom coder for the permissions attribute that should be an
20 # array of symbols. Rails 3 uses Psych which can be *unbelievably*
21 # slow on some platforms (eg. mingw32).
22 class PermissionsAttributeCoder
23 def self.load(str)
24 str.to_s.scan(/:([a-z0-9_]+)/).flatten.map(&:to_sym)
25 end
26
27 def self.dump(value)
28 YAML.dump(value)
29 end
30 end
31
19 # Built-in roles
32 # Built-in roles
20 BUILTIN_NON_MEMBER = 1
33 BUILTIN_NON_MEMBER = 1
21 BUILTIN_ANONYMOUS = 2
34 BUILTIN_ANONYMOUS = 2
22
35
23 ISSUES_VISIBILITY_OPTIONS = [
36 ISSUES_VISIBILITY_OPTIONS = [
24 ['all', :label_issues_visibility_all],
37 ['all', :label_issues_visibility_all],
25 ['default', :label_issues_visibility_public],
38 ['default', :label_issues_visibility_public],
26 ['own', :label_issues_visibility_own]
39 ['own', :label_issues_visibility_own]
27 ]
40 ]
28
41
29 scope :sorted, order("#{table_name}.builtin ASC, #{table_name}.position ASC")
42 scope :sorted, order("#{table_name}.builtin ASC, #{table_name}.position ASC")
30 scope :givable, order("#{table_name}.position ASC").where(:builtin => 0)
43 scope :givable, order("#{table_name}.position ASC").where(:builtin => 0)
31 scope :builtin, lambda { |*args|
44 scope :builtin, lambda { |*args|
32 compare = (args.first == true ? 'not' : '')
45 compare = (args.first == true ? 'not' : '')
33 where("#{compare} builtin = 0")
46 where("#{compare} builtin = 0")
34 }
47 }
35
48
36 before_destroy :check_deletable
49 before_destroy :check_deletable
37 has_many :workflows, :dependent => :delete_all do
50 has_many :workflows, :dependent => :delete_all do
38 def copy(source_role)
51 def copy(source_role)
39 Workflow.copy(nil, source_role, nil, proxy_association.owner)
52 Workflow.copy(nil, source_role, nil, proxy_association.owner)
40 end
53 end
41 end
54 end
42
55
43 has_many :member_roles, :dependent => :destroy
56 has_many :member_roles, :dependent => :destroy
44 has_many :members, :through => :member_roles
57 has_many :members, :through => :member_roles
45 acts_as_list
58 acts_as_list
46
59
47 serialize :permissions, Array
60 serialize :permissions, ::Role::PermissionsAttributeCoder
48 attr_protected :builtin
61 attr_protected :builtin
49
62
50 validates_presence_of :name
63 validates_presence_of :name
51 validates_uniqueness_of :name
64 validates_uniqueness_of :name
52 validates_length_of :name, :maximum => 30
65 validates_length_of :name, :maximum => 30
53 validates_inclusion_of :issues_visibility,
66 validates_inclusion_of :issues_visibility,
54 :in => ISSUES_VISIBILITY_OPTIONS.collect(&:first),
67 :in => ISSUES_VISIBILITY_OPTIONS.collect(&:first),
55 :if => lambda {|role| role.respond_to?(:issues_visibility)}
68 :if => lambda {|role| role.respond_to?(:issues_visibility)}
56
69
57 def permissions
58 read_attribute(:permissions) || []
59 end
60
61 def permissions=(perms)
70 def permissions=(perms)
62 perms = perms.collect {|p| p.to_sym unless p.blank? }.compact.uniq if perms
71 perms = perms.collect {|p| p.to_sym unless p.blank? }.compact.uniq if perms
63 write_attribute(:permissions, perms)
72 write_attribute(:permissions, perms)
64 end
73 end
65
74
66 def add_permission!(*perms)
75 def add_permission!(*perms)
67 self.permissions = [] unless permissions.is_a?(Array)
76 self.permissions = [] unless permissions.is_a?(Array)
68
77
69 permissions_will_change!
78 permissions_will_change!
70 perms.each do |p|
79 perms.each do |p|
71 p = p.to_sym
80 p = p.to_sym
72 permissions << p unless permissions.include?(p)
81 permissions << p unless permissions.include?(p)
73 end
82 end
74 save!
83 save!
75 end
84 end
76
85
77 def remove_permission!(*perms)
86 def remove_permission!(*perms)
78 return unless permissions.is_a?(Array)
87 return unless permissions.is_a?(Array)
79 permissions_will_change!
88 permissions_will_change!
80 perms.each { |p| permissions.delete(p.to_sym) }
89 perms.each { |p| permissions.delete(p.to_sym) }
81 save!
90 save!
82 end
91 end
83
92
84 # Returns true if the role has the given permission
93 # Returns true if the role has the given permission
85 def has_permission?(perm)
94 def has_permission?(perm)
86 !permissions.nil? && permissions.include?(perm.to_sym)
95 !permissions.nil? && permissions.include?(perm.to_sym)
87 end
96 end
88
97
89 def <=>(role)
98 def <=>(role)
90 if role
99 if role
91 if builtin == role.builtin
100 if builtin == role.builtin
92 position <=> role.position
101 position <=> role.position
93 else
102 else
94 builtin <=> role.builtin
103 builtin <=> role.builtin
95 end
104 end
96 else
105 else
97 -1
106 -1
98 end
107 end
99 end
108 end
100
109
101 def to_s
110 def to_s
102 name
111 name
103 end
112 end
104
113
105 def name
114 def name
106 case builtin
115 case builtin
107 when 1; l(:label_role_non_member, :default => read_attribute(:name))
116 when 1; l(:label_role_non_member, :default => read_attribute(:name))
108 when 2; l(:label_role_anonymous, :default => read_attribute(:name))
117 when 2; l(:label_role_anonymous, :default => read_attribute(:name))
109 else; read_attribute(:name)
118 else; read_attribute(:name)
110 end
119 end
111 end
120 end
112
121
113 # Return true if the role is a builtin role
122 # Return true if the role is a builtin role
114 def builtin?
123 def builtin?
115 self.builtin != 0
124 self.builtin != 0
116 end
125 end
117
126
118 # Return true if the role is a project member role
127 # Return true if the role is a project member role
119 def member?
128 def member?
120 !self.builtin?
129 !self.builtin?
121 end
130 end
122
131
123 # Return true if role is allowed to do the specified action
132 # Return true if role is allowed to do the specified action
124 # action can be:
133 # action can be:
125 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
134 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
126 # * a permission Symbol (eg. :edit_project)
135 # * a permission Symbol (eg. :edit_project)
127 def allowed_to?(action)
136 def allowed_to?(action)
128 if action.is_a? Hash
137 if action.is_a? Hash
129 allowed_actions.include? "#{action[:controller]}/#{action[:action]}"
138 allowed_actions.include? "#{action[:controller]}/#{action[:action]}"
130 else
139 else
131 allowed_permissions.include? action
140 allowed_permissions.include? action
132 end
141 end
133 end
142 end
134
143
135 # Return all the permissions that can be given to the role
144 # Return all the permissions that can be given to the role
136 def setable_permissions
145 def setable_permissions
137 setable_permissions = Redmine::AccessControl.permissions - Redmine::AccessControl.public_permissions
146 setable_permissions = Redmine::AccessControl.permissions - Redmine::AccessControl.public_permissions
138 setable_permissions -= Redmine::AccessControl.members_only_permissions if self.builtin == BUILTIN_NON_MEMBER
147 setable_permissions -= Redmine::AccessControl.members_only_permissions if self.builtin == BUILTIN_NON_MEMBER
139 setable_permissions -= Redmine::AccessControl.loggedin_only_permissions if self.builtin == BUILTIN_ANONYMOUS
148 setable_permissions -= Redmine::AccessControl.loggedin_only_permissions if self.builtin == BUILTIN_ANONYMOUS
140 setable_permissions
149 setable_permissions
141 end
150 end
142
151
143 # Find all the roles that can be given to a project member
152 # Find all the roles that can be given to a project member
144 def self.find_all_givable
153 def self.find_all_givable
145 Role.givable.all
154 Role.givable.all
146 end
155 end
147
156
148 # Return the builtin 'non member' role. If the role doesn't exist,
157 # Return the builtin 'non member' role. If the role doesn't exist,
149 # it will be created on the fly.
158 # it will be created on the fly.
150 def self.non_member
159 def self.non_member
151 find_or_create_system_role(BUILTIN_NON_MEMBER, 'Non member')
160 find_or_create_system_role(BUILTIN_NON_MEMBER, 'Non member')
152 end
161 end
153
162
154 # Return the builtin 'anonymous' role. If the role doesn't exist,
163 # Return the builtin 'anonymous' role. If the role doesn't exist,
155 # it will be created on the fly.
164 # it will be created on the fly.
156 def self.anonymous
165 def self.anonymous
157 find_or_create_system_role(BUILTIN_ANONYMOUS, 'Anonymous')
166 find_or_create_system_role(BUILTIN_ANONYMOUS, 'Anonymous')
158 end
167 end
159
168
160 private
169 private
161
170
162 def allowed_permissions
171 def allowed_permissions
163 @allowed_permissions ||= permissions + Redmine::AccessControl.public_permissions.collect {|p| p.name}
172 @allowed_permissions ||= permissions + Redmine::AccessControl.public_permissions.collect {|p| p.name}
164 end
173 end
165
174
166 def allowed_actions
175 def allowed_actions
167 @actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten
176 @actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten
168 end
177 end
169
178
170 def check_deletable
179 def check_deletable
171 raise "Can't delete role" if members.any?
180 raise "Can't delete role" if members.any?
172 raise "Can't delete builtin role" if builtin?
181 raise "Can't delete builtin role" if builtin?
173 end
182 end
174
183
175 def self.find_or_create_system_role(builtin, name)
184 def self.find_or_create_system_role(builtin, name)
176 role = where(:builtin => builtin).first
185 role = where(:builtin => builtin).first
177 if role.nil?
186 if role.nil?
178 role = create(:name => name, :position => 0) do |r|
187 role = create(:name => name, :position => 0) do |r|
179 r.builtin = builtin
188 r.builtin = builtin
180 end
189 end
181 raise "Unable to create the #{name} role." if role.new_record?
190 raise "Unable to create the #{name} role." if role.new_record?
182 end
191 end
183 role
192 role
184 end
193 end
185 end
194 end
@@ -1,128 +1,133
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class RoleTest < ActiveSupport::TestCase
20 class RoleTest < ActiveSupport::TestCase
21 fixtures :roles, :workflows
21 fixtures :roles, :workflows
22
22
23 def test_sorted_scope
23 def test_sorted_scope
24 assert_equal Role.all.sort, Role.sorted.all
24 assert_equal Role.all.sort, Role.sorted.all
25 end
25 end
26
26
27 def test_givable_scope
27 def test_givable_scope
28 assert_equal Role.all.reject(&:builtin?).sort, Role.givable.all
28 assert_equal Role.all.reject(&:builtin?).sort, Role.givable.all
29 end
29 end
30
30
31 def test_builtin_scope
31 def test_builtin_scope
32 assert_equal Role.all.select(&:builtin?).sort, Role.builtin(true).all.sort
32 assert_equal Role.all.select(&:builtin?).sort, Role.builtin(true).all.sort
33 assert_equal Role.all.reject(&:builtin?).sort, Role.builtin(false).all.sort
33 assert_equal Role.all.reject(&:builtin?).sort, Role.builtin(false).all.sort
34 end
34 end
35
35
36 def test_copy_workflows
36 def test_copy_workflows
37 source = Role.find(1)
37 source = Role.find(1)
38 assert_equal 90, source.workflows.size
38 assert_equal 90, source.workflows.size
39
39
40 target = Role.new(:name => 'Target')
40 target = Role.new(:name => 'Target')
41 assert target.save
41 assert target.save
42 target.workflows.copy(source)
42 target.workflows.copy(source)
43 target.reload
43 target.reload
44 assert_equal 90, target.workflows.size
44 assert_equal 90, target.workflows.size
45 end
45 end
46
46
47 def test_permissions_should_be_unserialized_with_its_coder
48 Role::PermissionsAttributeCoder.expects(:load).once
49 Role.find(1).permissions
50 end
51
47 def test_add_permission
52 def test_add_permission
48 role = Role.find(1)
53 role = Role.find(1)
49 size = role.permissions.size
54 size = role.permissions.size
50 role.add_permission!("apermission", "anotherpermission")
55 role.add_permission!("apermission", "anotherpermission")
51 role.reload
56 role.reload
52 assert role.permissions.include?(:anotherpermission)
57 assert role.permissions.include?(:anotherpermission)
53 assert_equal size + 2, role.permissions.size
58 assert_equal size + 2, role.permissions.size
54 end
59 end
55
60
56 def test_remove_permission
61 def test_remove_permission
57 role = Role.find(1)
62 role = Role.find(1)
58 size = role.permissions.size
63 size = role.permissions.size
59 perm = role.permissions[0..1]
64 perm = role.permissions[0..1]
60 role.remove_permission!(*perm)
65 role.remove_permission!(*perm)
61 role.reload
66 role.reload
62 assert ! role.permissions.include?(perm[0])
67 assert ! role.permissions.include?(perm[0])
63 assert_equal size - 2, role.permissions.size
68 assert_equal size - 2, role.permissions.size
64 end
69 end
65
70
66 def test_name
71 def test_name
67 I18n.locale = 'fr'
72 I18n.locale = 'fr'
68 assert_equal 'Manager', Role.find(1).name
73 assert_equal 'Manager', Role.find(1).name
69 assert_equal 'Anonyme', Role.anonymous.name
74 assert_equal 'Anonyme', Role.anonymous.name
70 assert_equal 'Non membre', Role.non_member.name
75 assert_equal 'Non membre', Role.non_member.name
71 end
76 end
72
77
73 def test_find_all_givable
78 def test_find_all_givable
74 assert_equal Role.all.reject(&:builtin?).sort, Role.find_all_givable
79 assert_equal Role.all.reject(&:builtin?).sort, Role.find_all_givable
75 end
80 end
76
81
77 context "#anonymous" do
82 context "#anonymous" do
78 should "return the anonymous role" do
83 should "return the anonymous role" do
79 role = Role.anonymous
84 role = Role.anonymous
80 assert role.builtin?
85 assert role.builtin?
81 assert_equal Role::BUILTIN_ANONYMOUS, role.builtin
86 assert_equal Role::BUILTIN_ANONYMOUS, role.builtin
82 end
87 end
83
88
84 context "with a missing anonymous role" do
89 context "with a missing anonymous role" do
85 setup do
90 setup do
86 Role.delete_all("builtin = #{Role::BUILTIN_ANONYMOUS}")
91 Role.delete_all("builtin = #{Role::BUILTIN_ANONYMOUS}")
87 end
92 end
88
93
89 should "create a new anonymous role" do
94 should "create a new anonymous role" do
90 assert_difference('Role.count') do
95 assert_difference('Role.count') do
91 Role.anonymous
96 Role.anonymous
92 end
97 end
93 end
98 end
94
99
95 should "return the anonymous role" do
100 should "return the anonymous role" do
96 role = Role.anonymous
101 role = Role.anonymous
97 assert role.builtin?
102 assert role.builtin?
98 assert_equal Role::BUILTIN_ANONYMOUS, role.builtin
103 assert_equal Role::BUILTIN_ANONYMOUS, role.builtin
99 end
104 end
100 end
105 end
101 end
106 end
102
107
103 context "#non_member" do
108 context "#non_member" do
104 should "return the non-member role" do
109 should "return the non-member role" do
105 role = Role.non_member
110 role = Role.non_member
106 assert role.builtin?
111 assert role.builtin?
107 assert_equal Role::BUILTIN_NON_MEMBER, role.builtin
112 assert_equal Role::BUILTIN_NON_MEMBER, role.builtin
108 end
113 end
109
114
110 context "with a missing non-member role" do
115 context "with a missing non-member role" do
111 setup do
116 setup do
112 Role.delete_all("builtin = #{Role::BUILTIN_NON_MEMBER}")
117 Role.delete_all("builtin = #{Role::BUILTIN_NON_MEMBER}")
113 end
118 end
114
119
115 should "create a new non-member role" do
120 should "create a new non-member role" do
116 assert_difference('Role.count') do
121 assert_difference('Role.count') do
117 Role.non_member
122 Role.non_member
118 end
123 end
119 end
124 end
120
125
121 should "return the non-member role" do
126 should "return the non-member role" do
122 role = Role.non_member
127 role = Role.non_member
123 assert role.builtin?
128 assert role.builtin?
124 assert_equal Role::BUILTIN_NON_MEMBER, role.builtin
129 assert_equal Role::BUILTIN_NON_MEMBER, role.builtin
125 end
130 end
126 end
131 end
127 end
132 end
128 end
133 end
General Comments 0
You need to be logged in to leave comments. Login now