├── .gitignore ├── LICENSE ├── README.rdoc ├── Rakefile ├── lib ├── tasks │ └── spec.rb ├── trusted_params.rb └── trusted_params │ ├── active_record_additions.rb │ └── hash_additions.rb ├── rails └── init.rb ├── spec ├── spec_helper.rb └── trusted_params │ ├── active_record_additions_spec.rb │ └── hash_additions_spec.rb └── trusted_params.gemspec /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 Ryan Bates 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | = Trusted Params 2 | 3 | Rails plugin which adds a convenient way to override attr_accessible protection. 4 | 5 | If you are unfamiliar with the dangers of mass assignment please check these links 6 | 7 | * http://railspikes.com/2008/9/22/is-your-rails-application-safe-from-mass-assignment 8 | * http://railscasts.com/episodes/26-hackers-love-mass-assignment 9 | 10 | 11 | == Install 12 | 13 | You can install this as a plugin into your Rails app. 14 | 15 | script/plugin install git://github.com/ryanb/trusted-params.git 16 | 17 | 18 | == Features 19 | 20 | This plugin does several things. 21 | 22 | * Adds "trust" method on hash to bypass attribute protection 23 | * Disables attr_protected because you should use attr_accessible. 24 | * Requires attr_accessible be specified in every model 25 | * Adds :all as option to attr_accessible to allow all attributes to be mass-assignable 26 | * Raises an exception when assigning a protected attribute (instead of just a log message) 27 | 28 | 29 | == Usage 30 | 31 | When using this plugin, you must define attr_accessible in every model to allow mass assignment. You can use :all to mark all attributes as accessible. 32 | 33 | class Comment < ActiveRecord::Base 34 | attr_accessible :all 35 | end 36 | 37 | However, only do this if you want all attributes accessible to the public. Many times you will want to limit what the general public can set. 38 | 39 | class Comment < ActiveRecord::Base 40 | attr_accessible :author_name, :email, :content 41 | end 42 | 43 | Administrators should be able to bypass the protected attributes and set anything. This can be done with the "trust" method. 44 | 45 | def create 46 | params[:comment].trust if admin? 47 | @comment = Comment.new(params[:comment]) 48 | # ... 49 | end 50 | 51 | You can mark certain attributes as trusted for different roles 52 | 53 | params[:comment].trust(:spam, :important) if moderator? 54 | 55 | Then only those attributes will be allowed to bypass mass assignment. 56 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'rake' 3 | 4 | Dir["#{File.dirname(__FILE__)}/lib/tasks/*.rb"].sort.each { |ext| load ext } 5 | -------------------------------------------------------------------------------- /lib/tasks/spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec/rake/spectask' 2 | 3 | spec_files = Rake::FileList["spec/**/*_spec.rb"] 4 | 5 | desc "Run specs" 6 | Spec::Rake::SpecTask.new do |t| 7 | t.spec_files = spec_files 8 | t.spec_opts = ["-c"] 9 | end 10 | -------------------------------------------------------------------------------- /lib/trusted_params.rb: -------------------------------------------------------------------------------- 1 | $:.unshift(File.dirname(__FILE__)) 2 | require 'trusted_params/hash_additions' 3 | require 'trusted_params/active_record_additions' 4 | -------------------------------------------------------------------------------- /lib/trusted_params/active_record_additions.rb: -------------------------------------------------------------------------------- 1 | module TrustedParams 2 | module ActiveRecordAdditions 3 | def self.included(base) 4 | base.extend(ClassMethods) 5 | base.alias_method_chain :remove_attributes_protected_from_mass_assignment, :trusted_params 6 | end 7 | 8 | def remove_attributes_protected_from_mass_assignment_with_trusted_params(attributes) 9 | unless self.class.accessible_attributes && self.class.accessible_attributes.include?("all") 10 | attributes.each do |key, value| 11 | unless (self.class.accessible_attributes && self.class.accessible_attributes.include?(key.to_s)) || attributes.trusted?(key) 12 | raise ActiveRecord::UnavailableAttributeAssignmentError, "attribute \"#{key}\" is protected from mass assignment, use attr_accessible" 13 | end 14 | end 15 | end 16 | attributes 17 | end 18 | 19 | module ClassMethods 20 | def self.extended(base) 21 | base.metaclass.alias_method_chain :attr_protected, :disabled 22 | end 23 | 24 | def attr_protected_with_disabled(*args) 25 | raise "attr_protected has been disabled by trusted-params plugin, use attr_accessible" 26 | end 27 | end 28 | end 29 | end 30 | 31 | module ActiveRecord 32 | Base.class_eval do 33 | include TrustedParams::ActiveRecordAdditions 34 | end 35 | 36 | class UnavailableAttributeAssignmentError < ActiveRecordError 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/trusted_params/hash_additions.rb: -------------------------------------------------------------------------------- 1 | module TrustedParams 2 | module HashAdditions 3 | def trust(*attribute_names) 4 | if attribute_names.empty? 5 | @trusted_attributes = :all 6 | each_key { |k| self[k].trust if self[k].kind_of? Hash } 7 | else 8 | @trusted_attributes = attribute_names.map(&:to_sym) 9 | attribute_names.each do |attribute_name| 10 | self[attribute_name].trust if self[attribute_name].kind_of? Hash 11 | end 12 | end 13 | self 14 | end 15 | 16 | def trusted?(attribute_name) 17 | if defined?(@trusted_attributes) 18 | @trusted_attributes == :all || @trusted_attributes.include?(attribute_name.to_sym) 19 | end 20 | end 21 | end 22 | end 23 | 24 | # I would prefer not setting this in all hashes, but it is the easiest solution at the moment. 25 | class Hash 26 | include TrustedParams::HashAdditions 27 | end 28 | 29 | # override "dup" method because it doesn't carry over trusted attributes 30 | # I wish there was a better way to do this... 31 | class HashWithIndifferentAccess 32 | def dup_with_trusted_attributes 33 | hash = dup_without_trusted_attributes 34 | hash.instance_variable_set("@trusted_attributes", @trusted_attributes) if defined?(@trusted_attributes) 35 | hash 36 | end 37 | alias_method_chain :dup, :trusted_attributes 38 | end -------------------------------------------------------------------------------- /rails/init.rb: -------------------------------------------------------------------------------- 1 | require 'trusted_params' 2 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'spec' 3 | require 'active_support' 4 | require 'active_record' 5 | require File.dirname(__FILE__) + '/../lib/trusted_params.rb' 6 | 7 | Spec::Runner.configure do |config| 8 | config.mock_with :rr 9 | end 10 | 11 | class MockedModel < ActiveRecord::Base 12 | class_inheritable_hash :paginate_options 13 | 14 | def self.paginate(options) 15 | self.paginate_options = options 16 | end 17 | 18 | def self.add_column(name) 19 | returning ActiveRecord::ConnectionAdapters::Column.new(name, nil) do |column| 20 | @columns ||= [] 21 | @columns << column 22 | end 23 | end 24 | 25 | def self.reset_columns 26 | write_inheritable_attribute(:attr_accessible, []) 27 | @columns = [] 28 | end 29 | 30 | def self.columns 31 | @columns || [] 32 | end 33 | 34 | def self.columns_hash 35 | columns.index_by{|c| c.name.to_s} 36 | end 37 | 38 | def logger 39 | Logger.new("/dev/null") 40 | end 41 | 42 | def self.inspect 43 | "Model Mock" 44 | end 45 | 46 | def self.table_name 47 | 'mocked_models' 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /spec/trusted_params/active_record_additions_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../spec_helper' 2 | 3 | describe MockedModel do 4 | before(:each) do 5 | MockedModel.reset_columns 6 | MockedModel.add_column(:name) 7 | MockedModel.add_column(:content) 8 | end 9 | 10 | it "should not allow one to set attr_protected" do 11 | lambda { MockedModel.attr_protected(:foo) }.should raise_error 12 | end 13 | 14 | it "should not be able to mass assign attributes by default" do 15 | lambda { MockedModel.new(:name => "foo") }.should raise_error(ActiveRecord::UnavailableAttributeAssignmentError) 16 | end 17 | 18 | it "should be able to mass assign any attribute with :all" do 19 | MockedModel.attr_accessible :all 20 | m = MockedModel.new(:name => "foo") 21 | m.name.should == "foo" 22 | end 23 | 24 | it "should be able to mass assign specific attributes" do 25 | MockedModel.attr_accessible :name 26 | m = MockedModel.new(:name => "foo") 27 | m.name.should == "foo" 28 | lambda { MockedModel.new(:content => "foo") }.should raise_error(ActiveRecord::UnavailableAttributeAssignmentError) 29 | end 30 | 31 | it "should be able to mass assign with trusted hash" do 32 | m = MockedModel.new({:name => "foo"}.trust) 33 | m.name.should == "foo" 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /spec/trusted_params/hash_additions_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../spec_helper' 2 | 3 | describe Hash do 4 | before(:each) do 5 | @hash = { :foo => "bar" } 6 | end 7 | 8 | it "should not consider attributes trusted by default" do 9 | @hash.should_not be_trusted(:foo) 10 | end 11 | 12 | it "should trust attributes marked as trusted" do 13 | @hash.trust(:foo) 14 | @hash.should be_trusted(:foo) 15 | end 16 | 17 | it "should trust all attributes when passing no arguments" do 18 | @hash.trust 19 | @hash.should be_trusted(:foo) 20 | @hash.should be_trusted(:anything) 21 | end 22 | 23 | it "should trust multiple attributes in one call" do 24 | @hash.trust(:foo, :hello) 25 | @hash.should be_trusted(:foo) 26 | @hash.should be_trusted(:hello) 27 | @hash.should_not be_trusted(:anything) 28 | end 29 | 30 | it "should inherit trust in nested hashes" do 31 | @hash[:child] = { :boing => "bong" } 32 | @hash.trust(:child) 33 | @hash.should be_trusted(:child) 34 | @hash[:child].should be_trusted(:boing) 35 | end 36 | 37 | it "should return hash from trust method" do 38 | @hash.trust.should == @hash 39 | end 40 | 41 | it "should be indifferent between string and symbol" do 42 | @hash.trust("foo") 43 | @hash.should be_trusted(:foo) 44 | @hash.trust(:bar) 45 | @hash.should be_trusted("bar") 46 | end 47 | 48 | it "should persist trusted when duplicating HashWithIndifferentAccess" do 49 | h1 = HashWithIndifferentAccess.new(:foo => "bar") 50 | h1.trust 51 | h2 = h1.dup 52 | h2.should be_trusted(:foo) 53 | end 54 | 55 | it "should inherit trust in all nested hashes" do 56 | @hash[:child] = { :boing => "bong" } 57 | @hash.trust 58 | @hash.should be_trusted(:child) 59 | @hash[:child].should be_trusted(:boing) 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /trusted_params.gemspec: -------------------------------------------------------------------------------- 1 | Gem::Specification.new do |s| 2 | s.name = "trusted_params" 3 | s.version = "1.0.0" 4 | s.author = "Ryan Bates" 5 | s.email = "ryan@railscasts.com" 6 | s.homepage = "http://github.com/ryanb/trusted-params" 7 | s.summary = "Rails plugin which adds a convenient way to override attr_accessible protection." 8 | 9 | s.files = Dir["{lib,spec,rails}/**/*", "[A-Z]*"] 10 | s.require_path = "lib" 11 | 12 | s.rubyforge_project = s.name 13 | s.required_rubygems_version = ">= 1.3.4" 14 | end --------------------------------------------------------------------------------