##// END OF EJS Templates
Makes issue safe_attributes extensible (#6000)....
Jean-Philippe Lang -
r4377:3409333522a7
parent child
Show More
@@ -0,0 +1,75
1 # Redmine - project management software
2 # Copyright (C) 2006-2010 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 module Redmine
19 module SafeAttributes
20 def self.included(base)
21 base.extend(ClassMethods)
22 end
23
24 module ClassMethods
25 # Declares safe attributes
26 # An optional Proc can be given for conditional inclusion
27 #
28 # Example:
29 # safe_attributes 'title', 'pages'
30 # safe_attributes 'isbn', :if => {|book, user| book.author == user}
31 def safe_attributes(*args)
32 @safe_attributes ||= []
33 if args.empty?
34 @safe_attributes
35 else
36 options = args.last.is_a?(Hash) ? args.pop : {}
37 @safe_attributes << [args, options]
38 end
39 end
40 end
41
42 # Returns an array that can be safely set by user or current user
43 #
44 # Example:
45 # book.safe_attributes # => ['title', 'pages']
46 # book.safe_attributes(book.author) # => ['title', 'pages', 'isbn']
47 def safe_attribute_names(user=User.current)
48 names = []
49 self.class.safe_attributes.collect do |attrs, options|
50 if options[:if].nil? || options[:if].call(self, user)
51 names += attrs.collect(&:to_s)
52 end
53 end
54 names.uniq
55 end
56
57 # Returns a hash with unsafe attributes removed
58 # from the given attrs hash
59 #
60 # Example:
61 # book.delete_unsafe_attributes({'title' => 'My book', 'foo' => 'bar'})
62 # # => {'title' => 'My book'}
63 def delete_unsafe_attributes(attrs, user=User.current)
64 safe = safe_attribute_names(user)
65 attrs.dup.delete_if {|k,v| !safe.include?(k)}
66 end
67
68 # Sets attributes from attrs that are safe
69 # attrs is a Hash with string keys
70 def safe_attributes=(attrs, user=User.current)
71 return unless attrs.is_a?(Hash)
72 self.attributes = delete_unsafe_attributes(attrs, user)
73 end
74 end
75 end
@@ -0,0 +1,87
1 # Redmine - project management software
2 # Copyright (C) 2006-2010 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 require File.dirname(__FILE__) + '/../../../test_helper'
19
20 class Redmine::SafeAttributesTest < ActiveSupport::TestCase
21
22 class Base
23 def attributes=(attrs)
24 attrs.each do |key, value|
25 send("#{key}=", value)
26 end
27 end
28 end
29
30 class Person < Base
31 attr_accessor :firstname, :lastname, :login
32 include Redmine::SafeAttributes
33 safe_attributes :firstname, :lastname
34 safe_attributes :login, :if => lambda {|person, user| user.admin?}
35 end
36
37 class Book < Base
38 attr_accessor :title
39 include Redmine::SafeAttributes
40 safe_attributes :title
41 end
42
43 def test_safe_attribute_names
44 p = Person.new
45 assert_equal ['firstname', 'lastname'], p.safe_attribute_names(User.anonymous)
46 assert_equal ['firstname', 'lastname', 'login'], p.safe_attribute_names(User.find(1))
47 end
48
49 def test_safe_attribute_names_without_user
50 p = Person.new
51 User.current = nil
52 assert_equal ['firstname', 'lastname'], p.safe_attribute_names
53 User.current = User.find(1)
54 assert_equal ['firstname', 'lastname', 'login'], p.safe_attribute_names
55 end
56
57 def test_set_safe_attributes
58 p = Person.new
59 p.send('safe_attributes=', {'firstname' => 'John', 'lastname' => 'Smith', 'login' => 'jsmith'}, User.anonymous)
60 assert_equal 'John', p.firstname
61 assert_equal 'Smith', p.lastname
62 assert_nil p.login
63
64 p = Person.new
65 User.current = User.find(1)
66 p.send('safe_attributes=', {'firstname' => 'John', 'lastname' => 'Smith', 'login' => 'jsmith'}, User.find(1))
67 assert_equal 'John', p.firstname
68 assert_equal 'Smith', p.lastname
69 assert_equal 'jsmith', p.login
70 end
71
72 def test_set_safe_attributes_without_user
73 p = Person.new
74 User.current = nil
75 p.safe_attributes = {'firstname' => 'John', 'lastname' => 'Smith', 'login' => 'jsmith'}
76 assert_equal 'John', p.firstname
77 assert_equal 'Smith', p.lastname
78 assert_nil p.login
79
80 p = Person.new
81 User.current = User.find(1)
82 p.safe_attributes = {'firstname' => 'John', 'lastname' => 'Smith', 'login' => 'jsmith'}
83 assert_equal 'John', p.firstname
84 assert_equal 'Smith', p.lastname
85 assert_equal 'jsmith', p.login
86 end
87 end
@@ -16,6 +16,8
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 Issue < ActiveRecord::Base
18 class Issue < ActiveRecord::Base
19 include Redmine::SafeAttributes
20
19 belongs_to :project
21 belongs_to :project
20 belongs_to :tracker
22 belongs_to :tracker
21 belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id'
23 belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id'
@@ -214,31 +216,29 class Issue < ActiveRecord::Base
214 write_attribute :estimated_hours, (h.is_a?(String) ? h.to_hours : h)
216 write_attribute :estimated_hours, (h.is_a?(String) ? h.to_hours : h)
215 end
217 end
216
218
217 SAFE_ATTRIBUTES = %w(
219 safe_attributes 'tracker_id',
218 tracker_id
220 'status_id',
219 status_id
221 'parent_issue_id',
220 parent_issue_id
222 'category_id',
221 category_id
223 'assigned_to_id',
222 assigned_to_id
224 'priority_id',
223 priority_id
225 'fixed_version_id',
224 fixed_version_id
226 'subject',
225 subject
227 'description',
226 description
228 'start_date',
227 start_date
229 'due_date',
228 due_date
230 'done_ratio',
229 done_ratio
231 'estimated_hours',
230 estimated_hours
232 'custom_field_values',
231 custom_field_values
233 'custom_fields',
232 custom_fields
234 'lock_version',
233 lock_version
235 :if => lambda {|issue, user| issue.new_record? || user.allowed_to?(:edit_issues, issue.project) }
234 ) unless const_defined?(:SAFE_ATTRIBUTES)
236
235
237 safe_attributes 'status_id',
236 SAFE_ATTRIBUTES_ON_TRANSITION = %w(
238 'assigned_to_id',
237 status_id
239 'fixed_version_id',
238 assigned_to_id
240 'done_ratio',
239 fixed_version_id
241 :if => lambda {|issue, user| issue.new_statuses_allowed_to(user).any? }
240 done_ratio
241 ) unless const_defined?(:SAFE_ATTRIBUTES_ON_TRANSITION)
242
242
243 # Safely sets attributes
243 # Safely sets attributes
244 # Should be called from controllers instead of #attributes=
244 # Should be called from controllers instead of #attributes=
@@ -249,13 +249,8 class Issue < ActiveRecord::Base
249 return unless attrs.is_a?(Hash)
249 return unless attrs.is_a?(Hash)
250
250
251 # User can change issue attributes only if he has :edit permission or if a workflow transition is allowed
251 # User can change issue attributes only if he has :edit permission or if a workflow transition is allowed
252 if new_record? || user.allowed_to?(:edit_issues, project)
252 attrs = delete_unsafe_attributes(attrs, user)
253 attrs = attrs.reject {|k,v| !SAFE_ATTRIBUTES.include?(k)}
253 return if attrs.empty?
254 elsif new_statuses_allowed_to(user).any?
255 attrs = attrs.reject {|k,v| !SAFE_ATTRIBUTES_ON_TRANSITION.include?(k)}
256 else
257 return
258 end
259
254
260 # Tracker must be set before since new_statuses_allowed_to depends on it.
255 # Tracker must be set before since new_statuses_allowed_to depends on it.
261 if t = attrs.delete('tracker_id')
256 if t = attrs.delete('tracker_id')
General Comments 0
You need to be logged in to leave comments. Login now