shields_up 0.17.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 391eded2ca4d95f1777b8a227f13905e7f01b975
4
+ data.tar.gz: 05c2a17280fb9c761b3743e688abcc3c9cbe97ce
5
+ SHA512:
6
+ metadata.gz: a13deb71b95918e3c1b7d297ec5f72a9d8b29a71d9a58c8f3259d09d9d8373aa2c6f7881b9f7c0c2c0b0df01adcd0f6f052383454ec527c2ac061faf045830a1
7
+ data.tar.gz: 4d99e218185e1db2828543e5c9ce39f469c593a9cc0a16c98516e1625ffe7d903acb7bf895c2b81ac830c85b49137aaf19d5562bf0bb785de84549da3a8732fd
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'mocha'
6
+ gem 'activemodel'
7
+ gem 'rails'
8
+ gem 'minitest', '~> 4.0'
9
+ gem "codeclimate-test-reporter", :require => nil
data/Gemfile.lock ADDED
@@ -0,0 +1,108 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ shields_up (0.16.0)
5
+ activesupport (~> 3.2)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ actionmailer (3.2.19)
11
+ actionpack (= 3.2.19)
12
+ mail (~> 2.5.4)
13
+ actionpack (3.2.19)
14
+ activemodel (= 3.2.19)
15
+ activesupport (= 3.2.19)
16
+ builder (~> 3.0.0)
17
+ erubis (~> 2.7.0)
18
+ journey (~> 1.0.4)
19
+ rack (~> 1.4.5)
20
+ rack-cache (~> 1.2)
21
+ rack-test (~> 0.6.1)
22
+ sprockets (~> 2.2.1)
23
+ activemodel (3.2.19)
24
+ activesupport (= 3.2.19)
25
+ builder (~> 3.0.0)
26
+ activerecord (3.2.19)
27
+ activemodel (= 3.2.19)
28
+ activesupport (= 3.2.19)
29
+ arel (~> 3.0.2)
30
+ tzinfo (~> 0.3.29)
31
+ activeresource (3.2.19)
32
+ activemodel (= 3.2.19)
33
+ activesupport (= 3.2.19)
34
+ activesupport (3.2.19)
35
+ i18n (~> 0.6, >= 0.6.4)
36
+ multi_json (~> 1.0)
37
+ arel (3.0.3)
38
+ builder (3.0.4)
39
+ codeclimate-test-reporter (0.4.1)
40
+ simplecov (>= 0.7.1, < 1.0.0)
41
+ docile (1.1.5)
42
+ erubis (2.7.0)
43
+ hike (1.2.3)
44
+ i18n (0.6.9)
45
+ journey (1.0.4)
46
+ json (1.8.1)
47
+ mail (2.5.4)
48
+ mime-types (~> 1.16)
49
+ treetop (~> 1.4.8)
50
+ metaclass (0.0.4)
51
+ mime-types (1.25.1)
52
+ minitest (4.7.5)
53
+ mocha (1.1.0)
54
+ metaclass (~> 0.0.1)
55
+ multi_json (1.10.1)
56
+ polyglot (0.3.5)
57
+ rack (1.4.5)
58
+ rack-cache (1.2)
59
+ rack (>= 0.4)
60
+ rack-ssl (1.3.4)
61
+ rack
62
+ rack-test (0.6.2)
63
+ rack (>= 1.0)
64
+ rails (3.2.19)
65
+ actionmailer (= 3.2.19)
66
+ actionpack (= 3.2.19)
67
+ activerecord (= 3.2.19)
68
+ activeresource (= 3.2.19)
69
+ activesupport (= 3.2.19)
70
+ bundler (~> 1.0)
71
+ railties (= 3.2.19)
72
+ railties (3.2.19)
73
+ actionpack (= 3.2.19)
74
+ activesupport (= 3.2.19)
75
+ rack-ssl (~> 1.3.2)
76
+ rake (>= 0.8.7)
77
+ rdoc (~> 3.4)
78
+ thor (>= 0.14.6, < 2.0)
79
+ rake (10.3.2)
80
+ rdoc (3.12.2)
81
+ json (~> 1.4)
82
+ simplecov (0.9.1)
83
+ docile (~> 1.1.0)
84
+ multi_json (~> 1.0)
85
+ simplecov-html (~> 0.8.0)
86
+ simplecov-html (0.8.0)
87
+ sprockets (2.2.2)
88
+ hike (~> 1.2)
89
+ multi_json (~> 1.0)
90
+ rack (~> 1.0)
91
+ tilt (~> 1.1, != 1.3.0)
92
+ thor (0.19.1)
93
+ tilt (1.4.1)
94
+ treetop (1.4.15)
95
+ polyglot
96
+ polyglot (>= 0.3.1)
97
+ tzinfo (0.3.41)
98
+
99
+ PLATFORMS
100
+ ruby
101
+
102
+ DEPENDENCIES
103
+ activemodel
104
+ codeclimate-test-reporter
105
+ minitest (~> 4.0)
106
+ mocha
107
+ rails
108
+ shields_up!
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2014 Appfolio Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,107 @@
1
+ [![Build Status](https://travis-ci.org/appfolio/shields_up.png)](https://travis-ci.org/appfolio/shields_up)
2
+ [![Code Climate](https://codeclimate.com/github/appfolio/shields_up/badges/gpa.svg)](https://codeclimate.com/github/appfolio/shields_up)
3
+ [![Test Coverage](https://codeclimate.com/github/appfolio/shields_up/badges/coverage.svg)](https://codeclimate.com/github/appfolio/shields_up)
4
+
5
+ #Shields Up
6
+ This gem provides an alternative implementation of strong_parameters.
7
+ ##usage (the grammar for permit statements is the same as strong_parameters)
8
+ - **Read this first**: [Strong Parameter Usage](http://edgeguides.rubyonrails.org/action_controller_overview.html#strong-parameters)
9
+
10
+
11
+ ##Differences to strong parameter
12
+ params.symbolize_keys (or similar functions) will disable strong_parameters
13
+ protection silently. With ShieldsUp this can not happen.
14
+
15
+ ## Enable ShieldsUp<br>
16
+
17
+ in Gemfile<br>
18
+ ```
19
+ gem 'shields_up'
20
+ ```
21
+ in controllers<br>
22
+ ```
23
+ include ShieldsUp
24
+ ```
25
+
26
+ - ShieldsUp::Parameter type only allows three operations: [], permit and require.
27
+ - You can use symbols or strings to access variables.<br>
28
+
29
+ ##Example:<br>
30
+ ```
31
+ params[:company]
32
+ ```
33
+ not:
34
+ ```
35
+ params["company"]
36
+ ```
37
+ or
38
+ ```
39
+ params.fetch(:company)
40
+ params.fetch("company")
41
+ ```
42
+
43
+ ##A more complicated example:<br>
44
+ ```
45
+ params.permit(:company => [:address, :enabled])
46
+ ```
47
+ not:
48
+ ```
49
+ params.permit("company" => [:address, "enabled"])
50
+ ```
51
+
52
+ ## How to disable shields up.<br>
53
+ If you have a bunch of legacy code in a controller or you call into gems whose
54
+ parameter handling
55
+ you do not control (e.g. devise) you can disable shields_up at your own risk.
56
+ Similar to holding a lock around critical sections these blocks should generally be kept as short as possible.
57
+ <pre>
58
+ params.with_shields_down do
59
+ call_some_legacy_stuff_or_gem()
60
+ end
61
+ </pre>
62
+ <br>
63
+ - with_shields_down can not be nested. This means inside with_shields_down there should be no with_shields_down
64
+ - the places in an application disabling parameter protection are such explicit and can be accounted for during an audit
65
+ </b>
66
+
67
+ ##Common Pitfalls:
68
+ - *to update and destroy associated records, permit <i>:id,</i> and <i> :_destroy </i> when you use accepts_nested_attributes_for in combination with a has_many association. <br>
69
+
70
+ Example:
71
+ ```
72
+ # To whitelist the following data:
73
+ # {"applicant" => {"email_address" => "some@email.com",
74
+ # "contact_info_attributes" => { "1" => {"salutation" => "First Salutation"},
75
+ # "2" => {"salutation" => "Second Salutation"}}}}
76
+ params.permit(
77
+ {:applicant => [
78
+ :email_address,
79
+ {:contact_info_attributes => [:salutation, :id, :_destroy]},
80
+ ]}
81
+ )
82
+ ```
83
+ - *use hash inside permit statment** for non scaler type.<br>
84
+
85
+ Example:
86
+ ```
87
+ # To whitelist the following data:
88
+ # {"applicant" => {"email_address" => "some@email.com",
89
+ # "info" => { "first_name" => 'First', "last_name" => 'Last'}}}
90
+ ```
91
+ <pre>
92
+ params.permit(
93
+ {:applicant => [
94
+ :email_address,
95
+ <b>{</b>:info => [:first_name, :last_name]<b>}</b>
96
+ ]}
97
+ )
98
+ </pre>
99
+
100
+ ##Limitations
101
+ Similar to strong_parameters, this gem was designed with the most common use cases in mind. It is not meant as a silver bullet to handle all your whitelisting problems. However you can easily disable shields_up, and sanitize the parameter with your own code to adapt to your situation.<br>
102
+
103
+ In order to keep our grammar compatible to strong_parameters it is not possible
104
+ to express permission for array of arrays:
105
+ ```
106
+ {'company' => [['a','b'],['a','b']]}
107
+ ```
data/Rakefile ADDED
@@ -0,0 +1,23 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+
4
+ begin
5
+ Bundler.setup(:default, :development)
6
+ rescue Bundler::BundlerError => e
7
+ $stderr.puts e.message
8
+ $stderr.puts 'Run `bundle install` to install missing gems'
9
+ exit e.status_code
10
+ end
11
+
12
+ require 'rake'
13
+ require 'rake/testtask'
14
+
15
+ namespace :test do
16
+ Rake::TestTask.new(:all) do |test|
17
+ test.libs << 'lib' << 'test'
18
+ test.pattern = 'test/shields_up/**/*_test.rb'
19
+ test.verbose = true
20
+ end
21
+ end
22
+
23
+ task :default => 'test:all'
@@ -0,0 +1,4 @@
1
+ module ShieldsUp
2
+ class ParameterMissing < RuntimeError; end
3
+ class UnsupportedParameterType < RuntimeError; end
4
+ end
@@ -0,0 +1,158 @@
1
+ require 'active_support/core_ext/hash/indifferent_access'
2
+ module ShieldsUp
3
+ class Parameters
4
+ if defined?(ActionController) && defined?(ActionController::Parameters)
5
+ PARAM_TYPE = ActionController::Parameters
6
+ else
7
+ PARAM_TYPE = ActiveSupport::HashWithIndifferentAccess
8
+ end
9
+
10
+ PERMITTED_SCALAR_TYPES = [
11
+ String,
12
+ Symbol,
13
+ NilClass,
14
+ Numeric,
15
+ TrueClass,
16
+ FalseClass,
17
+ Date,
18
+ Time,
19
+ # DateTimes are Dates, we document the type but avoid the redundant check.
20
+ StringIO,
21
+ IO,
22
+ ]
23
+ if defined?(ActionDispatch) && defined?(ActionDispatch::Http) && defined?(ActionDispatch::Http::UploadedFile)
24
+ PERMITTED_SCALAR_TYPES << ActionDispatch::Http::UploadedFile
25
+ end
26
+ if defined?(Rack) && defined?(Rack::Test) && defined?(Rack::Test::UploadedFile)
27
+ PERMITTED_SCALAR_TYPES << Rack::Test::UploadedFile
28
+ end
29
+
30
+ def with_shields_down
31
+ saved = @controller.params
32
+ @controller.params = @original_params
33
+ yield
34
+ ensure
35
+ @controller.params = saved
36
+ end
37
+
38
+ def initialize(params, controller)
39
+ raise UnsupportedParameterType.new unless params.is_a? ActiveSupport::HashWithIndifferentAccess
40
+ @original_params = params
41
+ @controller = controller
42
+ @params = deep_dup_to_hash(params)
43
+ end
44
+
45
+ def to_s
46
+ @params.inspect
47
+ end
48
+
49
+ def permit(*permissions)
50
+ {}.tap do |permitted|
51
+ permissions.each do |permission|
52
+ permission, key = (permission.is_a?(Symbol) || permission.is_a?(String)) ? [permission, permission.to_s] : [permission.values.first, permission.keys.first.to_s]
53
+ if @params.has_key?(key)
54
+ if permission.is_a?(Symbol) || permission.is_a?(String)
55
+ result = permit_scalar(key)
56
+ permitted[key] = result if @params[key] == result
57
+ else
58
+ result = permit_nested(key, permission)
59
+ permitted[key] = result if result
60
+ end
61
+ end
62
+ end
63
+ end.symbolize_keys
64
+ end
65
+
66
+ def require(key)
67
+ self[key] or raise ParameterMissing.new("Required parameter #{key} does not exist in #{to_s}")
68
+ end
69
+
70
+ # def permit!
71
+ # deep_dup_to_hash(@params).symbolize_keys
72
+ # end
73
+
74
+ def [](key)
75
+ key = key.to_s
76
+ value = @params[key]
77
+ if value.is_a?(Hash)
78
+ self.class.new(@original_params[key], @controller)
79
+ elsif value.is_a?(Array)
80
+ [].tap do |array|
81
+ value.each_with_index do |element, i|
82
+ if permitted_scalar?(element)
83
+ array << element
84
+ elsif element.is_a? Hash
85
+ array << self.class.new(@original_params[key][i], @controller)
86
+ end
87
+ end
88
+ end
89
+ else
90
+ permit_scalar(key)
91
+ end
92
+ end
93
+
94
+ private
95
+
96
+ def permit_scalar(key)
97
+ permitted_scalar?(@params[key]) ? @params[key] : nil
98
+ end
99
+
100
+ def permit_simple_hash(name, permissions)
101
+ if @params[name].is_a? Hash
102
+ self.class.new(@original_params[name], @controller).permit(*permissions)
103
+ else
104
+ permit_scalar(name)
105
+ end
106
+ end
107
+
108
+ def permit_nested_attributes_for(name, permissions)
109
+ {}.tap do |result|
110
+ @params[name].each do |key, value|
111
+ result[key] = self.class.new(@original_params[name][key], @controller).permit(*permissions) if value.is_a? Hash
112
+ result[key] = value if permitted_scalar?(value)
113
+ end
114
+ end
115
+ end
116
+
117
+ def permit_array_of_hashes(name, permissions)
118
+ @params[name].zip(@original_params[name]).select{|el| el[0].is_a? Hash}.collect{|el| self.class.new(el[1], @controller).permit(*permissions)}
119
+ end
120
+
121
+ def permit_scalars(name)
122
+ @params[name].select { |element| permitted_scalar? element }
123
+ end
124
+
125
+ def integer_key?(k)
126
+ k =~ /\A-?\d+\z/
127
+ end
128
+
129
+ def permit_nested(nested_name, permissions_for_nested)
130
+ if permissions_for_nested == [] # Declaration {:comment_ids => []}.
131
+ @params[nested_name].is_a?(Array) ? permit_scalars(nested_name) : nil
132
+ elsif @params[nested_name].is_a? Array # Declaration {:user => :name} or {:user => [:name, :age, {:adress => ...}]}.
133
+ permit_array_of_hashes(nested_name, permissions_for_nested)
134
+ elsif @params[nested_name].is_a?(Hash) && @params[nested_name].keys.all? { |k| integer_key?(k) } #{ '1' => {'title' => 'First Chapter'}, '2' => {'title' => 'Second Chapter'}}
135
+ permit_nested_attributes_for(nested_name, permissions_for_nested)
136
+ else
137
+ permit_simple_hash(nested_name, permissions_for_nested)
138
+ end
139
+ end
140
+
141
+ def permitted_scalar?(value)
142
+ PERMITTED_SCALAR_TYPES.any? {|type| value.is_a?(type)}
143
+ end
144
+
145
+ def deep_dup_to_hash(params)
146
+ return dup_if_possible(params) unless params.is_a?(PARAM_TYPE)
147
+ {}.tap do |dup|
148
+ params.each do |key, value|
149
+ dup[key] = deep_dup_to_hash(value)
150
+ end
151
+ end
152
+ end
153
+
154
+ def dup_if_possible(v)
155
+ v.duplicable? ? v.dup : v
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,10 @@
1
+ module ShieldsUp
2
+ def self.included(klass)
3
+ klass.before_filter do
4
+ self.params = ShieldsUp::Parameters.new(params, self) unless params.is_a?(ShieldsUp::Parameters)
5
+ end
6
+ klass.rescue_from(ShieldsUp::ParameterMissing) do |parameter_missing_exception|
7
+ render :text => "Required parameter missing: #{parameter_missing_exception}", :status => :bad_request
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,3 @@
1
+ module ShieldsUp
2
+ VERSION = '0.17.0'.freeze
3
+ end
data/lib/shields_up.rb ADDED
@@ -0,0 +1,3 @@
1
+ require 'shields_up/shields_up'
2
+ require 'shields_up/exceptions'
3
+ require 'shields_up/parameters'
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'shields_up/version'
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = 'shields_up'
8
+ s.version = ShieldsUp::VERSION
9
+ s.platform = Gem::Platform::RUBY
10
+ s.authors = ['AppFolio']
11
+ s.email = 'dev@appfolio.com'
12
+ s.description = 'Mass assignment Protection made by AppFolio Inc., inspired by strong_parameters.'
13
+ s.summary = s.description
14
+ s.homepage = 'http://github.com/appfolio/shields_up'
15
+ s.licenses = ['MIT']
16
+
17
+ s.files = Dir['**/*'].reject{ |f| f[%r{^pkg/}] || f[%r{^test/}] }
18
+ s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
+ s.require_paths = ['lib']
20
+ s.add_dependency('activesupport', '~> 3.2')
21
+ end
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: shields_up
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.17.0
5
+ platform: ruby
6
+ authors:
7
+ - AppFolio
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-10-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.2'
27
+ description: Mass assignment Protection made by AppFolio Inc., inspired by strong_parameters.
28
+ email: dev@appfolio.com
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - Gemfile
34
+ - Gemfile.lock
35
+ - LICENSE
36
+ - README.md
37
+ - Rakefile
38
+ - lib/shields_up.rb
39
+ - lib/shields_up/exceptions.rb
40
+ - lib/shields_up/parameters.rb
41
+ - lib/shields_up/shields_up.rb
42
+ - lib/shields_up/version.rb
43
+ - shields_up.gemspec
44
+ homepage: http://github.com/appfolio/shields_up
45
+ licenses:
46
+ - MIT
47
+ metadata: {}
48
+ post_install_message:
49
+ rdoc_options: []
50
+ require_paths:
51
+ - lib
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ requirements: []
63
+ rubyforge_project:
64
+ rubygems_version: 2.2.2
65
+ signing_key:
66
+ specification_version: 4
67
+ summary: Mass assignment Protection made by AppFolio Inc., inspired by strong_parameters.
68
+ test_files: []