@@ -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 |
|
|
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 |
|
|
229 | 'due_date', | |
228 |
|
|
230 | 'done_ratio', | |
229 | done_ratio |
|
231 | 'estimated_hours', | |
230 | estimated_hours |
|
232 | 'custom_field_values', | |
231 |
custom_field |
|
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