accessible_for 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,101 @@
1
+ # AccessibleFor: key-based hash sanitizer for Ruby
2
+
3
+ This is a simple mass-assignment security module loosely based on
4
+ [ActiveModel::MassAssignmentSecurity][1]. It attempts to steal the good ideas
5
+ and some of the API while being compatible with Rails 2.3-based applications.
6
+
7
+ Only attr_accessible (or its equivalent, keep reading) is implemented, because
8
+ attr_protected is just a bad ActiveRecord API that hung around for some reason,
9
+ and we don't want it stinking up the place.
10
+
11
+ There are actually two available APIs, the ActiveModel-workalike and a new one
12
+ called accessible_for. They provide identical functionality.
13
+
14
+ # Usage
15
+
16
+ ## ActiveModel-workalike API
17
+
18
+ class TacoShop < Controller
19
+ include MassAssignmentBackport
20
+
21
+ # when no role is specified, :default is used
22
+ attr_accessible :rating
23
+
24
+ # you can specify multiple roles
25
+ attr_accessible :filling, :topping, :as => [:default, :manager]
26
+
27
+ # and add to existing roles
28
+ attr_accessible :price, :as => :manager
29
+
30
+ def update
31
+ Taco.find(params[:id]).update_attributes!(taco_params)
32
+ end
33
+
34
+ protected
35
+
36
+ def taco_params
37
+ # use sanitize_for_mass_assignment to build a safe hash given a role.
38
+ # when nothing/nil is passed for the role, :default is used
39
+ sanitize_for_mass_assignment params[:taco], current_user.manager? ? :manager : nil
40
+ end
41
+ end
42
+
43
+ ## accessible_for API
44
+
45
+ class TacoShop < Controller
46
+ include AccessibleFor
47
+
48
+ # there are no implicit roles and you can declare only one group at a time
49
+ accessible_for :default => [ :filling, :topping, :rating ]
50
+ accessible_for :manager => [ :filling, :topping, :price ]
51
+
52
+ def update
53
+ Taco.find(params[:id]).update_attributes!(taco_params)
54
+ end
55
+
56
+ protected
57
+
58
+ def taco_params
59
+ # use sanitize_for(role, params) to build a safe hash
60
+ # again, there is no implicit role
61
+ if current_user.manager?
62
+ sanitize_for :manager, params[:taco]
63
+ else
64
+ sanitize_for :default, params[:taco]
65
+ end
66
+ end
67
+ end
68
+
69
+ # Rationale
70
+
71
+ There are two things I've never liked about ActiveRecord's attr_* API:
72
+
73
+ It's model-level when the resources I am trying to protect are controller-level.
74
+ This actually gets in our way when we're just trying to test/manipulate our own
75
+ models outside of a controller context, making it harder to work with
76
+ our own data for no good reason. I feel this phenomenon could have the effect of
77
+ discouraging developers from using it.
78
+
79
+ Another problem with ActiveRecord is that it provides attr_protected.
80
+ Blacklisting instead of whitelisting is just a bad idea, and I see no reason
81
+ to allow/support it when security is the primary goal.
82
+
83
+ So once we address those two things we have something that looks a bit like
84
+ ActiveModel's implementation minus attr_protected, which is the purpose of the
85
+ ActiveModel-workalike API. However there are problems with this API as well:
86
+
87
+ The role is optional, leading to lack of clarity. Sometimes you need to
88
+ specify :default, sometimes it's implicit. I think an API designed for
89
+ hardening should be more transparent.
90
+
91
+ The way the role is specified is also suboptimal. It's at the end of the
92
+ declaration so you have to hunt for it. It uses the key :as implying a
93
+ user-based access role, but the fact is this value is really just a scope and
94
+ can mean anything.
95
+
96
+ # Author
97
+
98
+ Zack Hobson (zack@zackhobson.com)
99
+
100
+ [1]: http://api.rubyonrails.org/classes/ActiveModel/MassAssignmentSecurity.html
101
+
@@ -0,0 +1,14 @@
1
+ desc 'run tests'
2
+ task :test do
3
+ # This should work in my opinion, but it only runs the last loaded test.
4
+ #sh 'ruby -Ilib -rminitest/autorun test/*_test.rb'
5
+ require 'minitest/unit'
6
+ $:.unshift("./lib")
7
+ Dir['test/*_test.rb'].each {|t| load t}
8
+ MiniTest::Unit.autorun
9
+ end
10
+
11
+ desc 'build the gem'
12
+ task :gem do
13
+ sh 'gem build accessible_for.gemspec'
14
+ end
@@ -0,0 +1,16 @@
1
+ $:.unshift File.expand_path("./lib")
2
+ require 'accessible_for'
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "accessible_for"
6
+ s.version = AccessibleFor::VERSION
7
+ s.summary = 'Simple API for sanitizing hashes by input key'
8
+ s.description = <<-EOD
9
+ This is a simple mass-assignment security module loosely based on
10
+ ActiveModel::MassAssignmentSecurity. It attempts to steal the good ideas
11
+ and some of the API while being compatible with Rails 2.3-based applications.
12
+ EOD
13
+ s.authors = ['Zack Hobson']
14
+ s.files = `git ls-files`.split("\n")
15
+ s.test_files = `git ls-files -- test/*`.split("\n")
16
+ end
@@ -0,0 +1,24 @@
1
+ require 'mass_assignment_backport'
2
+
3
+ module AccessibleFor
4
+ VERSION = "0.1.0"
5
+
6
+ def self.included(mod)
7
+ mod.send :include, MassAssignmentBackport
8
+ mod.extend ClassMethods
9
+ end
10
+
11
+ module ClassMethods
12
+ def accessible_for params
13
+ params.each do |role, attrs|
14
+ attr_accessible *([attrs].flatten.push(:as => role))
15
+ end
16
+ end
17
+ end
18
+
19
+ def sanitize_for role, values
20
+ sanitize_for_mass_assignment values, role
21
+ end
22
+ end
23
+
24
+
@@ -0,0 +1,36 @@
1
+ module MassAssignmentBackport
2
+ VERSION = "0.1.0"
3
+
4
+ def self.included(mod)
5
+ mod.extend ClassMethods
6
+ end
7
+
8
+ module ClassMethods
9
+ attr_accessor :_accessible_attributes
10
+
11
+ def attr_accessible *args
12
+ options = args.last.kind_of?(Hash) ? args.pop : {}
13
+ role = options[:as] || :default
14
+ self._accessible_attributes ||= {}
15
+ [role].flatten.each do |name|
16
+ self._accessible_attributes[name] = accessible_attributes(name) + args
17
+ end
18
+ end
19
+
20
+ def accessible_attributes role=:default
21
+ _accessible_attributes[role] || []
22
+ end
23
+ end
24
+
25
+ def sanitize_for_mass_assignment values, role=:default
26
+ {}.tap do |result|
27
+ values.each do |k, v|
28
+ if self.class._accessible_attributes[role].include?(k.to_sym)
29
+ yield k, v if block_given?
30
+ result[k] = v
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+
@@ -0,0 +1,16 @@
1
+ $:.unshift File.expand_path("./lib")
2
+ require 'mass_assignment_backport'
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "mass_assignment_backport"
6
+ s.version = MassAssignmentBackport::VERSION
7
+ s.summary = 'Simple API for sanitizing hashes by input key'
8
+ s.description = <<-EOD
9
+ This is a simple mass-assignment security module loosely based on
10
+ ActiveModel::MassAssignmentSecurity. It attempts to steal the good ideas
11
+ and some of the API while being compatible with Rails 2.3-based applications.
12
+ EOD
13
+ s.authors = ['Zack Hobson']
14
+ s.files = `git ls-files`.split("\n")
15
+ s.test_files = `git ls-files -- test/*`.split("\n")
16
+ end
@@ -0,0 +1,49 @@
1
+ require 'accessible_for'
2
+
3
+ class AccessibleForTest < MiniTest::Unit::TestCase
4
+ include AccessibleFor
5
+ accessible_for :default => :topping
6
+ accessible_for :manager => [:price, :topping]
7
+
8
+ def test_accessible_default
9
+ default = sanitize_for :default, :topping => 'salsa', :price => 123, :extra => 'foo'
10
+ assert default.has_key?(:topping), "default gets accessible key"
11
+ assert !default.has_key?(:price), "default does not get inaccessible key"
12
+ assert !default.has_key?(:extra), "default does not get extra key"
13
+ end
14
+
15
+ def test_accessible_role
16
+ manager = sanitize_for :manager, :topping => 'salsa', :price => 123, :extra => 'foo'
17
+ assert manager.has_key?(:topping), "role gets accessible key"
18
+ assert manager.has_key?(:price), "role gets second accessible key"
19
+ assert !manager.has_key?(:extra), "role does not get extra key"
20
+ end
21
+
22
+ class SubTest
23
+ include AccessibleFor
24
+ accessible_for :default => :toasted
25
+ accessible_for :manager => :free
26
+ end
27
+ class SubclassTest < SubTest
28
+ accessible_for :default => :rating
29
+ accessible_for :manager => :toasted
30
+ end
31
+
32
+ def test_ignores_parent_settings_default
33
+ sub = SubclassTest.new
34
+ default = sub.sanitize_for :default, :toasted => true, :rating => 'good'
35
+ assert !default.has_key?(:toasted), "inherited default does not get parent accessible key"
36
+ assert default.has_key?(:rating), "inherited default gets accessible key"
37
+ assert !default.has_key?(:free), "inherited default does not get inaccessible key"
38
+ assert !default.has_key?(:extra), "inherited default does not get extra key"
39
+ end
40
+
41
+ def test_ignores_parent_settings_role
42
+ sub = SubclassTest.new
43
+ manager = sub.sanitize_for :manager, :toasted => true, :free => true, :rating => 'good'
44
+ assert !manager.has_key?(:free), "inherited role does not get parent accessible key"
45
+ assert manager.has_key?(:toasted), "inherited role gets accessible key"
46
+ assert !manager.has_key?(:rating), "inherited role does not get inaccessible key"
47
+ assert !manager.has_key?(:extra), "inherited role does not get extra key"
48
+ end
49
+ end
@@ -0,0 +1,49 @@
1
+ require 'mass_assignment_backport'
2
+
3
+ class MassAssignmentTest < MiniTest::Unit::TestCase
4
+ include MassAssignmentBackport
5
+ attr_accessible :topping, :as => [:default, :manager]
6
+ attr_accessible :price, :as => :manager
7
+
8
+ def test_accessible_default
9
+ default = sanitize_for_mass_assignment :topping => 'salsa', :price => 123, :extra => 'foo'
10
+ assert default.has_key?(:topping), "default gets accessible key"
11
+ assert !default.has_key?(:price), "default does not get inaccessible key"
12
+ assert !default.has_key?(:extra), "default does not get extra key"
13
+ end
14
+
15
+ def test_accessible_role
16
+ manager = sanitize_for_mass_assignment({ :topping => 'salsa', :price => 123, :extra => 'foo' }, :manager)
17
+ assert manager.has_key?(:topping), "role gets accessible key"
18
+ assert manager.has_key?(:price), "role gets second accessible key"
19
+ assert !manager.has_key?(:extra), "role does not get extra key"
20
+ end
21
+
22
+ class SubTest
23
+ include MassAssignmentBackport
24
+ attr_accessible :toasted
25
+ attr_accessible :free, :as => :manager
26
+ end
27
+ class SubclassTest < SubTest
28
+ attr_accessible :rating
29
+ attr_accessible :toasted, :as => :manager
30
+ end
31
+
32
+ def test_ignores_parent_settings_default
33
+ sub = SubclassTest.new
34
+ default = sub.sanitize_for_mass_assignment :toasted => true, :rating => 'good'
35
+ assert !default.has_key?(:toasted), "inherited default does not get parent accessible key"
36
+ assert default.has_key?(:rating), "inherited default gets accessible key"
37
+ assert !default.has_key?(:free), "inherited default does not get inaccessible key"
38
+ assert !default.has_key?(:extra), "inherited default does not get extra key"
39
+ end
40
+
41
+ def test_ignores_parent_settings_role
42
+ sub = SubclassTest.new
43
+ manager = sub.sanitize_for_mass_assignment({ :toasted => true, :free => true, :rating => 'good' }, :manager)
44
+ assert !manager.has_key?(:free), "inherited role does not get parent accessible key"
45
+ assert manager.has_key?(:toasted), "inherited role gets accessible key"
46
+ assert !manager.has_key?(:rating), "inherited role does not get inaccessible key"
47
+ assert !manager.has_key?(:extra), "inherited role does not get extra key"
48
+ end
49
+ end
metadata ADDED
@@ -0,0 +1,56 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: accessible_for
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Zack Hobson
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-03-06 00:00:00.000000000Z
13
+ dependencies: []
14
+ description: ! " This is a simple mass-assignment security module loosely based
15
+ on\n ActiveModel::MassAssignmentSecurity. It attempts to steal the good ideas\n
16
+ \ and some of the API while being compatible with Rails 2.3-based applications.\n"
17
+ email:
18
+ executables: []
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - README.markdown
23
+ - Rakefile
24
+ - accessible_for.gemspec
25
+ - lib/accessible_for.rb
26
+ - lib/mass_assignment_backport.rb
27
+ - mass_assignment_backport.gemspec
28
+ - test/accessible_for_test.rb
29
+ - test/mass_assignment_test.rb
30
+ homepage:
31
+ licenses: []
32
+ post_install_message:
33
+ rdoc_options: []
34
+ require_paths:
35
+ - lib
36
+ required_ruby_version: !ruby/object:Gem::Requirement
37
+ none: false
38
+ requirements:
39
+ - - ! '>='
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ required_rubygems_version: !ruby/object:Gem::Requirement
43
+ none: false
44
+ requirements:
45
+ - - ! '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ requirements: []
49
+ rubyforge_project:
50
+ rubygems_version: 1.8.10
51
+ signing_key:
52
+ specification_version: 3
53
+ summary: Simple API for sanitizing hashes by input key
54
+ test_files:
55
+ - test/accessible_for_test.rb
56
+ - test/mass_assignment_test.rb