accessible_for 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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