Skip to content
This repository has been archived by the owner on Aug 17, 2017. It is now read-only.

Serenity/selective logging #215

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ test/dummy/log/*.log
test/dummy/tmp/
test/dummy/.sass-cache
tmp/
.idea/
1 change: 1 addition & 0 deletions .ruby-gemset
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
strong
1 change: 1 addition & 0 deletions .ruby-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ruby-2.1.5
4 changes: 4 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ GEM
erubis (2.6.6)
abstract (>= 1.0.0)
i18n (0.5.3)
metaclass (0.0.4)
mocha (1.1.0)
metaclass (~> 0.0.1)
rack (1.2.8)
rack-mount (0.6.14)
rack (>= 1.0.0)
Expand All @@ -49,6 +52,7 @@ PLATFORMS
ruby

DEPENDENCIES
mocha
rake
rdoc (>= 4.0.0)
strong_parameters!
158 changes: 24 additions & 134 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,142 +1,32 @@
[![Travis CI](https://secure.travis-ci.org/rails/strong_parameters.png)](http://travis-ci.org/rails/strong_parameters) [![Gem Version](https://badge.fury.io/rb/strong_parameters.png)](http://badge.fury.io/rb/strong_parameters)
# Strong Parameters
# Indiegogo Modified Strong Parameters

With this plugin Action Controller parameters are forbidden to be used in Active Model mass assignments until they have been whitelisted. This means you'll have to make a conscious choice about which attributes to allow for mass updating and thus prevent accidentally exposing that which shouldn't be exposed.
This is a modified version of the [Strong Parameters]
gem. It differs from the original gem in these ways:

In addition, parameters can be marked as required and flow through a predefined raise/rescue flow to end up as a 400 Bad Request with no effort.
* This one is not all or nothing. You include ActionController::StrongParameters
explicitly in
specific controllers. This allows you to have some controllers
with strong parameters and others without.

``` ruby
class PeopleController < ActionController::Base
# This will raise an ActiveModel::ForbiddenAttributes exception because it's using mass assignment
# without an explicit permit step.
def create
Person.create(params[:person])
end

# This will pass with flying colors as long as there's a person key in the parameters, otherwise
# it'll raise an ActionController::MissingParameter exception, which will get caught by
# ActionController::Base and turned into that 400 Bad Request reply.
def update
person = current_account.people.find(params[:id])
person.update_attributes!(person_params)
redirect_to person
end

private
# Using a private method to encapsulate the permissible parameters is just a good pattern
# since you'll be able to reuse the same permit list between create and update. Also, you
# can specialize this method with per-user checking of permissible attributes.
def person_params
params.require(:person).permit(:name, :age)
end
end
```

## Permitted Scalar Values

Given

``` ruby
params.permit(:id)
```

the key `:id` will pass the whitelisting if it appears in `params` and it has a permitted scalar value associated. Otherwise the key is going to be filtered out, so arrays, hashes, or any other objects cannot be injected.

The permitted scalar types are `String`, `Symbol`, `NilClass`, `Numeric`, `TrueClass`, `FalseClass`, `Date`, `Time`, `DateTime`, `StringIO`, `IO`, `ActionDispatch::Http::UploadedFile` and `Rack::Test::UploadedFile`.

To declare that the value in `params` must be an array of permitted scalar values map the key to an empty array:

``` ruby
params.permit(:id => [])
```

To whitelist an entire hash of parameters, the `permit!` method can be used

``` ruby
params.require(:log_entry).permit!
```

This will mark the `:log_entry` parameters hash and any subhash of it permitted. Extreme care should be taken when using `permit!` as it will allow all current and future model attributes to be mass-assigned.

## Nested Parameters

You can also use permit on nested parameters, like:

``` ruby
params.permit(:name, {:emails => []}, :friends => [ :name, { :family => [ :name ], :hobbies => [] }])
```

This declaration whitelists the `name`, `emails` and `friends` attributes. It is expected that `emails` will be an array of permitted scalar values and that `friends` will be an array of resources with specific attributes : they should have a `name` attribute (any permitted scalar values allowed), a `hobbies` attribute as an array of permitted scalar values, and a `family` attribute which is restricted to having a `name` (any permitted scalar values allowed, too).

Thanks to Nick Kallen for the permit idea!

## Require Multiple Parameters
* Likewise, with standard strong parameters, the logging vs raise vs
do nothing setting for unpermitted parameters effects all controllers.
This version allows you to log some controllers while raising in others.
To log, include ActionController::LoggingParameters.

If you want to make sure that multiple keys are present in a params hash, you can call the method twice:
To enable logging rather than exceptions in a particular controller
just include ActionController::LoggingParameters after including
ActionController::StrongParameters, like so:

``` ruby
params.require(:token)
params.require(:post).permit(:title)
```

## Handling of Unpermitted Keys

By default parameter keys that are not explicitly permitted will be logged in the development and test environment. In other environments these parameters will simply be filtered out and ignored.

Additionally, this behaviour can be changed by changing the `config.action_controller.action_on_unpermitted_parameters` property in your environment files. If set to `:log` the unpermitted attributes will be logged, if set to `:raise` an exception will be raised.

## Use Outside of Controllers

While Strong Parameters will enforce permitted and required values in your application controllers, keep in mind
that you will need to sanitize untrusted data used for mass assignment when in use outside of controllers.

For example, if you retrieve JSON data from a third party API call and pass the unchecked parsed result on to
`Model.create`, undesired mass assignments could take place. You can alleviate this risk by slicing the hash data,
or wrapping the data in a new instance of `ActionController::Parameters` and declaring permissions the same as
you would in a controller. For example:

``` ruby
raw_parameters = { :email => "[email protected]", :name => "John", :admin => true }
parameters = ActionController::Parameters.new(raw_parameters)
user = User.create(parameters.permit(:name, :email))
```

## More Examples

Head over to the [Rails guide about Action Controller](http://guides.rubyonrails.org/action_controller_overview.html#more-examples).

## Installation

In Gemfile:
```ruby
class NewBooksController < ActionController::Base
include ActionController::StrongParameters
include ActionController::LoggingParameters

``` ruby
gem 'strong_parameters'
```

and then run `bundle`. To activate the strong parameters, you need to include this module in
every model you want protected.

``` ruby
class Post < ActiveRecord::Base
include ActiveModel::ForbiddenAttributesProtection
end
```

Alternatively, you can protect all Active Record resources by default by creating an initializer and pasting the line:

``` ruby
ActiveRecord::Base.send(:include, ActiveModel::ForbiddenAttributesProtection)
```

If you want to now disable the default whitelisting that occurs in Rails 3.2, change the `config.active_record.whitelist_attributes` property in your `config/application.rb`:

``` ruby
config.active_record.whitelist_attributes = false
def create
params.permit(:book => [:pages])
head :ok
end
end
```

This will allow you to remove / not have to use `attr_accessible` and do mass assignment inside your code and tests.

## Compatibility

This plugin is only fully compatible with Rails versions 3.0, 3.1 and 3.2 but not 4.0+, as it is part of Rails Core in 4.0.
An unofficial Rails 2 version is [strong_parameters_rails2](https://github.com/grosser/strong_parameters/tree/rails2).
[Strong Parameters]: https://github.com/rails/strong_parameters
31 changes: 31 additions & 0 deletions lib/action_controller/logging_parameters.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
module ActionController
class DecoratesParameters
attr_reader :params
cattr_accessor :logger

self.logger = ActionController::Base.logger

methods_to_delegate = (ActionController::Parameters.new.methods - Object.new.methods - [:permit]) + [:require]
delegate *methods_to_delegate, :to => :params

def initialize(params)
@params = params
end

def permit(key)
params.permit(key)
rescue ActionController::UnpermittedParameters => e
DecoratesParameters.logger.warn(e.message)
end
end

module LoggingParameters
def params
@_params ||= DecoratesParameters.new(Parameters.new(request.parameters))
end

def params=(val)
@_params = val.is_a?(Hash) ? DecoratesParameters.new(Parameters.new(val)) : val
end
end
end
2 changes: 1 addition & 1 deletion lib/action_controller/parameters.rb
Original file line number Diff line number Diff line change
Expand Up @@ -265,4 +265,4 @@ def params=(val)
end
end

ActiveSupport.on_load(:action_controller) { include ActionController::StrongParameters }
# ActiveSupport.on_load(:action_controller) { include ActionController::StrongParameters }
1 change: 1 addition & 0 deletions lib/strong_parameters.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require 'action_controller/parameters'
require 'action_controller/logging_parameters'
require 'active_model/forbidden_attributes_protection'
require 'strong_parameters/railtie'
require 'strong_parameters/log_subscriber'
1 change: 1 addition & 0 deletions strong_parameters.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@ Gem::Specification.new do |s|
s.add_dependency "railties", "~> 3.0"

s.add_development_dependency "rake"
s.add_development_dependency "mocha"
end
45 changes: 45 additions & 0 deletions test/action_controller_logging_parameters_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
require 'test_helper'

class NewBooksController < ActionController::Base
include ActionController::StrongParameters
include ActionController::LoggingParameters

def create
params.permit(:book => [:pages])
head :ok
end
end

class ActionControllerLoggingParametersTest < ActionController::TestCase
tests NewBooksController

def setup
ActionController::Parameters.action_on_unpermitted_parameters = :log
end

def teardown
ActionController::Parameters.action_on_unpermitted_parameters = false
end

test "unpermitted parameters log and not raise" do
assert_logged('Unpermitted parameters: fishing') do
post :create, { :book => { :pages => 65 }, :fishing => "Turnips" }
end
assert_response :success
end

def assert_logged(message)
old_logger = ActionController::Base.logger
log = StringIO.new
ActionController::Base.logger = Logger.new(log)

begin
yield

log.rewind
assert_match message, log.read
ensure
ActionController::Base.logger = old_logger
end
end
end
2 changes: 2 additions & 0 deletions test/action_controller_required_params_test.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
require 'test_helper'

class BooksController < ActionController::Base
include ActionController::StrongParameters

def create
params.require(:book).require(:name)
head :ok
Expand Down
2 changes: 2 additions & 0 deletions test/action_controller_tainted_params_test.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
require 'test_helper'

class PeopleController < ActionController::Base
include ActionController::StrongParameters

def create
render :text => params[:person].permitted? ? "untainted" : "tainted"
end
Expand Down
46 changes: 46 additions & 0 deletions test/decorates_params_actually_logs_on_unpermitted_params_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
require 'test_helper'
require 'action_controller/parameters'
require 'action_controller/logging_parameters'

class DecoratesParamsActuallyLogsOnUnpermittedParamsTest < ActiveSupport::TestCase
def setup
ActionController::Parameters.action_on_unpermitted_parameters = :raise
end

def teardown
ActionController::Parameters.action_on_unpermitted_parameters = false
end

test "doesnt raise on unexpected params" do
params = ActionController::DecoratesParameters.new(ActionController::Parameters.new({
:book => { :pages => 65 },
:fishing => "Turnips"
}))

assert_logged('found unpermitted parameters: fishing') do
params.permit(:book => [:pages])
end
end

test "doesnt raise on unexpected nested params" do
params = ActionController::DecoratesParameters.new(ActionController::Parameters.new({
:book => { :pages => 65, :title => "Green Cats and where to find then." }
}))

assert_logged('found unpermitted parameters: title') do
params.permit(:book => [:pages])
end
end

def assert_logged(message)
log = StringIO.new
ActionController::DecoratesParameters.logger = Logger.new(log)

begin
yield

log.rewind
assert_match message, log.read
end
end
end