@@ -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 | 16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
17 | 17 | |
|
18 | 18 | class Issue < ActiveRecord::Base |
|
19 | include Redmine::SafeAttributes | |
|
20 | ||
|
19 | 21 | belongs_to :project |
|
20 | 22 | belongs_to :tracker |
|
21 | 23 | belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id' |
@@ -214,31 +216,29 class Issue < ActiveRecord::Base | |||
|
214 | 216 | write_attribute :estimated_hours, (h.is_a?(String) ? h.to_hours : h) |
|
215 | 217 | end |
|
216 | 218 | |
|
217 | SAFE_ATTRIBUTES = %w( | |
|
218 |
|
|
|
219 | status_id | |
|
220 | parent_issue_id | |
|
221 | category_id | |
|
222 | assigned_to_id | |
|
223 | priority_id | |
|
224 | fixed_version_id | |
|
225 | subject | |
|
226 | description | |
|
227 |
|
|
|
228 |
|
|
|
229 | done_ratio | |
|
230 | estimated_hours | |
|
231 |
custom_field |
|
|
232 | custom_fields | |
|
233 | lock_version | |
|
234 | ) unless const_defined?(:SAFE_ATTRIBUTES) | |
|
235 | ||
|
236 | SAFE_ATTRIBUTES_ON_TRANSITION = %w( | |
|
237 | status_id | |
|
238 | assigned_to_id | |
|
239 | fixed_version_id | |
|
240 | done_ratio | |
|
241 | ) unless const_defined?(:SAFE_ATTRIBUTES_ON_TRANSITION) | |
|
219 | safe_attributes 'tracker_id', | |
|
220 | 'status_id', | |
|
221 | 'parent_issue_id', | |
|
222 | 'category_id', | |
|
223 | 'assigned_to_id', | |
|
224 | 'priority_id', | |
|
225 | 'fixed_version_id', | |
|
226 | 'subject', | |
|
227 | 'description', | |
|
228 | 'start_date', | |
|
229 | 'due_date', | |
|
230 | 'done_ratio', | |
|
231 | 'estimated_hours', | |
|
232 | 'custom_field_values', | |
|
233 | 'custom_fields', | |
|
234 | 'lock_version', | |
|
235 | :if => lambda {|issue, user| issue.new_record? || user.allowed_to?(:edit_issues, issue.project) } | |
|
236 | ||
|
237 | safe_attributes 'status_id', | |
|
238 | 'assigned_to_id', | |
|
239 | 'fixed_version_id', | |
|
240 | 'done_ratio', | |
|
241 | :if => lambda {|issue, user| issue.new_statuses_allowed_to(user).any? } | |
|
242 | 242 | |
|
243 | 243 | # Safely sets attributes |
|
244 | 244 | # Should be called from controllers instead of #attributes= |
@@ -249,13 +249,8 class Issue < ActiveRecord::Base | |||
|
249 | 249 | return unless attrs.is_a?(Hash) |
|
250 | 250 | |
|
251 | 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) | |
|
253 | attrs = attrs.reject {|k,v| !SAFE_ATTRIBUTES.include?(k)} | |
|
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 | |
|
252 | attrs = delete_unsafe_attributes(attrs, user) | |
|
253 | return if attrs.empty? | |
|
259 | 254 | |
|
260 | 255 | # Tracker must be set before since new_statuses_allowed_to depends on it. |
|
261 | 256 | if t = attrs.delete('tracker_id') |
General Comments 0
You need to be logged in to leave comments.
Login now