optin_parsing 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0dba66eb4acade9c0e1b6178f68c9ed4acf64b51
4
+ data.tar.gz: 7291ad94416495756a203944f098e61c6e067be8
5
+ SHA512:
6
+ metadata.gz: ae20475ce706fe259e27049b5db4f640fb250e4b9ab1f55ee882e9b23322f4ba11fb504b35dc8c5f51b6e590518337046bce9ce986fea432891e6d5a183e3c1c
7
+ data.tar.gz: 85af8e502ce6308214b24b9b2b947cf82dd286bd65ef6b2d761f4ab59bb6aeb7ae6f9a9dabe9baa2254220bfb0eb8d9024eec80ecef664dbc1b7f037deab47e2
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/CHANGELOG ADDED
@@ -0,0 +1,4 @@
1
+ 0.2.0
2
+ =====
3
+
4
+ Honour ActionController's parameter filtering
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in optin_parsing.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Frederick Cheung
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,69 @@
1
+ # OptinParsing
2
+
3
+ Automatic parsing of json and xml request bodies requires care. Beyond vulnerabilities in the parameter parsing code itself the ability for a requester to be able to control the type of parameters combines badly with some of mysql's typecasting logic, for example
4
+
5
+ User.find_by_secret_token(0)
6
+
7
+ returns a user with a secret token that does not look to mysql like a valid integer instead of returning nil. Rails 3.2.12 contains some mitigations for this (the above example does not work on rails 3.2.12 and above) but can't catch all cases. You can avoid this by calling `to_s` on parameters that should be strings. For more details see the [security advisory](http://groups.google.com/group/rubyonrails-security/browse_thread/thread/64e747e461f98c25). It is easy to forget to do this and it goes against the philosophy of "secure by default".
8
+
9
+ An easy mitigation is to simply disable this parsing by removing mime types from `ActionDispatch::ParamsParser::DEFAULT_PARSERS` this is a change that affects all controllers.
10
+
11
+ This gem allows the automatic parameter parsing to be turned on per controller and per action, so that (for example) api endpoints that need to accept json and/or xml can continue to work (and be audited for their parameter usage) but reducing the surface of attack by not allowing json bodies for all those requests that do not need it.
12
+
13
+ The default is for such parsing to be disabled for all controllers.
14
+
15
+ ## Installation
16
+
17
+ Add this line to your application's Gemfile:
18
+
19
+ gem 'optin_parsing'
20
+
21
+ And then execute:
22
+
23
+ $ bundle
24
+
25
+ Or install it yourself as:
26
+
27
+ $ gem install optin_parsing
28
+
29
+ ## Usage
30
+
31
+ Allow a controller to parse xml bodies automatically
32
+
33
+ class MyController < ApplicationController
34
+ parses :xml
35
+ end
36
+
37
+ Allow a controller to parse json bodies automatically, only for a certain action
38
+
39
+ class MyController < ApplicationController
40
+ parses :json, :only => :some_action
41
+ end
42
+
43
+ Allow a controller to parse json bodies automatically, except for certain actions
44
+
45
+ class MyController < ApplicationController
46
+ parses :json, :except => [:some_exempt_action, :another_exempt_action]
47
+ end
48
+
49
+ Allow a controller to parse a specific mime type, with a custom strategy
50
+
51
+ class MyController < ApplicationController
52
+ parses Mime::YAML do |raw_post|
53
+ #parse raw_post and return a hash of data
54
+ end
55
+ end
56
+
57
+ Subclasses inherit their parent classes' settings
58
+
59
+ ## Caveats
60
+
61
+ Ordinarily parameters are parsed by a Rack middleware. This gem defers parsing until the request is processed by the controller: the parsed parameters will not be available to middleware code that runs before this.
62
+
63
+ ## Contributing
64
+
65
+ 1. Fork it
66
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
67
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
68
+ 4. Push to the branch (`git push origin my-new-feature`)
69
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,5 @@
1
+ require "optin_parsing/version"
2
+ require "optin_parsing/railtie"
3
+ require "optin_parsing/controller_additions"
4
+ module OptinParsing
5
+ end
@@ -0,0 +1,98 @@
1
+ module OptinParsing
2
+ module ControllerAdditions
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ class_attribute :parse_strategies
7
+ self.parse_strategies = {}
8
+ hide_action :decode_formatted_parameters
9
+ end
10
+
11
+ module ClassMethods
12
+ # Declares that you want to parse a specific body type, for this controller and its subclasses
13
+ # You can either pass the symbols +:xml+ or +:json+ or an instance of Mime::Type. If you pass
14
+ # a Mime::Type you must also supply a block. The block will be passed the request raw post data
15
+ # and should return a hash of parsed parameter data
16
+ #
17
+ # You can also supply a hash of options containing the keys +:except+ or +:only+ to restrict which
18
+ # actions will be parsed
19
+ #
20
+ #
21
+ # @param [Symbol, Mime::Type] mime_type_or_short_cut
22
+ # @option options [Array,Symbol] :except A list of actions for which parsing should not be enabled
23
+ # @option options [Array,Symbol] :only A list of actions for which parsing should be enabled
24
+
25
+ def parses mime_type_or_short_cut, options={}, &block
26
+
27
+ case mime_type_or_short_cut
28
+ when Mime::Type
29
+ raise ArgumentError, "You must supply a block when specifying a mime type" unless block
30
+ self.parse_strategies = parse_strategies.merge(mime_type_or_short_cut => [block, normalize_optin_options(options)])
31
+ when :xml
32
+ self.parse_strategies = parse_strategies.merge(Mime::XML => [:xml, normalize_optin_options(options)])
33
+ when :json
34
+ self.parse_strategies = parse_strategies.merge(Mime::JSON => [:json, normalize_optin_options(options)])
35
+ end
36
+ end
37
+ private
38
+
39
+ def normalize_optin_options options
40
+ options.each_with_object({}) do |(key,value), options|
41
+ options[key] = Array(value).collect {|action_name| action_name.to_s}
42
+ end
43
+ end
44
+ end
45
+
46
+ def process_action(method_name, *args)
47
+ strategy, options = parse_strategies[request.content_mime_type]
48
+ if strategy
49
+ if should_decode_body(options)
50
+ if data = decode_formatted_parameters(strategy)
51
+ params.merge!(data)
52
+ log_parsed(apply_filter_parameters(data))
53
+ end
54
+ end
55
+ end
56
+ super
57
+ end
58
+
59
+ private
60
+
61
+ def should_decode_body options
62
+ if options[:only]
63
+ options[:only].include?(action_name)
64
+ elsif options[:except]
65
+ !options[:except].include?(action_name)
66
+ else
67
+ true
68
+ end
69
+ end
70
+
71
+ def decode_formatted_parameters(strategy)
72
+ case strategy
73
+ when Proc
74
+ strategy.call(request.raw_post)
75
+ when :xml
76
+ data = request.deep_munge(Hash.from_xml(request.body.read) || {})
77
+ request.body.rewind if request.body.respond_to?(:rewind)
78
+ data.with_indifferent_access
79
+ when :json
80
+ data = request.deep_munge ActiveSupport::JSON.decode(request.body.read)
81
+ request.body.rewind if request.body.respond_to?(:rewind)
82
+ data = {:_json => data} unless data.is_a?(Hash)
83
+ data.with_indifferent_access
84
+ else
85
+ false
86
+ end
87
+ end
88
+
89
+ def log_parsed(data)
90
+ Rails.logger.info "Parsed #{request.content_mime_type}: #{data.inspect}"
91
+ end
92
+
93
+ def apply_filter_parameters(data)
94
+ parameter_filter = ActionDispatch::Http::ParameterFilter.new(Rails.configuration.filter_parameters)
95
+ parameter_filter.filter(data)
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,18 @@
1
+ require "optin_parsing"
2
+ require "rails"
3
+
4
+ module OptinParsing
5
+ # = OptinParsing Railtie
6
+ class Railtie < Rails::Railtie
7
+
8
+ initializer "optinparsing.inject_modules" do |app|
9
+ ActiveSupport.on_load(:action_controller) do
10
+ ActionController::Base.send :include, OptinParsing::ControllerAdditions
11
+ end
12
+ end
13
+
14
+ initializer "optinparsing.remove_default_parsers" do |app|
15
+ config.app_middleware.delete '::ActionDispatch::ParamsParser'
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,3 @@
1
+ module OptinParsing
2
+ VERSION = "0.2.0"
3
+ end
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'optin_parsing/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "optin_parsing"
8
+ gem.version = OptinParsing::VERSION
9
+ gem.authors = ["Frederick Cheung"]
10
+ gem.email = ["frederick.cheung@gmail.com"]
11
+ gem.description = %q{Mitigate the dangers of automatic json/xml parsing by only enabling them for the controllers & actions that require it}
12
+ gem.summary = %q{Mitigate the dangers of automatic json/xml parsing by only enabling them for the controllers & actions that require it}
13
+ gem.homepage = "https://github.com/fcheung/optin_parsing"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_development_dependency "rspec", "~>2.10"
21
+ gem.add_development_dependency "rspec-rails", "~>2.10"
22
+ gem.add_development_dependency "rspec-instafail"
23
+
24
+ end
@@ -0,0 +1,144 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'optin_parsing controller with controller additions included', :type => :controller do
4
+ include Rails.application.routes.url_helpers
5
+ module Actions
6
+ def index
7
+ @parsed_params = params.except(:controller, :action)
8
+ head :ok
9
+ end
10
+
11
+ def new
12
+ @parsed_params = params.except(:controller, :action)
13
+ head :ok
14
+ end
15
+
16
+ def _routes
17
+ @routes
18
+ end
19
+ end
20
+
21
+
22
+ def json
23
+ '{"test": "value"}'
24
+ end
25
+
26
+ def xml
27
+ '<?xml version="1.0" encoding="UTF-8"?><test>value</test>'
28
+ end
29
+
30
+ context 'when the module is included' do
31
+ def self.preconfigured_controller(&block)
32
+ controller(ActionController::Base) do
33
+ include OptinParsing::ControllerAdditions
34
+ include Actions
35
+ instance_eval(&block)
36
+ end
37
+ end
38
+
39
+ context 'parsing is not enabled' do
40
+ preconfigured_controller {}
41
+
42
+ it { should_not parse_parameters.of_type(Mime::JSON).for_action(:index).with_body(json) }
43
+ it { should_not parse_parameters.of_type(Mime::XML).for_action(:index).with_body(xml) }
44
+ end
45
+
46
+ context 'parsing of xml is enabled' do
47
+ context 'unconditionally' do
48
+ preconfigured_controller do
49
+ parses :xml
50
+ end
51
+
52
+ it { should_not parse_parameters.of_type(Mime::JSON).for_action(:index).with_body(json) }
53
+ it { should parse_parameters.of_type(Mime::XML).for_action(:index).with_body(xml) }
54
+ end
55
+ end
56
+
57
+ context 'parsing of a custom type is enabled' do
58
+ preconfigured_controller do
59
+ parses Mime::YAML do |body|
60
+ YAML.load(body)
61
+ end
62
+ end
63
+ it { should parse_parameters.of_type(Mime::YAML).for_action(:index).with_body({'test' => 'value'}.to_yaml) }
64
+ end
65
+
66
+ context 'parsing of json is enabled' do
67
+ context 'unconditionally' do
68
+ preconfigured_controller do
69
+ parses :json
70
+ end
71
+
72
+ it 'logs parsed parameters honouring filter_parameters config' do
73
+ request.env['RAW_POST_DATA'] = json
74
+ request.env['CONTENT_TYPE'] = Mime::JSON.to_s
75
+ Rails.configuration.filter_parameters = [:test]
76
+
77
+ controller.should_receive(:log_parsed).with({'test' => '[FILTERED]'})
78
+
79
+ put :index
80
+ end
81
+
82
+ it { should parse_parameters.of_type(Mime::JSON).for_action(:index).with_body(json) }
83
+ it { should_not parse_parameters.of_type(Mime::XML).for_action(:index).with_body(xml) }
84
+ end
85
+
86
+ context 'an only option is specified' do
87
+ preconfigured_controller do
88
+ parses :json, :only => :index
89
+ end
90
+
91
+ context 'the action is contained in the list' do
92
+ it { should parse_parameters.of_type(Mime::JSON).for_action(:index).with_body(json) }
93
+ end
94
+
95
+ context 'the action is not contained in the list' do
96
+ it { should_not parse_parameters.of_type(Mime::JSON).for_action(:new).with_body(json) }
97
+ end
98
+ end
99
+
100
+ context 'an except option is specified' do
101
+ preconfigured_controller do
102
+ parses :json, :except => :index
103
+ end
104
+
105
+ context 'the action is contained in the list' do
106
+ it { should_not parse_parameters.of_type(Mime::JSON).for_action(:index).with_body(json) }
107
+ end
108
+
109
+ context 'the action is not contained in the list' do
110
+ it { should parse_parameters.of_type(Mime::JSON).for_action(:new).with_body(json) }
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
116
+
117
+
118
+ RSpec::Matchers.define :parse_parameters do |y|
119
+
120
+ chain :of_type do |mime_type|
121
+ @mime_type = mime_type
122
+ end
123
+
124
+ chain :for_action do |action_name|
125
+ @action_name = action_name
126
+ end
127
+
128
+ chain :with_body do |body|
129
+ @body = body
130
+ end
131
+
132
+ match do
133
+ @expected = {'test' => 'value'}
134
+ request.env['RAW_POST_DATA'] = @body
135
+ request.env['CONTENT_TYPE'] = @mime_type.to_s
136
+ put @action_name.to_sym
137
+ @actual = assigns(:parsed_params)
138
+ @actual == @expected
139
+ end
140
+
141
+ failure_message_for_should do
142
+ "Expected parameters #{@expected} got #{@actual}"
143
+ end
144
+ end
@@ -0,0 +1,21 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ require 'rails/all'
4
+ require 'yaml'
5
+
6
+ $: << File.dirname(__FILE__) + '/../lib'
7
+
8
+ require 'optin_parsing'
9
+ require 'action_controller'
10
+ require 'rspec/rails'
11
+
12
+ module DummyApplication
13
+ class Application < Rails::Application
14
+ config.secret_token = '*******************************'
15
+ config.logger = Logger.new(File.expand_path('../test.log', __FILE__))
16
+ Rails.logger = config.logger
17
+ end
18
+ end
19
+ RSpec.configure do |config|
20
+
21
+ end
metadata ADDED
@@ -0,0 +1,103 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: optin_parsing
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Frederick Cheung
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-04-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.10'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.10'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec-rails
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.10'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.10'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec-instafail
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: Mitigate the dangers of automatic json/xml parsing by only enabling them
56
+ for the controllers & actions that require it
57
+ email:
58
+ - frederick.cheung@gmail.com
59
+ executables: []
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - ".gitignore"
64
+ - CHANGELOG
65
+ - Gemfile
66
+ - LICENSE.txt
67
+ - README.md
68
+ - Rakefile
69
+ - lib/optin_parsing.rb
70
+ - lib/optin_parsing/controller_additions.rb
71
+ - lib/optin_parsing/railtie.rb
72
+ - lib/optin_parsing/version.rb
73
+ - optin_parsing.gemspec
74
+ - spec/optin_parsing_spec.rb
75
+ - spec/spec_helper.rb
76
+ homepage: https://github.com/fcheung/optin_parsing
77
+ licenses: []
78
+ metadata: {}
79
+ post_install_message:
80
+ rdoc_options: []
81
+ require_paths:
82
+ - lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ required_rubygems_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ requirements: []
94
+ rubyforge_project:
95
+ rubygems_version: 2.2.2
96
+ signing_key:
97
+ specification_version: 4
98
+ summary: Mitigate the dangers of automatic json/xml parsing by only enabling them
99
+ for the controllers & actions that require it
100
+ test_files:
101
+ - spec/optin_parsing_spec.rb
102
+ - spec/spec_helper.rb
103
+ has_rdoc: