Merge branch 'mass_assignment'

Conflicts:
	app/models/issue.rb
pull/41/head
Jens Ulferts 13 years ago
commit f695ff0c33
  1. 5
      app/controllers/documents_controller.rb
  2. 6
      app/controllers/issue_categories_controller.rb
  3. 89
      app/controllers/members_controller.rb
  4. 29
      app/controllers/messages_controller.rb
  5. 17
      app/controllers/news_controller.rb
  6. 3
      app/controllers/projects_controller.rb
  7. 8
      app/controllers/timelog_controller.rb
  8. 2
      app/controllers/users_controller.rb
  9. 7
      app/controllers/versions_controller.rb
  10. 2
      app/controllers/wikis_controller.rb
  11. 2
      app/models/attachment.rb
  12. 2
      app/models/board.rb
  13. 2
      app/models/change.rb
  14. 1
      app/models/changeset.rb
  15. 2
      app/models/comment.rb
  16. 5
      app/models/document.rb
  17. 2
      app/models/enabled_module.rb
  18. 2
      app/models/enumeration.rb
  19. 13
      app/models/issue.rb
  20. 12
      app/models/issue_category.rb
  21. 2
      app/models/journal.rb
  22. 12
      app/models/member.rb
  23. 12
      app/models/member_role.rb
  24. 10
      app/models/message.rb
  25. 5
      app/models/news.rb
  26. 4
      app/models/project.rb
  27. 2
      app/models/query.rb
  28. 2
      app/models/repository.rb
  29. 3
      app/models/time_entry.rb
  30. 2
      app/models/token.rb
  31. 12
      app/models/user.rb
  32. 4
      app/models/user_preference.rb
  33. 14
      app/models/version.rb
  34. 2
      app/models/watcher.rb
  35. 5
      app/models/wiki.rb
  36. 2
      app/models/wiki_content.rb
  37. 2
      app/models/workflow.rb
  38. 1
      app/views/members/_member_errors.rhtml
  39. 2
      config/initializers/backtrace_silencers.rb
  40. 26
      config/initializers/mass_assignment.rb
  41. 2
      config/locales/de.yml
  42. 1
      config/locales/en-GB.yml
  43. 2
      config/locales/en.yml
  44. 2
      config/locales/mk.yml
  45. 3
      test/functional/issue_categories_controller_test.rb
  46. 2
      test/functional/wiki_controller_test.rb
  47. 6
      test/unit/group_test.rb
  48. 9
      test/unit/issue_nested_set_test.rb
  49. 68
      test/unit/issue_test.rb
  50. 10
      test/unit/member_test.rb
  51. 2
      test/unit/project_test.rb
  52. 4
      test/unit/user_test.rb
  53. 32
      test/unit/version_test.rb
  54. 4
      test/unit/watcher_test.rb
  55. 2
      vendor/plugins/acts_as_journalized/lib/redmine/acts/journalized/users.rb
  56. 20
      vendor/plugins/find_mass_assignment/MIT-LICENSE
  57. 67
      vendor/plugins/find_mass_assignment/README.markdown
  58. 22
      vendor/plugins/find_mass_assignment/Rakefile
  59. 1
      vendor/plugins/find_mass_assignment/init.rb
  60. 1
      vendor/plugins/find_mass_assignment/install.rb
  61. 108
      vendor/plugins/find_mass_assignment/lib/find_mass_assignment.rb
  62. 5
      vendor/plugins/find_mass_assignment/lib/tasks/find_mass_assignment_tasks.rake
  63. 76
      vendor/plugins/find_mass_assignment/lib/unsafe_build_and_create.rb
  64. 1
      vendor/plugins/find_mass_assignment/uninstall.rb

@ -43,8 +43,9 @@ class DocumentsController < ApplicationController
end
def new
@document = @project.documents.build(params[:document])
if request.post? and @document.save
@document = @project.documents.build
@document.safe_attributes = params[:document]
if request.post? && @document.save
attachments = Attachment.attach_files(@document, params[:attachments])
render_attachment_warning_if_needed(@document)
flash[:notice] = l(:notice_successful_create)

@ -23,7 +23,8 @@ class IssueCategoriesController < ApplicationController
verify :method => :post, :only => :destroy
def new
@category = @project.issue_categories.build(params[:category])
@category = @project.issue_categories.build
@category.safe_attributes = params[:category]
if request.post?
if @category.save
respond_to do |format|
@ -50,7 +51,8 @@ class IssueCategoriesController < ApplicationController
end
def edit
if request.post? and @category.update_attributes(params[:category])
@category.safe_attributes = params[:category]
if request.post? and @category.save
flash[:notice] = l(:notice_successful_update)
redirect_to :controller => 'projects', :action => 'settings', :tab => 'categories', :id => @project
end

@ -20,18 +20,11 @@ class MembersController < ApplicationController
before_filter :authorize
def new
members = []
if params[:member] && request.post?
attrs = params[:member].dup
if (user_ids = attrs.delete(:user_ids))
user_ids.each do |user_id|
members << Member.new(attrs.merge(:user_id => user_id))
end
else
members << Member.new(attrs)
end
if params[:member]
members = new_members_from_params
@project.members << members
end
respond_to do |format|
if members.present? && members.all? {|m| m.valid? }
@ -45,30 +38,35 @@ class MembersController < ApplicationController
}
}
else
format.js {
render(:update) {|page|
errors = members.collect {|m|
m.errors.full_messages
}.flatten.uniq
page.alert(l(:notice_failed_to_save_members, :errors => errors.join(', ')))
}
if params[:member]
page.insert_html :top, "tab-content-members", :partial => "members/member_errors", :locals => {:member => members.first}
else
page.insert_html :top, "tab-content-members", content_tag(:div,
content_tag(:ul,
content_tag(:li,
content_tag(:a, l(:error_check_user_and_role)))),
:class => "errorExplanation", :id => "errorExplanation")
end
}
}
end
end
end
def edit
if request.post? and @member.update_attributes(params[:member])
if request.post? and
member = update_member_from_params and
member.save
respond_to do |format|
format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project }
format.js {
render(:update) {|page|
render(:update) { |page|
page.replace_html "tab-content-members", :partial => 'projects/settings/members'
page << 'hideOnLoad()'
page.visual_effect(:highlight, "member-#{@member.id}")
page.visual_effect(:highlight, "member-#{@member.id}") unless Member.find_by_id(@member.id).nil?
}
}
end
@ -94,4 +92,53 @@ class MembersController < ApplicationController
render :layout => false
end
private
def new_members_from_params
members = []
attrs = params[:member].dup
user_ids = if attrs[:user_ids].present?
attrs.delete(:user_ids)
elsif attrs[:user_id].present?
[attrs.delete(:user_id)]
else
[]
end
roles = Role.find_all_by_id(attrs.delete(:role_ids))
user_ids.each do |user_id|
member = Member.new attrs
member.roles = roles
member.user_id = user_id
members << member
end
members
end
def update_member_from_params
# this way, mass assignment is considered and all updates happen in one transaction (autosave)
attrs = params[:member].dup
attrs.delete(:id)
role_ids = attrs.delete(:role_ids).map(&:to_i).select{ |i| i > 0 }
roles = Role.find_all_by_id(role_ids)
# Keep inherited roles
role_ids += @member.member_roles.select { |mr| !mr.inherited_from.nil? }.collect(&:role_id)
new_role_ids = role_ids - @member.role_ids
# Add new roles
new_role_ids.each { |id| @member.member_roles.build.tap { |r| r.role_id = id } }
# Remove roles (Rails' #role_ids= will not trigger MemberRole#on_destroy)
member_roles_to_destroy = @member.member_roles.select { |mr| !role_ids.include?(mr.role_id) }
if member_roles_to_destroy.any?
member_roles_to_destroy.each(&:mark_for_destruction)
Watcher.prune(:user => @member.principal, :project => @member.project)
end
@member.attributes = attrs
@member
end
end

@ -48,26 +48,26 @@ class MessagesController < ApplicationController
# Create a new topic
def new
@message = Message.new(params[:message])
@message = Message.new
@message.author = User.current
@message.board = @board
if params[:message] && User.current.allowed_to?(:edit_messages, @project)
@message.locked = params[:message]['locked']
@message.sticky = params[:message]['sticky']
end
if request.post? && @message.save
call_hook(:controller_messages_new_after_save, { :params => params, :message => @message})
attachments = Attachment.attach_files(@message, params[:attachments])
render_attachment_warning_if_needed(@message)
redirect_to :action => 'show', :id => @message
@message.safe_attributes = params[:message]
if request.post?
if @message.save
call_hook(:controller_messages_new_after_save, { :params => params, :message => @message})
attachments = Attachment.attach_files(@message, params[:attachments])
render_attachment_warning_if_needed(@message)
redirect_to :action => 'show', :id => @message
end
end
end
# Reply to a topic
def reply
@reply = Message.new(params[:reply])
@reply = Message.new
@reply.author = User.current
@reply.board = @board
@reply.safe_attributes = params[:reply]
@topic.children << @reply
if !@reply.new_record?
call_hook(:controller_messages_reply_after_save, { :params => params, :message => @reply})
@ -80,11 +80,8 @@ class MessagesController < ApplicationController
# Edit a message
def edit
(render_403; return false) unless @message.editable_by?(User.current)
if params[:message]
@message.locked = params[:message]['locked']
@message.sticky = params[:message]['sticky']
end
if request.post? && @message.update_attributes(params[:message])
@message.safe_attributes = params[:message]
if request.post? && @message.save
attachments = Attachment.attach_files(@message, params[:attachments])
render_attachment_warning_if_needed(@message)
flash[:notice] = l(:notice_successful_update)

@ -61,14 +61,12 @@ class NewsController < ApplicationController
def create
@news = News.new(:project => @project, :author => User.current)
if request.post?
@news.attributes = params[:news]
if @news.save
flash[:notice] = l(:notice_successful_create)
redirect_to :controller => 'news', :action => 'index', :project_id => @project
else
render :action => 'new'
end
@news.safe_attributes = params[:news]
if @news.save
flash[:notice] = l(:notice_successful_create)
redirect_to :controller => 'news', :action => 'index', :project_id => @project
else
render :action => 'new'
end
end
@ -76,7 +74,8 @@ class NewsController < ApplicationController
end
def update
if request.put? and @news.update_attributes(params[:news])
@news.safe_attributes = params[:news]
if @news.save
flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'show', :id => @news
else

@ -59,7 +59,8 @@ class ProjectsController < ApplicationController
def new
@issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
@trackers = Tracker.all
@project = Project.new(params[:project])
@project = Project.new
@project.safe_attributes = params[:project]
end
verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }

@ -97,7 +97,7 @@ class TimelogController < ApplicationController
def new
@time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
@time_entry.attributes = params[:time_entry]
@time_entry.safe_attributes = params[:time_entry]
call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
render :action => 'edit'
@ -106,7 +106,7 @@ class TimelogController < ApplicationController
verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
def create
@time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
@time_entry.attributes = params[:time_entry]
@time_entry.safe_attributes = params[:time_entry]
call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
@ -127,14 +127,14 @@ class TimelogController < ApplicationController
end
def edit
@time_entry.attributes = params[:time_entry]
@time_entry.safe_attributes = params[:time_entry]
call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
end
verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
def update
@time_entry.attributes = params[:time_entry]
@time_entry.safe_attributes = params[:time_entry]
call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })

@ -136,7 +136,7 @@ class UsersController < ApplicationController
def update
@user.admin = params[:user][:admin] if params[:user][:admin]
@user.login = params[:user][:login] if params[:user][:login]
@user.safe_attributes = params[:user]
@user.safe_attributes = params[:user].except(:login) # :login is protected
if params[:user][:password].present? && @user.change_password_allowed?
@user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation]
end

@ -56,7 +56,7 @@ class VersionsController < ApplicationController
if params[:version]
attributes = params[:version].dup
attributes.delete('sharing') unless attributes.nil? || @version.allowed_sharings.include?(attributes['sharing'])
@version.attributes = attributes
@version.safe_attributes = attributes
end
end
@ -66,7 +66,7 @@ class VersionsController < ApplicationController
if params[:version]
attributes = params[:version].dup
attributes.delete('sharing') unless attributes.nil? || @version.allowed_sharings.include?(attributes['sharing'])
@version.attributes = attributes
@version.safe_attributes = attributes
end
if request.post?
@ -101,7 +101,8 @@ class VersionsController < ApplicationController
if request.put? && params[:version]
attributes = params[:version].dup
attributes.delete('sharing') unless @version.allowed_sharings.include?(attributes['sharing'])
if @version.update_attributes(attributes)
@version.safe_attributes = attributes
if @version.save
flash[:notice] = l(:notice_successful_update)
redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
else

@ -19,7 +19,7 @@ class WikisController < ApplicationController
# Create or update a project's wiki
def edit
@wiki = @project.wiki || Wiki.new(:project => @project)
@wiki.attributes = params[:wiki]
@wiki.safe_attributes = params[:wiki]
@wiki.save if request.post?
render(:update) {|page| page.replace_html "tab-content-wiki", :partial => 'projects/settings/wiki'}
end

@ -23,6 +23,8 @@ class Attachment < ActiveRecord::Base
belongs_to :author, :class_name => "User", :foreign_key => "author_id"
attr_protected :author_id
validates_presence_of :container, :filename, :author
validates_length_of :filename, :maximum => 255
validates_length_of :disk_filename, :maximum => 255

@ -19,6 +19,8 @@ class Board < ActiveRecord::Base
belongs_to :last_message, :class_name => 'Message', :foreign_key => :last_message_id
acts_as_list :scope => :project_id
acts_as_watchable
attr_protected :project_id
validates_presence_of :name, :description
validates_length_of :name, :maximum => 30

@ -20,6 +20,8 @@ class Change < ActiveRecord::Base
delegate :repository_encoding, :to => :changeset, :allow_nil => true, :prefix => true
attr_protected :changeset_id
def relative_path
changeset.repository.relative_path(path)
end

@ -32,6 +32,7 @@ class Changeset < ActiveRecord::Base
:project_key => "#{Repository.table_name}.project_id",
:date_column => 'committed_on'
attr_protected :user_id
validates_presence_of :repository_id, :revision, :committed_on, :commit_date
validates_uniqueness_of :revision, :scope => :repository_id

@ -16,5 +16,7 @@ class Comment < ActiveRecord::Base
belongs_to :commented, :polymorphic => true, :counter_cache => true
belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
attr_protected :author_id
validates_presence_of :commented, :author, :comments
end

@ -13,6 +13,7 @@
#++
class Document < ActiveRecord::Base
include Redmine::SafeAttributes
belongs_to :project
belongs_to :category, :class_name => "DocumentCategory", :foreign_key => "category_id"
acts_as_attachable :delete_permission => :manage_documents
@ -24,6 +25,8 @@ class Document < ActiveRecord::Base
end)
acts_as_searchable :columns => ['title', "#{table_name}.description"], :include => :project
attr_protected :project_id
validates_presence_of :project, :title, :category
validates_length_of :title, :maximum => 60
@ -31,6 +34,8 @@ class Document < ActiveRecord::Base
named_scope :visible, lambda {|*args| { :include => :project,
:conditions => Project.allowed_to_condition(args.first || User.current, :view_documents) } }
safe_attributes 'category_id', 'title', 'description'
def visible?(user=User.current)
!user.nil? && user.allowed_to?(:view_documents, project)
end

@ -15,6 +15,8 @@
class EnabledModule < ActiveRecord::Base
belongs_to :project
attr_protected :project_id
validates_presence_of :name
validates_uniqueness_of :name, :scope => :project_id

@ -22,6 +22,8 @@ class Enumeration < ActiveRecord::Base
acts_as_tree :order => 'position ASC'
before_destroy :check_integrity
attr_protected :project_id
validates_presence_of :name
validates_uniqueness_of :name, :scope => [:type, :project_id]

@ -61,6 +61,8 @@ class Issue < ActiveRecord::Base
DONE_RATIO_OPTIONS = %w(issue_field issue_status)
attr_protected :project_id, :author_id
validates_presence_of :subject, :priority, :project, :tracker, :author, :status
validates_length_of :subject, :maximum => 255
@ -119,7 +121,8 @@ class Issue < ActiveRecord::Base
def copy_from(arg)
issue = arg.is_a?(Issue) ? arg : Issue.visible.find(arg)
self.attributes = issue.attributes.dup.except("id", "root_id", "parent_id", "lft", "rgt", "created_on", "updated_on")
# attributes don't come from form, so it's save to force assign
self.force_attributes = issue.attributes.dup.except("id", "root_id", "parent_id", "lft", "rgt", "created_on", "updated_on")
self.parent_issue_id = issue.parent_id if issue.parent_id
self.custom_field_values = issue.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h}
self.status = issue.status
@ -560,7 +563,7 @@ class Issue < ActiveRecord::Base
s << ' assigned-to-me' if User.current.logged? && assigned_to_id == User.current.id
s
end
# Saves an issue, time_entry, attachments, and a journal from the parameters
# Returns false if save fails
def save_issue_with_child_records(params, existing_time_entry=nil)
@ -590,16 +593,16 @@ class Issue < ActiveRecord::Base
rescue ActiveRecord::StaleObjectError
attachments[:files].each(&:destroy)
error_message = l(:notice_locking_conflict)
journals_since = self.journals.after(lock_version)
if journals_since.any?
changes = journals_since.map { |j| "#{j.user.name} (#{j.created_at.to_s(:short)})" }
error_message << " " << l(:notice_locking_conflict_additional_information, :users => changes.join(', '))
end
error_message << " " << l(:notice_locking_conflict_reload_page)
errors.add_to_base error_message
raise ActiveRecord::Rollback
end

@ -13,14 +13,26 @@
#++
class IssueCategory < ActiveRecord::Base
include Redmine::SafeAttributes
belongs_to :project
belongs_to :assigned_to, :class_name => 'User', :foreign_key => 'assigned_to_id'
has_many :issues, :foreign_key => 'category_id', :dependent => :nullify
attr_protected :project_id
validates_presence_of :name
validates_uniqueness_of :name, :scope => [:project_id]
validates_length_of :name, :maximum => 30
# validates that assignee is member of the issue category's project
validates_each :assigned_to_id do |record, attr, value|
if value # allow nil
record.errors.add(attr, l(:error_must_be_project_member)) unless record.project.user_ids.include?(value.to_i)
end
end
safe_attributes 'name', 'assigned_to_id'
alias :destroy_without_reassign :destroy
# Destroy the category

@ -32,6 +32,8 @@ class Journal < ActiveRecord::Base
belongs_to :journaled, :class_name => 'Journal'
belongs_to :user
#attr_protected :user_id
# "touch" the journaled object on creation
after_create :touch_journaled_after_creation

@ -15,10 +15,12 @@
class Member < ActiveRecord::Base
belongs_to :user
belongs_to :principal, :foreign_key => 'user_id'
has_many :member_roles, :dependent => :destroy
has_many :member_roles, :dependent => :destroy, :autosave => true
has_many :roles, :through => :member_roles
belongs_to :project
attr_protected :project_id, :user_id, :role_ids
validates_presence_of :principal, :project
validates_uniqueness_of :user_id, :scope => :project_id
@ -36,7 +38,7 @@ class Member < ActiveRecord::Base
new_role_ids = ids - role_ids
# Add new roles
new_role_ids.each {|id| member_roles << MemberRole.new(:role_id => id) }
new_role_ids.each {|id| member_roles << MemberRole.new.tap {|r| r.role_id = id } }
# Remove roles (Rails' #role_ids= will not trigger MemberRole#on_destroy)
member_roles_to_destroy = member_roles.select {|mr| !ids.include?(mr.role_id)}
if member_roles_to_destroy.any?
@ -72,14 +74,16 @@ class Member < ActiveRecord::Base
# Find or initilize a Member with an id, attributes, and for a Principal
def self.edit_membership(id, new_attributes, principal=nil)
@membership = id.present? ? Member.find(id) : Member.new(:principal => principal)
@membership.attributes = new_attributes
# interface refactoring needed
# not critical atm because only admins can invoke it (see users and groups controllers)
@membership.force_attributes = new_attributes
@membership
end
protected
def validate
errors.add_on_empty :role if member_roles.empty? && roles.empty?
errors.add_on_empty :role if member_roles.empty? && roles.empty? || !member_roles.empty? && member_roles.all?(&:marked_for_destruction?)
end
private

@ -16,11 +16,11 @@ class MemberRole < ActiveRecord::Base
belongs_to :member
belongs_to :role
after_destroy :remove_member_if_empty
after_create :add_role_to_group_users
after_destroy :remove_role_from_group_users
attr_protected :member_id, :role_id
validates_presence_of :role
def validate
@ -33,14 +33,8 @@ class MemberRole < ActiveRecord::Base
private
def remove_member_if_empty
if member.roles.empty?
member.destroy
end
end
def add_role_to_group_users
if member.principal.is_a?(Group)
if member && member.principal.is_a?(Group)
member.principal.users.each do |user|
user_member = Member.find_by_project_id_and_user_id(member.project_id, user.id) || Member.new(:project_id => member.project_id, :user_id => user.id)
user_member.member_roles << MemberRole.new(:role => role, :inherited_from => id)

@ -13,6 +13,7 @@
#++
class Message < ActiveRecord::Base
include Redmine::SafeAttributes
belongs_to :board
belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
acts_as_tree :counter_cache => :replies_count, :order => "#{Message.table_name}.created_on ASC"
@ -39,7 +40,8 @@ class Message < ActiveRecord::Base
acts_as_watchable
attr_protected :locked, :sticky
attr_protected :locked, :sticky, :author_id
validates_presence_of :board, :subject, :content
validates_length_of :subject, :maximum => 255
@ -48,6 +50,12 @@ class Message < ActiveRecord::Base
named_scope :visible, lambda {|*args| { :include => {:board => :project},
:conditions => Project.allowed_to_condition(args.first || User.current, :view_messages) } }
safe_attributes 'subject', 'content'
safe_attributes 'locked', 'sticky',
:if => lambda {|message, user|
user.allowed_to?(:edit_messages, message.project)
}
def visible?(user=User.current)
!user.nil? && user.allowed_to?(:view_messages, project)
end

@ -13,10 +13,13 @@
#++
class News < ActiveRecord::Base
include Redmine::SafeAttributes
belongs_to :project
belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
has_many :comments, :as => :commented, :dependent => :delete_all, :order => "created_on"
attr_protected :project_id, :author_id
validates_presence_of :title, :description
validates_length_of :title, :maximum => 60
validates_length_of :summary, :maximum => 255
@ -32,6 +35,8 @@ class News < ActiveRecord::Base
:conditions => Project.allowed_to_condition(args.first || User.current, :view_news)
}}
safe_attributes 'title', 'summary', 'description'
def visible?(user=User.current)
!user.nil? && user.allowed_to?(:view_news, project)
end

@ -671,7 +671,7 @@ class Project < ActiveRecord::Base
def copy_issue_categories(project)
project.issue_categories.each do |issue_category|
new_issue_category = IssueCategory.new
new_issue_category.attributes = issue_category.attributes.dup.except("id", "project_id")
new_issue_category.send(:attributes=, issue_category.attributes.dup.except("id", "project_id"), false)
self.issue_categories << new_issue_category
end
end
@ -753,7 +753,7 @@ class Project < ActiveRecord::Base
members_to_copy.each do |member|
new_member = Member.new
new_member.attributes = member.attributes.dup.except("id", "project_id", "created_on")
new_member.send(:attributes=, member.attributes.dup.except("id", "project_id", "created_on"), false)
# only copy non inherited roles
# inherited roles will be added when copying the group membership
role_ids = member.member_roles.reject(&:inherited?).collect(&:role_id)

@ -20,7 +20,7 @@ class Query < ActiveRecord::Base
serialize :column_names
serialize :sort_criteria, Array
attr_protected :project_id, :user_id
attr_protected :project_id #, :user_id
validates_presence_of :name, :on => :save
validates_length_of :name, :maximum => 255

@ -22,6 +22,8 @@ class Repository < ActiveRecord::Base
# Raw SQL to delete changesets and changes in the database
# has_many :changesets, :dependent => :destroy is too slow for big repositories
before_destroy :clear_changesets
attr_protected :project_id
validates_length_of :password, :maximum => 255, :allow_nil => true
# Checks if the SCM is enabled when creating a repository

@ -13,6 +13,7 @@
#++
class TimeEntry < ActiveRecord::Base
include Redmine::SafeAttributes
# could have used polymorphic association
# project association here allows easy loading of time entries at project level with one database trip
belongs_to :project
@ -37,6 +38,8 @@ class TimeEntry < ActiveRecord::Base
:conditions => Project.allowed_to_condition(args.first || User.current, :view_time_entries)
}}
safe_attributes 'hours', 'comments', 'issue_id', 'activity_id', 'spent_on', 'custom_field_values'
def after_initialize
if new_record? && self.activity.nil?
if default_activity = TimeEntryActivity.default

@ -15,6 +15,8 @@
class Token < ActiveRecord::Base
belongs_to :user
validates_uniqueness_of :value
#attr_protected :user_id
before_create :delete_previous_tokens

@ -155,7 +155,9 @@ class User < Principal
# user is not yet registered, try to authenticate with available sources
attrs = AuthSource.authenticate(login, password)
if attrs
user = new(attrs)
# login is both safe and protected in chilis core code
# in case it's intentional we keep it that way
user = new(attrs.except(:login))
user.login = login
user.language = Setting.default_language
if user.save
@ -551,7 +553,13 @@ class User < Principal
def self.anonymous
anonymous_user = AnonymousUser.find(:first)
if anonymous_user.nil?
anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
(anonymous_user = AnonymousUser.new.tap do |u|
u.lastname = 'Anonymous'
u.login = ''
u.firstname = ''
u.mail = ''
u.status = 0
end).save
raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
end
anonymous_user

@ -15,8 +15,10 @@
class UserPreference < ActiveRecord::Base
belongs_to :user
serialize :others
#attr_protected :user_id
attr_protected :others
attr_protected :others, :user_id
def initialize(attributes = nil)
super

@ -13,6 +13,7 @@
#++
class Version < ActiveRecord::Base
include Redmine::SafeAttributes
after_update :update_issues_from_sharing_change
belongs_to :project
has_many :fixed_issues, :class_name => 'Issue', :foreign_key => 'fixed_version_id', :dependent => :nullify
@ -23,10 +24,13 @@ class Version < ActiveRecord::Base
VERSION_STATUSES = %w(open locked closed)
VERSION_SHARINGS = %w(none descendants hierarchy tree system)
attr_protected :project_id
validates_presence_of :name
validates_uniqueness_of :name, :scope => [:project_id]
validates_length_of :name, :maximum => 60
validates_format_of :effective_date, :with => /^\d{4}-\d{2}-\d{2}$/, :message => :not_a_date, :allow_nil => true
validates_format_of :start_date, :with => /^\d{4}-\d{2}-\d{2}$/, :message => :not_a_date, :allow_nil => true
validates_inclusion_of :status, :in => VERSION_STATUSES
validates_inclusion_of :sharing, :in => VERSION_SHARINGS
@ -34,6 +38,16 @@ class Version < ActiveRecord::Base
named_scope :visible, lambda {|*args| { :include => :project,
:conditions => Project.allowed_to_condition(args.first || User.current, :view_issues) } }
safe_attributes 'name',
'description',
'effective_date',
'due_date',
'start_date',
'wiki_page_title',
'status',
'sharing',
'custom_field_values'
# Returns true if +user+ or current user is allowed to view the version
def visible?(user=User.current)
user.allowed_to?(:view_issues, self.project)

@ -15,6 +15,8 @@
class Watcher < ActiveRecord::Base
belongs_to :watchable, :polymorphic => true
belongs_to :user
#attr_protected :user_id
validates_presence_of :user
validates_uniqueness_of :user_id, :scope => [:watchable_type, :watchable_id]

@ -13,15 +13,20 @@
#++
class Wiki < ActiveRecord::Base
include Redmine::SafeAttributes
belongs_to :project
has_many :pages, :class_name => 'WikiPage', :dependent => :destroy, :order => 'title'
has_many :redirects, :class_name => 'WikiRedirect', :dependent => :delete_all
acts_as_watchable
attr_protected :project_id
validates_presence_of :start_page
validates_format_of :start_page, :with => /^[^,\.\/\?\;\|\:]*$/
safe_attributes 'start_page', 'tabs_attributes'
def visible?(user=User.current)
!user.nil? && user.allowed_to?(:view_wiki_pages, project)
end

@ -21,6 +21,8 @@ class WikiContent < ActiveRecord::Base
validates_length_of :comments, :maximum => 255, :allow_nil => true
attr_accessor :comments
#attr_protected :author_id
before_save :comments_to_journal_notes

@ -17,6 +17,8 @@ class Workflow < ActiveRecord::Base
belongs_to :old_status, :class_name => 'IssueStatus', :foreign_key => 'old_status_id'
belongs_to :new_status, :class_name => 'IssueStatus', :foreign_key => 'new_status_id'
#attr_protected :role_id
validates_presence_of :role, :old_status, :new_status
# Returns workflow transitions count by tracker and role

@ -0,0 +1 @@
<%= error_messages_for :member, :object => member %>

@ -18,4 +18,4 @@
# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
# You can also remove all the silencers if you're trying do debug a problem that might steem from framework code.
# Rails.backtrace_cleaner.remove_silencers!
Rails.backtrace_cleaner.remove_silencers!

@ -0,0 +1,26 @@
class ActiveRecord::Base
# for full security the following call will disallow any attributes by default
# attr_accessible
# call this to force mass assignment even of protected attributes
def force_attributes=(new_attributes)
self.send(:attributes=, new_attributes, false)
end
# this override will protected the foreign key of certain belongs_to associations by default (see #protected_association?)
# def self.belongs_to(association_id, options = {})
# ret = super
# if protected_association? association_id
# foreign_key = options[:foreign_key] || "#{association_id}_id"
# attr_protected foreign_key
# end
# ret
# end
private
# def protected_association?(association_id)
# PROTECTED_ASSOCIATIONS.include? association_id.to_s
# end
#
# PROTECTED_ASSOCIATIONS = %w[project user author]
end

@ -209,6 +209,8 @@ de:
error_workflow_copy_target: Bitte wählen Sie die Ziel-Tracker und -Rollen.
error_unable_delete_issue_status: "Der Ticket-Status konnte nicht gelöscht werden."
error_unable_to_connect: Fehler beim Verbinden (%{value})
error_check_user_and_role: "Bitte wählen Sie einen Nutzer und eine Rolle."
error_must_be_project_member: "muss Mitglied des Projekts sein"
warning_attachments_not_saved: "%{count} Datei(en) konnten nicht gespeichert werden."
mail_subject_lost_password: "Ihr %{value} Kennwort"

@ -195,6 +195,7 @@ en-GB:
error_workflow_copy_target: 'Please select target tracker(s) and role(s)'
error_unable_delete_issue_status: 'Unable to delete issue status'
error_unable_to_connect: "Unable to connect (%{value})"
error_must_be_project_member: "must be project member"
warning_attachments_not_saved: "%{count} file(s) could not be saved."
mail_subject_lost_password: "Your %{value} password"

@ -194,6 +194,8 @@ en:
error_workflow_copy_target: 'Please select target tracker(s) and role(s)'
error_unable_delete_issue_status: 'Unable to delete issue status'
error_unable_to_connect: "Unable to connect (%{value})"
error_check_user_and_role: "Please choose a user and a role."
error_must_be_project_member: "must be project member"
warning_attachments_not_saved: "%{count} file(s) could not be saved."
mail_subject_lost_password: "Your %{value} password"

@ -155,7 +155,7 @@ mk:
notice_successful_delete: Успешно бришење.
notice_successful_connection: Успешна конекција.
notice_file_not_found: The page you were trying to access doesn't exist or has been removed.
notice_locking_conflict: Data has been updated by another user.
notice_locking_conflict: Information has been updated by at least one other user in the meantime.
notice_not_authorized: You are not authorized to access this page.
notice_email_sent: "Е-порака е пратена на %{value}"
notice_email_error: "Се случи грешка при праќање на е-пораката (%{value})"

@ -38,12 +38,13 @@ class IssueCategoriesControllerTest < ActionController::TestCase
def test_post_new
@request.session[:user_id] = 2 # manager
assert_difference 'IssueCategory.count' do
post :new, :project_id => '1', :category => {:name => 'New category'}
post :new, :project_id => '1', :category => {:name => 'New category', :assigned_to_id => 3}
end
assert_redirected_to '/projects/ecookbook/settings/categories'
category = IssueCategory.find_by_name('New category')
assert_not_nil category
assert_equal 1, category.project_id
assert_equal 3, category.assigned_to_id
end
def test_post_edit

@ -174,7 +174,7 @@ class WikiControllerTest < ActionController::TestCase
assert_template 'edit'
assert_tag :div,
:attributes => { :class => /error/ },
:content => /Data has been updated by another user/
:content => /Information has been updated by at least one other user in the meantime/
assert_tag 'textarea',
:attributes => { :name => 'content[text]' },
:content => /Text should not be lost/

@ -26,7 +26,7 @@ class GroupTest < ActiveSupport::TestCase
user = User.find(9)
project = Project.first
Member.create!(:principal => group, :project => project, :role_ids => [1, 2])
(m = Member.new.force_attributes = {:principal => group, :project => project, :role_ids => [1, 2]}).save!
group.users << user
assert user.member_of?(project)
end
@ -37,7 +37,7 @@ class GroupTest < ActiveSupport::TestCase
project = Project.first
group.users << user
m = Member.create!(:principal => group, :project => project, :role_ids => [1, 2])
(m = Member.new.force_attributes = {:principal => group, :project => project, :role_ids => [1, 2]}).save!
assert user.member_of?(project)
end
@ -46,7 +46,7 @@ class GroupTest < ActiveSupport::TestCase
user = User.find(9)
project = Project.first
group.users << user
m = Member.create!(:principal => group, :project => project, :role_ids => [1])
(m = Member.new.force_attributes = {:principal => group, :project => project, :role_ids => [1]}).save!
assert_equal [1], user.reload.roles_for_project(project).collect(&:id).sort
m.role_ids = [1, 2]

@ -184,9 +184,9 @@ class IssueNestedSetTest < ActiveSupport::TestCase
issue2 = create_issue!
issue3 = create_issue!(:parent_issue_id => issue2.id)
issue4 = create_issue!
r1 = IssueRelation.create!(:issue_from => issue1, :issue_to => issue2, :relation_type => IssueRelation::TYPE_PRECEDES)
r2 = IssueRelation.create!(:issue_from => issue1, :issue_to => issue3, :relation_type => IssueRelation::TYPE_PRECEDES)
r3 = IssueRelation.create!(:issue_from => issue2, :issue_to => issue4, :relation_type => IssueRelation::TYPE_PRECEDES)
(r1 = IssueRelation.new).force_attributes = {:issue_from => issue1, :issue_to => issue2, :relation_type => IssueRelation::TYPE_PRECEDES}
(r2 = IssueRelation.new).force_attributes = {:issue_from => issue1, :issue_to => issue3, :relation_type => IssueRelation::TYPE_PRECEDES}
(r3 = IssueRelation.new).force_attributes = {:issue_from => issue2, :issue_to => issue4, :relation_type => IssueRelation::TYPE_PRECEDES}
issue2.reload
issue2.parent_issue_id = issue1.id
issue2.save!
@ -356,6 +356,7 @@ class IssueNestedSetTest < ActiveSupport::TestCase
# Helper that creates an issue with default attributes
def create_issue!(attributes={})
Issue.create!({:project_id => 1, :tracker_id => 1, :author_id => 1, :subject => 'test'}.merge(attributes))
(i = Issue.new.force_attributes = {:project_id => 1, :tracker_id => 1, :author_id => 1, :subject => 'test'}.merge(attributes)).save!
i
end
end

@ -25,14 +25,14 @@ class IssueTest < ActiveSupport::TestCase
:time_entries
def test_create
issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => IssuePriority.all.first, :subject => 'test_create', :description => 'IssueTest#test_create', :estimated_hours => '1:30')
issue = Issue.new.force_attributes = {:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => IssuePriority.all.first, :subject => 'test_create', :description => 'IssueTest#test_create', :estimated_hours => '1:30'}
assert issue.save
issue.reload
assert_equal 1.5, issue.estimated_hours
end
def test_create_minimal
issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => IssuePriority.all.first, :subject => 'test_create')
issue = Issue.new.force_attributes = {:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => IssuePriority.all.first, :subject => 'test_create'}
assert issue.save
assert issue.description.nil?
end
@ -41,7 +41,7 @@ class IssueTest < ActiveSupport::TestCase
field = IssueCustomField.find_by_name('Database')
field.update_attribute(:is_required, true)
issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :subject => 'test_create', :description => 'IssueTest#test_create_with_required_custom_field')
issue = Issue.new.force_attributes = {:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :subject => 'test_create', :description => 'IssueTest#test_create_with_required_custom_field'}
assert issue.available_custom_fields.include?(field)
# No value for the custom field
assert !issue.save
@ -85,7 +85,7 @@ class IssueTest < ActiveSupport::TestCase
issues = Issue.visible(user).all
assert issues.empty?
# User should see issues of projects for which he has view_issues permissions only
Member.create!(:principal => user, :project_id => 2, :role_ids => [1])
Member.new.force_attributes = {:principal => user, :project_id => 2, :role_ids => [1]}.save!
user.reload
issues = Issue.visible(user).all
assert issues.any?
@ -105,7 +105,7 @@ class IssueTest < ActiveSupport::TestCase
def test_errors_full_messages_should_include_custom_fields_errors
field = IssueCustomField.find_by_name('Database')
issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :subject => 'test_create', :description => 'IssueTest#test_create_with_required_custom_field')
issue = Issue.new.force_attributes = {:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :subject => 'test_create', :description => 'IssueTest#test_create_with_required_custom_field'}
assert issue.available_custom_fields.include?(field)
# Invalid value
issue.custom_field_values = { field.id => 'SQLServer' }
@ -162,7 +162,7 @@ class IssueTest < ActiveSupport::TestCase
end
def test_assigning_tracker_id_should_reload_custom_fields_values
issue = Issue.new(:project => Project.find(1))
issue = Issue.new.force_attributes = {:project => Project.find(1)}
assert issue.custom_field_values.empty?
issue.tracker_id = 1
assert issue.custom_field_values.any?
@ -172,7 +172,7 @@ class IssueTest < ActiveSupport::TestCase
attributes = ActiveSupport::OrderedHash.new
attributes['custom_field_values'] = { '1' => 'MySQL' }
attributes['tracker_id'] = '1'
issue = Issue.new(:project => Project.find(1))
issue = Issue.new.force_attributes = {:project => Project.find(1)}
issue.attributes = attributes
assert_not_nil issue.custom_value_for(1)
assert_equal 'MySQL', issue.custom_value_for(1).value
@ -202,7 +202,7 @@ class IssueTest < ActiveSupport::TestCase
end
def test_category_based_assignment
issue = Issue.create(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => IssuePriority.all.first, :subject => 'Assignment test', :description => 'Assignment test', :category_id => 1)
(issue = Issue.new.force_attributes = {:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => IssuePriority.all.first, :subject => 'Assignment test', :description => 'Assignment test', :category_id => 1}).save!
assert_equal IssueCategory.find(1).assigned_to, issue.assigned_to
end
@ -255,7 +255,7 @@ class IssueTest < ActiveSupport::TestCase
def test_should_close_duplicates
# Create 3 issues
issue1 = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :priority => IssuePriority.all.first, :subject => 'Duplicates test', :description => 'Duplicates test')
issue1 = Issue.new.force_attributes = {:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :priority => IssuePriority.all.first, :subject => 'Duplicates test', :description => 'Duplicates test'}
assert issue1.save
issue2 = issue1.clone
assert issue2.save
@ -263,11 +263,11 @@ class IssueTest < ActiveSupport::TestCase
assert issue3.save
# 2 is a dupe of 1
IssueRelation.create(:issue_from => issue2, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES)
IssueRelation.new.force_attributes = {:issue_from => issue2, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES}
# And 3 is a dupe of 2
IssueRelation.create(:issue_from => issue3, :issue_to => issue2, :relation_type => IssueRelation::TYPE_DUPLICATES)
IssueRelation.new.force_attributes = {:issue_from => issue3, :issue_to => issue2, :relation_type => IssueRelation::TYPE_DUPLICATES}
# And 3 is a dupe of 1 (circular duplicates)
IssueRelation.create(:issue_from => issue3, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES)
IssueRelation.new.force_attributes = {:issue_from => issue3, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES}
assert issue1.reload.duplicates.include?(issue2)
@ -282,13 +282,13 @@ class IssueTest < ActiveSupport::TestCase
def test_should_not_close_duplicated_issue
# Create 3 issues
issue1 = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :priority => IssuePriority.all.first, :subject => 'Duplicates test', :description => 'Duplicates test')
issue1 = Issue.new.force_attributes = {:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :priority => IssuePriority.all.first, :subject => 'Duplicates test', :description => 'Duplicates test'}
assert issue1.save
issue2 = issue1.clone
assert issue2.save
# 2 is a dupe of 1
IssueRelation.create(:issue_from => issue2, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES)
IssueRelation.new.force_attributes = {:issue_from => issue2, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES}
# 2 is a dup of 1 but 1 is not a duplicate of 2
assert !issue2.reload.duplicates.include?(issue1)
@ -301,24 +301,24 @@ class IssueTest < ActiveSupport::TestCase
end
def test_assignable_versions
issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 1, :subject => 'New issue')
issue = Issue.new.force_attributes = {:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 1, :subject => 'New issue'}
assert_equal ['open'], issue.assignable_versions.collect(&:status).uniq
end
def test_should_not_be_able_to_assign_a_new_issue_to_a_closed_version
issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 1, :subject => 'New issue')
issue = Issue.new.force_attributes = {:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 1, :subject => 'New issue'}
assert !issue.save
assert_not_nil issue.errors.on(:fixed_version_id)
end
def test_should_not_be_able_to_assign_a_new_issue_to_a_locked_version
issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 2, :subject => 'New issue')
issue = Issue.new.force_attributes = {:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 2, :subject => 'New issue'}
assert !issue.save
assert_not_nil issue.errors.on(:fixed_version_id)
end
def test_should_be_able_to_assign_a_new_issue_to_an_open_version
issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 3, :subject => 'New issue')
issue = Issue.new.force_attributes = {:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 3, :subject => 'New issue'}
assert issue.save
end
@ -531,9 +531,9 @@ class IssueTest < ActiveSupport::TestCase
end
def test_rescheduling_an_issue_should_reschedule_following_issue
issue1 = Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :subject => '-', :start_date => Date.today, :due_date => Date.today + 2)
issue2 = Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :subject => '-', :start_date => Date.today, :due_date => Date.today + 2)
IssueRelation.create!(:issue_from => issue1, :issue_to => issue2, :relation_type => IssueRelation::TYPE_PRECEDES)
(issue1 = Issue.new.force_attributes = {:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :subject => '-', :start_date => Date.today, :due_date => Date.today + 2}).save!
(issue2 = Issue.new.force_attributes = {:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :subject => '-', :start_date => Date.today, :due_date => Date.today + 2}).save!
IssueRelation.new.force_attributes = {:issue_from => issue1, :issue_to => issue2, :relation_type => IssueRelation::TYPE_PRECEDES}.save!
assert_equal issue1.due_date + 1, issue2.reload.start_date
issue1.due_date = Date.today + 5
@ -596,7 +596,7 @@ class IssueTest < ActiveSupport::TestCase
def test_create_should_send_email_notification
ActionMailer::Base.deliveries.clear
issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => IssuePriority.all.first, :subject => 'test_create', :estimated_hours => '1:30')
issue = Issue.new.force_attributes = {:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => IssuePriority.all.first, :subject => 'test_create', :estimated_hours => '1:30'}
assert issue.save
assert_equal 1, ActionMailer::Base.deliveries.size
@ -643,31 +643,31 @@ class IssueTest < ActiveSupport::TestCase
def test_all_dependent_issues
IssueRelation.delete_all
assert IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => IssueRelation::TYPE_PRECEDES)
assert IssueRelation.create!(:issue_from => Issue.find(2), :issue_to => Issue.find(3), :relation_type => IssueRelation::TYPE_PRECEDES)
assert IssueRelation.create!(:issue_from => Issue.find(3), :issue_to => Issue.find(8), :relation_type => IssueRelation::TYPE_PRECEDES)
assert IssueRelation.new.force_attributes = {:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => IssueRelation::TYPE_PRECEDES}.save!
assert IssueRelation.new.force_attributes = {:issue_from => Issue.find(2), :issue_to => Issue.find(3), :relation_type => IssueRelation::TYPE_PRECEDES}.save!
assert IssueRelation.new.force_attributes = {:issue_from => Issue.find(3), :issue_to => Issue.find(8), :relation_type => IssueRelation::TYPE_PRECEDES}.save!
assert_equal [2, 3, 8], Issue.find(1).all_dependent_issues.collect(&:id).sort
end
def test_all_dependent_issues_with_persistent_circular_dependency
IssueRelation.delete_all
assert IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => IssueRelation::TYPE_PRECEDES)
assert IssueRelation.create!(:issue_from => Issue.find(2), :issue_to => Issue.find(3), :relation_type => IssueRelation::TYPE_PRECEDES)
assert IssueRelation.new.force_attributes = {:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => IssueRelation::TYPE_PRECEDES}
assert IssueRelation.new.force_attributes = {:issue_from => Issue.find(2), :issue_to => Issue.find(3), :relation_type => IssueRelation::TYPE_PRECEDES}
# Validation skipping
assert IssueRelation.new(:issue_from => Issue.find(3), :issue_to => Issue.find(1), :relation_type => IssueRelation::TYPE_PRECEDES).save(false)
assert IssueRelation.new.force_attributes = {:issue_from => Issue.find(3), :issue_to => Issue.find(1), :relation_type => IssueRelation::TYPE_PRECEDES}.save(false)
assert_equal [2, 3], Issue.find(1).all_dependent_issues.collect(&:id).sort
end
def test_all_dependent_issues_with_persistent_multiple_circular_dependencies
IssueRelation.delete_all
assert IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => IssueRelation::TYPE_RELATES)
assert IssueRelation.create!(:issue_from => Issue.find(2), :issue_to => Issue.find(3), :relation_type => IssueRelation::TYPE_RELATES)
assert IssueRelation.create!(:issue_from => Issue.find(3), :issue_to => Issue.find(8), :relation_type => IssueRelation::TYPE_RELATES)
assert IssueRelation.new.force_attributes = {:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => IssueRelation::TYPE_RELATES}
assert IssueRelation.new.force_attributes = {:issue_from => Issue.find(2), :issue_to => Issue.find(3), :relation_type => IssueRelation::TYPE_RELATES}
assert IssueRelation.new.force_attributes = {:issue_from => Issue.find(3), :issue_to => Issue.find(8), :relation_type => IssueRelation::TYPE_RELATES}
# Validation skipping
assert IssueRelation.new(:issue_from => Issue.find(8), :issue_to => Issue.find(2), :relation_type => IssueRelation::TYPE_RELATES).save(false)
assert IssueRelation.new(:issue_from => Issue.find(3), :issue_to => Issue.find(1), :relation_type => IssueRelation::TYPE_RELATES).save(false)
assert IssueRelation.new.force_attributes = {:issue_from => Issue.find(8), :issue_to => Issue.find(2), :relation_type => IssueRelation::TYPE_RELATES}.save(false)
assert IssueRelation.new.force_attributes = {:issue_from => Issue.find(3), :issue_to => Issue.find(1), :relation_type => IssueRelation::TYPE_RELATES}.save(false)
assert_equal [2, 3, 8], Issue.find(1).all_dependent_issues.collect(&:id).sort
end
@ -870,7 +870,7 @@ class IssueTest < ActiveSupport::TestCase
def test_create_should_not_send_email_notification_if_told_not_to
ActionMailer::Base.deliveries.clear
issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => IssuePriority.first, :subject => 'test_create', :estimated_hours => '1:30')
issue = Issue.new.force_attributes = {:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => IssuePriority.first, :subject => 'test_create', :estimated_hours => '1:30'}
IssueObserver.instance.send_notification = false
assert issue.save

@ -21,7 +21,7 @@ class MemberTest < ActiveSupport::TestCase
end
def test_create
member = Member.new(:project_id => 1, :user_id => 4, :role_ids => [1, 2])
member = Member.new.force_attributes = {:project_id => 1, :user_id => 4, :role_ids => [1, 2]}
assert member.save
member.reload
@ -46,11 +46,11 @@ class MemberTest < ActiveSupport::TestCase
end
def test_validate
member = Member.new(:project_id => 1, :user_id => 2, :role_ids => [2])
member = Member.new.force_attributes = {:project_id => 1, :user_id => 2, :role_ids => [2]}
# same use can't have more than one membership for a project
assert !member.save
member = Member.new(:project_id => 1, :user_id => 2, :role_ids => [])
member = Member.new.force_attributes = {:project_id => 1, :user_id => 2, :role_ids => []}
# must have one role at least
assert !member.save
end
@ -80,7 +80,7 @@ class MemberTest < ActiveSupport::TestCase
context "of user" do
setup do
@member = Member.create!(:project => Project.find(2), :principal => User.find(9), :role_ids => [1, 2])
(@member = Member.new.force_attributes = {:project => Project.find(2), :principal => User.find(9), :role_ids => [1, 2]}).save!
end
context "by deleting membership" do
@ -107,7 +107,7 @@ class MemberTest < ActiveSupport::TestCase
context "of group" do
setup do
group = Group.find(10)
@member = Member.create!(:project => Project.find(2), :principal => group, :role_ids => [1, 2])
@member = (Member.new.force_attributes = {:project => Project.find(2), :principal => group, :role_ids => [1, 2]}).save!
group.users << User.find(9)
end

@ -837,7 +837,7 @@ class ProjectTest < ActiveSupport::TestCase
user = User.find(7)
group.users << user
# group role
Member.create!(:project_id => @source_project.id, :principal => group, :role_ids => [2])
(Member.new.force_attributes = {:project_id => @source_project.id, :principal => group, :role_ids => [2]}).save
member = Member.find_by_user_id_and_project_id(user.id, @source_project.id)
# additional role
member.role_ids = [1]

@ -510,7 +510,7 @@ class UserTest < ActiveSupport::TestCase
should "be false for a user with :only_my_events and isn't an author, creator, or assignee" do
@user = User.generate_with_protected!(:mail_notification => 'only_my_events')
Member.create!(:user => @user, :project => @project, :role_ids => [1])
(Member.new.force_attributes = {:user => @user, :project => @project, :role_ids => [1]}).save!
assert ! @user.notify_about?(@issue)
end
@ -556,7 +556,7 @@ class UserTest < ActiveSupport::TestCase
should "be false for a user with :selected and is not the author or assignee" do
@user = User.generate_with_protected!(:mail_notification => 'selected')
Member.create!(:user => @user, :project => @project, :role_ids => [1])
(Member.new.force_attributes = {:user => @user, :project => @project, :role_ids => [1]}).save!
assert ! @user.notify_about?(@issue)
end
end

@ -20,13 +20,13 @@ class VersionTest < ActiveSupport::TestCase
end
def test_create
v = Version.new(:project => Project.find(1), :name => '1.1', :effective_date => '2011-03-25')
(v = Version.new.force_attributes = {:project => Project.find(1), :name => '1.1', :effective_date => '2011-03-25' })
assert v.save
assert_equal 'open', v.status
end
def test_invalid_effective_date_validation
v = Version.new(:project => Project.find(1), :name => '1.1', :effective_date => '99999-01-01')
(v = Version.new.force_attributes = {:project => Project.find(1), :name => '1.1', :effective_date => '99999-01-01' })
assert !v.save
assert_equal I18n.translate('activerecord.errors.messages.not_a_date'), v.errors.on(:effective_date)
end
@ -35,7 +35,7 @@ class VersionTest < ActiveSupport::TestCase
context "with no value saved" do
should "be the date of the earlist issue" do
project = Project.find(1)
v = Version.create!(:project => project, :name => 'Progress')
(v = Version.new.force_attributes = {:project => project, :name => 'Progress'}).save!
add_issue(v, :estimated_hours => 10, :start_date => '2010-03-01')
Issue.generate_for_project!(project, :subject => 'not assigned', :start_date => '2010-01-01')
@ -46,8 +46,8 @@ class VersionTest < ActiveSupport::TestCase
context "with a value saved" do
should "be the value" do
project = Project.find(1)
v = Version.create!(:project => project, :name => 'Progress', :start_date => '2010-01-05')
add_issue(v, :estimated_hours => 10, :start_date => '2010-03-01')
(v = Version.new.force_attributes = {:project => project, :name => 'Progress', :start_date => '2010-01-05'})
add_issue(v, :estimated_hours => 10, :start_date => '2010-03-01').save!
assert_equal '2010-01-05', v.start_date.to_s
end
@ -58,14 +58,14 @@ class VersionTest < ActiveSupport::TestCase
def test_progress_should_be_0_with_no_assigned_issues
project = Project.find(1)
v = Version.create!(:project => project, :name => 'Progress')
(v = Version.new.force_attributes = {:project => project, :name => 'Progress'}).save!
assert_equal 0, v.completed_pourcent
assert_equal 0, v.closed_pourcent
end
def test_progress_should_be_0_with_unbegun_assigned_issues
project = Project.find(1)
v = Version.create!(:project => project, :name => 'Progress')
(v = Version.new.force_attributes = {:project => project, :name => 'Progress'}).save!
add_issue(v)
add_issue(v, :done_ratio => 0)
assert_progress_equal 0, v.completed_pourcent
@ -75,7 +75,7 @@ class VersionTest < ActiveSupport::TestCase
def test_progress_should_be_100_with_closed_assigned_issues
project = Project.find(1)
status = IssueStatus.find(:first, :conditions => {:is_closed => true})
v = Version.create!(:project => project, :name => 'Progress')
(v = Version.new.force_attributes = {:project => project, :name => 'Progress'}).save!
add_issue(v, :status => status)
add_issue(v, :status => status, :done_ratio => 20)
add_issue(v, :status => status, :done_ratio => 70, :estimated_hours => 25)
@ -86,7 +86,7 @@ class VersionTest < ActiveSupport::TestCase
def test_progress_should_consider_done_ratio_of_open_assigned_issues
project = Project.find(1)
v = Version.create!(:project => project, :name => 'Progress')
(v = Version.new.force_attributes = {:project => project, :name => 'Progress'}).save!
add_issue(v)
add_issue(v, :done_ratio => 20)
add_issue(v, :done_ratio => 70)
@ -96,7 +96,7 @@ class VersionTest < ActiveSupport::TestCase
def test_progress_should_consider_closed_issues_as_completed
project = Project.find(1)
v = Version.create!(:project => project, :name => 'Progress')
(v = Version.new.force_attributes = {:project => project, :name => 'Progress'}).save!
add_issue(v)
add_issue(v, :done_ratio => 20)
add_issue(v, :status => IssueStatus.find(:first, :conditions => {:is_closed => true}))
@ -106,7 +106,7 @@ class VersionTest < ActiveSupport::TestCase
def test_progress_should_consider_estimated_hours_to_weigth_issues
project = Project.find(1)
v = Version.create!(:project => project, :name => 'Progress')
(v = Version.new.force_attributes = {:project => project, :name => 'Progress'}).save!
add_issue(v, :estimated_hours => 10)
add_issue(v, :estimated_hours => 20, :done_ratio => 30)
add_issue(v, :estimated_hours => 40, :done_ratio => 10)
@ -117,7 +117,7 @@ class VersionTest < ActiveSupport::TestCase
def test_progress_should_consider_average_estimated_hours_to_weigth_unestimated_issues
project = Project.find(1)
v = Version.create!(:project => project, :name => 'Progress')
(v = Version.new.force_attributes = {:project => project, :name => 'Progress'}).save!
add_issue(v, :done_ratio => 20)
add_issue(v, :status => IssueStatus.find(:first, :conditions => {:is_closed => true}))
add_issue(v, :estimated_hours => 10, :done_ratio => 30)
@ -132,7 +132,7 @@ class VersionTest < ActiveSupport::TestCase
@project = Project.generate!(:identifier => 'test0')
@project.trackers << Tracker.generate!
@version = Version.generate!(:project => @project, :effective_date => nil)
(@version = Version.new.force_attributes = {:project => @project, :effective_date => nil)).save!
end
should "be false if there are no issues assigned" do
@ -178,7 +178,7 @@ class VersionTest < ActiveSupport::TestCase
context "#estimated_hours" do
setup do
@version = Version.create!(:project_id => 1, :name => '#estimated_hours')
(@version = Version.new.force_attributes = {:project_id => 1, :name => '#estimated_hours')).save!
end
should "return 0 with no assigned issues" do
@ -242,11 +242,11 @@ class VersionTest < ActiveSupport::TestCase
private
def add_issue(version, attributes={})
Issue.create!({:project => version.project,
(Issue.new.force_attributes = {:project => version.project,
:fixed_version => version,
:subject => 'Test',
:author => User.find(:first),
:tracker => version.project.trackers.find(:first)}.merge(attributes))
:tracker => version.project.trackers.find(:first)}.merge(attributes)).save!
end
def assert_progress_equal(expected_float, actual_float, message="")

@ -54,7 +54,7 @@ class WatcherTest < ActiveSupport::TestCase
@user.reload
assert !@user.valid?
issue = Issue.new(:project => Project.find(1), :tracker_id => 1, :subject => "test", :author => User.find(2))
(issue = Issue.new).force_attributes = {:project => Project.find(1), :tracker_id => 1, :subject => "test", :author => User.find(2)}
issue.watcher_users << @user
issue.save!
assert issue.watched_by?(@user)
@ -106,7 +106,7 @@ class WatcherTest < ActiveSupport::TestCase
Watcher.create!(:watchable => WikiPage.find(2), :user => user)
# private project (id: 2)
Member.create!(:project => Project.find(2), :principal => user, :role_ids => [1])
(Member.new.force_attributes = {:project => Project.find(2), :principal => user, :role_ids => [1]}).save!
Watcher.create!(:watchable => Issue.find(4), :user => user)
Watcher.create!(:watchable => Message.find(7), :user => user)
Watcher.create!(:watchable => Wiki.find(2), :user => user)

@ -68,7 +68,7 @@ module Redmine::Acts::Journalized
def self.included(base) # :nodoc:
base.class_eval do
belongs_to :user
# attr_protected :user_id
alias_method_chain :user=, :name
end
end

@ -0,0 +1,20 @@
Copyright (c) 2008 Michael Hartl
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

@ -0,0 +1,67 @@
# Find Mass Assignment
## A Rails plugin to find likely mass assignment vulnerabilities
The <tt>find\_mass\_assignment</tt> Rake task defined by the plugin finds likely mass assignment problems in Rails projects.
The method is to scan the controllers for likely mass assignment, and then find the corresponding models that *don't* have <tt>attr\_accessible</tt> defined. Any time that happens, it's a potential problem.
Install this plugin as follows:
$ script/plugin install git://github.com/mhartl/find_mass_assignment.git
For more information, see my [brief review of mass assignment](http://blog.mhartl.com/2008/09/21/mass-assignment-in-rails-applications/) and my discussion of [how to fix mass assignment vulnerabilities in Rails](http://blog.mhartl.com/2008/09/21/finding-and-fixing-mass-assignment-problems-in-rails-applications/).
**Warning:** For convenience, the plugin defines some "unsafe" attribute updates (see below), including a method called <tt>unsafe\_attributes=</tt> to bypass the <tt>attr\_accessible</tt> restrictions. This means that any attribute protected with <tt>attr\_protected</tt> can also be bypassed simply by hitting the application at a URL like
<pre>http://127.0.0.1:3000/.../?user[unsafe_attributes][admin]=1</pre>
As a result, if you use this plugin, **always use <tt>attr\_accessible</tt> in every model that is exposed to mass assignment via a web interface**.
(I tried working around this in <tt>unsafe\_attributes=</tt> by testing each attribute to make sure it wasn't protected, but merely testing whether <tt>attr\_protected</tt> included a given attribute, using <tt>self.class.attr\_protected.include?</tt>, somehow violated the restriction that no model can define both <tt>attr\_accessible</tt> and <tt>attr\_protected</tt>. The result was massive breakage in my test suites for any model that defined <tt>attr\_accessible</tt>, which is usually all of them.)
## Example
Suppose line 17 of the Users controller is
@user = User.new(params[:user])
but the User model *doesn't* define <tt>attr_accessible</tt>. Then we get the output
$ rake find_mass_assignment
/path/to/app/controllers/users_controller.rb
17 @user = User.new(params[:user])
This indicates that the User model has a likely mass assignment vulnerability. In the case of no apparent vulnerabilities, the rake task simply returns nothing.
The Unix exit status code of the rake task is 0 on success, 1 on failure, which means it can be used in a pre-commit hook. For example, if you use Git for version control, you can check for mass assignment vulnerabilities before each commit by putting
<pre>rake find_mass_assignment</pre>
at the end of the <tt>.git/hooks/pre-commit</tt> file.* Any commits that introduce potential mass assignment vulnerabilities (as determined by the plugin) will then fail automatically.
*Be sure to make the pre-commit hook file executable if it isn't already:
<pre>$ chmod +x .git/hooks/pre-commit</pre>
(You might also want to comment out the weird Perl script that's the default pre-commit hook on some systems; it gives you warnings like "You have some suspicious patch lines" that you probably don't want.)
# Unsafe attribute updates
It is often useful to override <tt>attr\_accessible</tt>, especially at the console and in tests, so the plugin also adds an assortment of helper methods to Active Record:
* unsafe\_new
* unsafe\_build
* unsafe\_create/unsafe\_create!
* unsafe\_update\_attributes/unsafe\_update\_attributes!
These work just like their safe counterparts, except they bypass attr\_accessible. For example,
<pre>Person.unsafe_new(:admin => true)</pre>
works even if <tt>admin</tt> isn't attr\_accessible.
# Copyright
Copyright (c) 2008 Michael Hartl, released under the MIT license

@ -0,0 +1,22 @@
require 'rake'
require 'rake/testtask'
require 'rake/rdoctask'
desc 'Default: run unit tests.'
task :default => :test
desc 'Test the find_mass_assignment plugin.'
Rake::TestTask.new(:test) do |t|
t.libs << 'lib'
t.pattern = 'test/**/*_test.rb'
t.verbose = true
end
desc 'Generate documentation for the find_mass_assignment plugin.'
Rake::RDocTask.new(:rdoc) do |rdoc|
rdoc.rdoc_dir = 'rdoc'
rdoc.title = 'FindMassAssignment'
rdoc.options << '--line-numbers' << '--inline-source'
rdoc.rdoc_files.include('README')
rdoc.rdoc_files.include('lib/**/*.rb')
end

@ -0,0 +1 @@
require File.join(File.dirname(__FILE__), "lib", "unsafe_build_and_create")

@ -0,0 +1 @@
# Install hook code here

@ -0,0 +1,108 @@
require 'active_support'
# Find potential mass assignment problems.
# The method is to scan the controllers for likely mass assignment,
# and then find the corresponding models that *don't* have
# attr_accessible defined. Any time that happens, it's a potential problem.
class String
@@cache = {}
# A regex to match likely cases of mass assignment
# Examples of matching strings:
# "Foo.new( { :bar => 'baz' } )"
# "Foo.update_attributes!(params[:foo])"
MASS_ASSIGNMENT = /(\w+)\.(new|create|update_attributes|build)!*\(/
# Return the strings that represent potential mass assignment problems.
# The MASS_ASSIGNMENT regex returns, e.g., ['Post', 'new'] because of
# the grouping methods; we want the first of the two for each match.
# For example, the call to scan might return
# [['Post', 'new'], ['User', 'create']]
# We then select the first element of each subarray, returning
# ['Post', 'User']
# Finally, we call classify to turn the string into a class.
def mass_assignment_models
scan(MASS_ASSIGNMENT).map { |problem| problem.first.classify }
end
# Return true if the string has potential mass assignment code.
def mass_assignment?
self =~ MASS_ASSIGNMENT
end
# Return true if the model defines attr_accessible.
# Note that 'attr_accessible' must be preceded by nothing other than
# whitespace; this catches cases where attr_accessible is commented out.
def attr_accessible?
model = "#{Rails.root}/app/models/#{self.underscore}.rb"
if File.exist?(model)
return @@cache[model] unless @@cache[model].nil?
@@cache[model] = File.open(model).read =~ /^\s*attr_accessible/
else
# If the model file doesn't exist, ignore it by returning true.
# This way, problem? is false and the item won't be flagged.
true
end
end
# Return true if a model does not define attr_accessible.
def problem?
!attr_accessible?
end
# Return true if a line has a problem model (no attr_accessible).
def problem_model?
problem = mass_assignment_models.find { |model| model.problem? }
!problem.nil?
end
# Return true if a controller string has a (likely) mass assignment problem.
# This is true if at least one of the controller's lines
# (1) Has a likely mass assignment
# (2) The corresponding model doesn't define attr_accessible
def mass_assignment_problem?
c = File.open(self)
problem = c.find { |line| line.mass_assignment? }
!problem.nil?
end
end
module MassAssignment
def self.print_mass_assignment_problems(controller)
lines = File.open(controller)
lines.each_with_index do |line, number|
if line.mass_assignment?
puts " #{number + 1} #{line}"
end
end
end
# Find and output mass assignment problems.
# Exit with non-zero status on error for use in pre-commit hooks.
# E.g., put 'rake find_mass_assignment' at the end of .git/hooks/pre-commit
# and then run
# $ chmod +x git/hooks/pre-commit
def self.find
exit_status = 0
# all core controllers
controllers = Dir.glob("#{Rails.root}/app/controllers/**/*controller*.rb")
# all plugin controllers and controller_patches
controllers += Dir.glob("#{Rails.root}/../plugins/**/*controller*.rb")
# minus all controller specs
controllers -= Dir.glob("#{Rails.root}/../plugins/**/*_spec.rb")
controllers.each do |controller|
if controller.mass_assignment_problem?
puts "\n#{controller}"
print_mass_assignment_problems(controller)
exit_status = 1
end
end
ensure
Process.exit exit_status
end
end

@ -0,0 +1,5 @@
desc "Find potential mass assignment vulnerabilities"
task :find_mass_assignment do
require File.join(File.dirname(__FILE__), "../find_mass_assignment.rb")
MassAssignment.find
end

@ -0,0 +1,76 @@
class ActiveRecord::Base
# Build and create records unsafely, bypassing attr_accessible.
# These methods are especially useful in tests and in the console.
# Inspired in part by http://pastie.textmate.org/104042
class << self
# Make a new record unsafely.
# This replaces new/build. For example,
# User.unsafe_new(:admin => true)
# works even if 'admin' isn't attr_accessible.
def unsafe_new(attrs = {})
record = new
record.unsafe_attributes = attrs
record
end
# Allow an unsafe build.
# For example,
# @blog.posts.unsafe_build(:published => true)
# works even if 'published' isn't attr_accessible.
alias_method :unsafe_build, :unsafe_new
# Create a record unsafely.
# For example,
# User.unsafe_create(:admin => true)
# works even if 'admin' isn't attr_accessible.
def unsafe_create(attrs)
record = unsafe_build(attrs)
record.save
record
end
# Same as unsafe_create, but raises an exception on error
# The analogy to create/create! is exact.
def unsafe_create!(attrs)
record = unsafe_build(attrs)
record.save!
record
end
end
# Update attributes unsafely.
# For example,
# @user.unsafe_update_attributes(:admin => true)
# works even if 'admin' isn't attr_accessible.
def unsafe_update_attributes(attrs)
self.unsafe_attributes = attrs
save
end
# Same as unsafe_update_attributes, but raises an exception on error
def unsafe_update_attributes!(attrs)
self.unsafe_attributes = attrs
save!
end
# Set attributes unsafely, bypassing attr_accessible.
def unsafe_attributes=(attrs)
raise attr_accessible_error unless attr_accessible_defined?
attrs.each do |k, v|
send("#{k}=", v)
end
end
private
def attr_accessible_defined?
!self.class.accessible_attributes.nil?
end
def attr_accessible_error
"#{self.class.name} is not protected by attr_accessible"
end
end

@ -0,0 +1 @@
# Uninstall hook code here
Loading…
Cancel
Save