param_guard 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,20 @@
1
+ Copyright 2012 Levente Bagi
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.
@@ -0,0 +1,82 @@
1
+ = Param Guard
2
+
3
+ Simple utility to check the format of request params, e.g. in a Rails controller, but it can be used on any hash-like Ruby structure.
4
+
5
+ == Installation
6
+
7
+ In Gemfile:
8
+
9
+ gem 'param_guard'
10
+
11
+ == Example
12
+
13
+ For example, in your controller, you could do:
14
+
15
+ def create
16
+ @user = User.new(user_params)
17
+ # ...
18
+ end
19
+
20
+ def user_params
21
+ ParamGuard.filter(
22
+ params,
23
+ {
24
+ user: [:required, :hash, {
25
+ email: [:permitted, :scalar],
26
+ password: [:permitted, :scalar],
27
+ password_confirmation: [:permitted, :scalar],
28
+ profile_attributes: [:required, :hash, {
29
+ date_of_birth: [:permitted, [:scalar, :multi]],
30
+ country_id: [:permitted, [:scalar]],
31
+ terms_ok: [:permitted, [:scalar]],
32
+ }]
33
+ }]
34
+ }
35
+ )[:user]
36
+ end
37
+
38
+ == Definition format
39
+
40
+ The definition should be a hash. The key represents a key in the params hash the value is an array of 2 or 3 values.
41
+
42
+ [1st item]
43
+ <code>:required</code> or <code>:permitted</code>.
44
+
45
+ [2nd item]
46
+ The 2nd item: the expected format of the value
47
+
48
+ - <code>:string</code>
49
+ - <code>:integer</code>
50
+ - <code>:float</code>
51
+ - <code>:hash</code>
52
+ - <code>:scalar</code> - non-structured types
53
+ - <code>:array</code>
54
+ - <code>:multi</code> - multi-parameter values handled by Rails, typically dates and times
55
+
56
+ The required type can also be an array, if any of these matches the value the value will be accepted.
57
+
58
+ [3rd item]
59
+
60
+ If the type is a hash, these will be the definition for the nested hash.
61
+
62
+ See the tests for examples.
63
+
64
+ == Return values
65
+
66
+ This will return a duplicate of params, from which parameters that are not mentioned, will be removed.
67
+
68
+ == Exceptions
69
+
70
+ If a key is required, but is not specified, a <code>ParamGuard::ParameterMissing</code> exception will be raised.
71
+
72
+ If the type is not right, <code>ParamGuard::ParameterOfInvalidType</code> will be raised.
73
+
74
+ Both are a sublass of <code>ParamGuard::InvalidParameters</code>.
75
+
76
+ You should catch these in the controller and respond with HTTP 400 Bad Request. E.g. in you +ApplicationController+:
77
+
78
+ rescue_from ParamGuard::InvalidParameters do |exception|
79
+ Rails.logger.info "Bad Request: #{exception.message}"
80
+ render :text => exception.message, :status => :bad_request
81
+ end
82
+
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ require 'bundler/gem_tasks'
5
+ rescue LoadError
6
+ raise 'You must `gem install bundler` and `bundle install` to run rake tasks'
7
+ end
8
+
9
+ require 'rdoc/task'
10
+
11
+ RDoc::Task.new(:rdoc) do |rdoc|
12
+ rdoc.rdoc_dir = 'rdoc'
13
+ rdoc.title = 'ParamGuard'
14
+ rdoc.options << '--line-numbers'
15
+ rdoc.rdoc_files.include('README.rdoc')
16
+ rdoc.rdoc_files.include('lib/**/*.rb')
17
+ end
18
+
19
+ require 'rake/testtask'
20
+
21
+ Rake::TestTask.new(:test) do |t|
22
+ t.libs << 'lib'
23
+ t.libs << 'test'
24
+ t.pattern = 'test/**/*_test.rb'
25
+ t.verbose = false
26
+ end
27
+
28
+ task :default => :test
@@ -0,0 +1,90 @@
1
+ module ParamGuard
2
+ InvalidParameters = Class.new(StandardError)
3
+ ParameterMissing = Class.new(InvalidParameters)
4
+ ParameterOfInvalidType = Class.new(InvalidParameters)
5
+
6
+ autoload :ParamDefinition, 'param_guard/param_definition'
7
+
8
+ class << self
9
+
10
+ # Sanitiize params based on rules in defs.
11
+ # Returns a duplicate of params, from which all non-declared fields are deleted.
12
+ # Raises errors ParameterMissing or ParameterOfInvalidType.
13
+ def filter(params, defs, parent_keys = [])
14
+ return params if defs.nil?
15
+ keys_to_keep = []
16
+ defs.each do |key, key_def|
17
+ definition = ParamDefinition.new(*key_def)
18
+ structure, value, params_to_keep = get_param(params, key)
19
+ if value
20
+ if definition.types.any?
21
+ unless definition.types.any?{|t| is_of_type?(t, value, structure)}
22
+ raise ParameterOfInvalidType.new(
23
+ "param '#{keys_to_s(parent_keys + [key])}' must be #{definition.types_for_sentence}"
24
+ )
25
+ end
26
+ end
27
+ keys_to_keep.concat params_to_keep.keys.map(&:to_s)
28
+ if structure == :normal && value.kind_of?(Hash)
29
+ params[key] = filter(value, definition.subdef, parent_keys + [key])
30
+ end
31
+ elsif definition.required?
32
+ raise ParameterMissing.new(
33
+ "param '#{keys_to_s(parent_keys + [key])}' is missing"
34
+ )
35
+ end
36
+ end
37
+ filtered = params.dup
38
+ filtered.delete_if{|key, value| ! keys_to_keep.include? key.to_s }
39
+ filtered
40
+ end
41
+
42
+ private
43
+
44
+ def get_param(params, key)
45
+ key, value = if v = params[key.to_sym]
46
+ [key.to_sym, v]
47
+ elsif v = params[key.to_s]
48
+ [key.to_s, v]
49
+ else
50
+ [key, nil]
51
+ end
52
+ if value
53
+ return :normal, value, { key => value }
54
+ elsif (keys = params.keys.grep(/\A#{Regexp.escape(key.to_s)}\(\d+[if]?\)\z/)).any?
55
+ values = keys.sort.map{|k| params[k]}
56
+ return :multi, values, Hash[keys.map{|k| [k, params[k]]}]
57
+ else
58
+ return :none, nil, {}
59
+ end
60
+ end
61
+
62
+ def is_of_type?(type, value, structure)
63
+ case type.to_sym
64
+ when :string
65
+ value.kind_of? String
66
+ when :integer
67
+ value.kind_of? Fixnum
68
+ when :float
69
+ value.kind_of? Float
70
+ when :hash
71
+ value.kind_of? Hash
72
+ when :scalar
73
+ [String, Fixnum, Float, NilClass, FalseClass, TrueClass].any?{|klass| value.kind_of? klass}
74
+ when :array
75
+ value.kind_of? Array
76
+ when :multi
77
+ structure == :multi
78
+ else
79
+ raise "Unknown type: #{type.inspect}"
80
+ end
81
+ end
82
+
83
+ def keys_to_s(keys)
84
+ "#{keys.first}#{keys[1..-1].map{|k| "[#{k}]"}.join}"
85
+ end
86
+
87
+ end
88
+ end
89
+
90
+
@@ -0,0 +1,21 @@
1
+ module ParamGuard
2
+
3
+ # Rules describing the requirements for a parameter.
4
+ class ParamDefinition < Struct.new(:presence, :type_or_types, :subdef)
5
+ def required?
6
+ presence == :required
7
+ end
8
+
9
+ def types
10
+ Array(type_or_types)
11
+ end
12
+
13
+ def types_for_sentence
14
+ a = types.map(&:to_s)
15
+ return nil if a.empty?
16
+ [a[0..-3] + [a.last(2).join(' or ')]].compact.join(', ')
17
+ end
18
+ end
19
+
20
+ end
21
+
@@ -0,0 +1,3 @@
1
+ module ParamGuard
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,21 @@
1
+ $: << File.expand_path("../../lib", __FILE__)
2
+ require 'test/unit'
3
+ require 'param_guard/param_definition'
4
+
5
+ class ParamGuardParamDefinitionTest < Test::Unit::TestCase
6
+ def test_types_for_sentence_no_types
7
+ assert_equal nil, ParamGuard::ParamDefinition.new.types_for_sentence
8
+ end
9
+
10
+ def test_types_for_sentence_one_type
11
+ assert_equal 'string', ParamGuard::ParamDefinition.new(nil, :string).types_for_sentence
12
+ end
13
+
14
+ def test_types_for_sentence_two_types
15
+ assert_equal 'string or integer', ParamGuard::ParamDefinition.new(nil, [:string, :integer]).types_for_sentence
16
+ end
17
+
18
+ def test_types_for_sentence_three_types
19
+ assert_equal 'hash, string or integer', ParamGuard::ParamDefinition.new(nil, [:hash, :string, :integer]).types_for_sentence
20
+ end
21
+ end
@@ -0,0 +1,121 @@
1
+ $: << File.expand_path("../../lib", __FILE__)
2
+ require 'test/unit'
3
+ require 'param_guard'
4
+
5
+ class ParamGuardTest < Test::Unit::TestCase
6
+
7
+ def test_filter_returns_only_required_and_permitted_params
8
+ defs = { :a => [:required], :b => [:permitted] }
9
+ params = { :a => 1, :b => 2, :c => 3 }
10
+ filtered = ParamGuard.filter(params, defs)
11
+ assert_equal({ :a => 1, :b => 2 }, filtered)
12
+ end
13
+
14
+ def test_filter_raises_error_if_required_param_missing
15
+ defs = { :a => [:required], :b => [:permitted] }
16
+ params = { :b => 2, :c => 3 }
17
+ assert_raise ParamGuard::ParameterMissing do
18
+ filtered = ParamGuard.filter(params, defs)
19
+ end
20
+ end
21
+
22
+ def test_filter_raises_error_if_param_of_wrong_type
23
+ defs = { :a => [:required, :string] }
24
+ params = { :a => 1 }
25
+ assert_raise ParamGuard::ParameterOfInvalidType do
26
+ filtered = ParamGuard.filter(params, defs)
27
+ end
28
+ end
29
+
30
+ def test_filter_raises_error_if_param_of_wrong_type_multiple_types_allowed
31
+ defs = { :a => [:required, [:string, :integer]] }
32
+ params = { :a => {} }
33
+ assert_raise ParamGuard::ParameterOfInvalidType do
34
+ filtered = ParamGuard.filter(params, defs)
35
+ end
36
+ end
37
+
38
+ def test_filter_when_param_of_matches_any_type
39
+ defs = { :a => [:required, [:string, :integer]] }
40
+ params = { :a => 1 }
41
+ filtered = ParamGuard.filter(params, defs)
42
+ assert_equal({ :a => 1 }, filtered)
43
+ end
44
+
45
+ def test_filter_raises_error_if_missing_parameter_in_nested_definition
46
+ defs = {
47
+ :user => [:required, :hash, {
48
+ :name => [:required, :string]
49
+ }]
50
+ }
51
+ params = { :user => {} }
52
+ assert_raise ParamGuard::ParameterMissing do
53
+ filtered = ParamGuard.filter(params, defs)
54
+ end
55
+ end
56
+
57
+ def test_filter_returns_only_valid_parameters_in_nested_definition
58
+ defs = {
59
+ :user => [:required, :hash, {
60
+ :name => [:required, :string]
61
+ }]
62
+ }
63
+ params = { :user => { :name => 'Bob', :email => 'bob@example.com' } }
64
+ filtered = ParamGuard.filter(params, defs)
65
+ assert_equal({ :user => { :name => 'Bob' } }, filtered)
66
+ end
67
+
68
+ def test_filter_accept_multiparams
69
+ defs = {
70
+ dob: [:required, :multi]
71
+ }
72
+ params = { "dob(1i)" => 1999, "dob(2i)" => 1, "dob(3i)" => 1 }
73
+ filtered = ParamGuard.filter(params, defs)
74
+ assert_equal params, filtered
75
+ end
76
+
77
+ def test_filter_accept_multiparams_when_scalar_or_multi_expected
78
+ defs = {
79
+ dob: [:required, [:scalar, :multi]]
80
+ }
81
+ params = { "dob(1i)" => "1999", "dob(2i)" => "1", "dob(3i)" => "1" }
82
+ filtered = ParamGuard.filter(params, defs)
83
+ assert_equal params, filtered
84
+ end
85
+
86
+ def test_filter_accept_scalar_when_scalar_or_multi_expected
87
+ defs = {
88
+ dob: [:required, [:scalar, :multi]]
89
+ }
90
+ params = { "dob" => "1999-01-01" }
91
+ filtered = ParamGuard.filter(params, defs)
92
+ assert_equal params, filtered
93
+ end
94
+
95
+ def test_filter_raise_if_not_multiparams_given
96
+ defs = {
97
+ dob: [:required, :multi]
98
+ }
99
+ params = { "dob" => "1999-01-02" }
100
+ assert_raise ParamGuard::ParameterOfInvalidType do
101
+ filtered = ParamGuard.filter(params, defs)
102
+ end
103
+ end
104
+
105
+ def test_filter_accept_multiparams_or_scalar
106
+ defs = {
107
+ dob: [:required, [:scalar, :multi]]
108
+ }
109
+ params = { "dob" => "1999-01-02" }
110
+ filtered = ParamGuard.filter(params, defs)
111
+ assert_equal params, filtered
112
+ end
113
+
114
+ def test_filter_does_not_alter_original_params_object
115
+ defs = { :a => [:required] }
116
+ params = { :a => 1, :b => 2 }
117
+ filtered = ParamGuard.filter(params, defs)
118
+ assert_equal({ :a => 1, :b => 2 }, params)
119
+ assert_equal({ :a => 1 }, filtered)
120
+ end
121
+ end
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: param_guard
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Levente Bagi
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-12-04 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rake
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ description:
31
+ email:
32
+ - bagilevi@gmail.com
33
+ executables: []
34
+ extensions: []
35
+ extra_rdoc_files: []
36
+ files:
37
+ - lib/param_guard/param_definition.rb
38
+ - lib/param_guard/version.rb
39
+ - lib/param_guard.rb
40
+ - MIT-LICENSE
41
+ - Rakefile
42
+ - README.rdoc
43
+ - test/param_definition_test.rb
44
+ - test/param_guard_test.rb
45
+ homepage:
46
+ licenses: []
47
+ post_install_message:
48
+ rdoc_options: []
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ! '>='
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ segments:
58
+ - 0
59
+ hash: 1412847858866998539
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ segments:
67
+ - 0
68
+ hash: 1412847858866998539
69
+ requirements: []
70
+ rubyforge_project:
71
+ rubygems_version: 1.8.24
72
+ signing_key:
73
+ specification_version: 3
74
+ summary: Filter parameters by defining a required structure
75
+ test_files:
76
+ - test/param_definition_test.rb
77
+ - test/param_guard_test.rb