README.md in signed_form-0.1.2 vs README.md in signed_form-0.2.0
- old
+ new
@@ -1,13 +1,23 @@
# SignedForm
[![Gem Version](https://badge.fury.io/rb/signed_form.png)](http://badge.fury.io/rb/signed_form)
[![Build Status](https://travis-ci.org/erichmenge/signed_form.png?branch=master)](https://travis-ci.org/erichmenge/signed_form)
[![Code Climate](https://codeclimate.com/github/erichmenge/signed_form.png)](https://codeclimate.com/github/erichmenge/signed_form)
+[![Coverage Status](https://coveralls.io/repos/erichmenge/signed_form/badge.png?branch=master)](https://coveralls.io/r/erichmenge/signed_form)
SignedForm brings new convenience and security to your Rails 4 or Rails 3 application.
+SignedForm is under active development. Please make sure you're reading the README associated with the version of
+SignedForm you're using. Click the tag link on GitHub to switch to the version you've installed to get the correct
+README.
+
+Or be brave and bundle the gem straight from GitHub master.
+
+A nicely displayed version of this README complete with table of contents is available
+[here](http://erichmenge.com/signed_form/).
+
## How It Works
Traditionally, when you create a form with Rails you enter your fields using something like `f.text_field :name` and so
on. Once you're done making your form you need to make sure that you've either set those parameters as accessible in
the model (Rails 3) or use `permit` (Rails 4). This is redundant. Why would you make a form for a user to fill out and
@@ -17,35 +27,45 @@
along with a HMAC-SHA1 signature of those attributes to protect them from tampering. That means no more `permit` and
no more `attr_accessible`. It just works.
What this looks like:
-``` erb
-<%= signed_form_for(@user) do |f| %>
+```erb
+<%= form_for @user, signed: true do |f| %>
<% f.add_signed_fields :zipcode, :state # Optionally add additional fields to sign %>
<%= f.text_field :name %>
<%= f.text_field :address %>
<%= f.submit %>
<% end %>
```
-``` ruby
+```ruby
UsersController < ApplicationController
- def create
+ def update
@user = User.find params[:id]
@user.update_attributes params[:user]
end
end
```
That's it. You're done. Need to add a field? Pop it in the form. You don't need to then update a list of attributes.
-`signed_form_for` works just like the standard `form_for`.
Of course, you're free to continue using the standard `form_for`. `SignedForm` is strictly opt-in. It won't change the
way you use standard forms.
+## More than just Convenience - Security
+
+SignedForm protects you in 3 ways:
+
+* Form fields are signed, so no alteration of the fields are allowed.
+* Form actions are signed. That means a form with an action of `/admin/users/3` will not work when submitted to `/users/3`.
+* Form views are digested (see below). So if you remove a field from your form, old forms will not be accepted despite
+ a valid signature.
+
+The second two methods of security are optional and can be turned off globally or on a form by form basis.
+
## Requirements
SignedForm requires:
* Ruby 1.9 or later
@@ -54,11 +74,13 @@
## Installation
Add this line to your application's Gemfile:
- gem 'signed_form'
+```ruby
+gem 'signed_form'
+```
And then execute:
$ bundle
@@ -68,67 +90,158 @@
If you're using Rails 4, it works out of the box.
You'll need to include `SignedForm::ActionController::PermitSignedParams` in the controller(s) you want to use
SignedForm with. This can be done application wide by adding the `include` to your ApplicationController.
-``` ruby
+```ruby
ApplicationController < ActionController::Base
include SignedForm::ActionController::PermitSignedParams
# ...
end
```
You'll also need to create an initializer:
- $ echo 'SignedForm::HMAC.secret_key = SecureRandom.hex(64)' > config/initializers/signed_form.rb
+```shell
+$ echo "SignedForm.secret_key = '$(rake secret)'" > config/initializers/signed_form.rb
+```
-**IMPORTANT** Please read below for information regarding this secret key.
+You'll probably want to keep this out of version control. Treat this key like you would your session secret, keep it
+private.
## Support for other Builders
-* [SimpleForm Adapter](https://github.com/erichmenge/signed_form-simple_form)
+Any form that wraps `form_for` and the default field helpers will work with SignedForm. For example, a signed SimpleForm
+might look like this:
-## Special Considerations
+```erb
+<%= simple_form_for @user, signed: true do |f| %>
+ f.input :name
+<% end %>
+```
-If you're running only a single application server the above initializer should work great for you, with a couple of
-caveats. If a user is in process of filling out a form and you restart your server, their form will be invalidated.
-You could pick a secret key using `rake secret` and put that in the initializer instead, but then in the event you
-remove a field someone could still access it using the old signature if some malicious person were to keep it around.
+This will create a signed form as expected.
-If you're running multiple application servers, the above initializer will not work. You'll need to keep the key in sync
-between all the servers. The security caveat with that is that if you ever remove a field from a form without updating
-that secret key, a malicious user could still access the field with the old signature. So you'll probably want to choose
-a new secret in the event you remove access to an attribute in a form.
+For builders that don't use the standard field helpers under the hood, you can create an adapter like this:
-My above initializer example errs on the side of caution, generating a new secret key every time the app starts up. Only
-you can decide what is right for you with respect to the secret key.
+```ruby
+class MyAdapter < SomeOtherBuilder
+ include SignedForm::FormBuilder
-### Multiple Access Points
+ def some_helper(field, *other_args)
+ add_signed_fields field
+ super
+ end
+end
+```
-Take for example the case where you have an administrative backend. You might have `/admin/users/edit`. Users can also
-change some information about themselves though, so there's `/users/edit` as well. Now you have an admin that gets
-demoted, but still has a user account. If that admin were to retain a form signature from `/admin/users/edit` they could
-use that signature to modify the same fields from `/users/edit`. As a means of preventing such access SignedForm provides
-the `sign_destination` option to `signed_form_for`. Example:
+Then in your view:
-``` erb
-<%= signed_form_for(@user, sign_destination: true) do |f| %>
- <%= f.text_field :name %>
- <!-- ... -->
+```erb
+<%= form_for @user, signed: true, builder: MyAdapter do |f| %>
+ <%= f.some_helper :name %>
<% end %>
```
-With `sign_destination` enabled, a form generated with a destination of `/admin/users/5` for example will only be
-accepted at that end point. The form would not be accepted at `/users/5`. So in the event you would like to use
-SignedForm on forms for the same resource, but different access levels, you have protection against the form being used
-elsewhere.
+## Form Digests
-### Caching
+SignedForm will create a digest of all the views/partials involved with rendering your form. If the form is modifed old
+forms will be expired. This is done to eliminate the possibility of old forms coming back to bite you.
-Another consideration to be aware of is caching. If you cache a form, and then change the secret key that form will
-perpetually submit parameters that fail verification. So if you want to cache the form you should tie the cache key to
-something that will be changed whenever the secret key changes.
+By default, there is a 5 minute grace period before old forms will be rejected. This is done so that if you make a
+trivial change to a form you won't prevent a form a user is currently filling out from being accepted when you
+restart your server.
+
+Of course if a critical mistake is made (such as allowing an admin field to be set in the form) you could change the
+secret key to prevent any old form from getting through.
+
+By default, these digests are not cached. That means that each form that is submitted will have the views be digested
+again. Most views and partials are relatively small so the cost of computing the MD5 hash of the files is not very
+expensive. However, if this is something you care about SignedForm also provides a memory store
+(`SignedForm::DigestStores::MemoryStore`) that will cache the digests in memory. Other stores could be used as well, as
+long as the object responds to `#fetch` taking the cache key as an argument as well as the block that will return the
+digest.
+
+## Example Configuration
+
+An example config/initializers/signed_form.rb might look something like this (these are the defaults, with the exception
+of the key of course):
+
+```ruby
+SignedForm.config do |c|
+ c.options[:sign_destination] = true
+ c.options[:digest] = true
+ c.options[:digest_grace_period] = 300
+ c.options[:signed] = false # If true, sign all forms by default
+
+ c.digest_store = SignedForm::DigestStores::NullStore.new
+ c.secret_key = 'supersecret'
+end
+```
+
+Those options that are in the options hash are the default per-form options. They can be overridden by passing the same
+option to the `form_for` method.
+
+## Testing Your Controllers
+
+Because your tests won't include a signature you will get a `ForbiddenAttributes` exception in your tests that do mass
+assignment. SignedForm includes a test helper method, `permit_all_parameters` that works with both TestUnit and RSpec.
+
+In your `spec_helper` file or `test_helper` file `require 'signed_form/test_helper'`. Then `include
+SignedForm::TestHelper` in tests where you need it. An example is below.
+
+**Caution**: `permit_all_parameters` without a block modifies the singleton class of the controller under test which
+lasts for the duration of the test. If you want `permit_all_parameters` to be limited to a specific part of the test,
+pass it a block and only that block will be affected. Example:
+
+```ruby
+describe CarsController do
+ include SignedForm::TestHelper
+
+ describe "POST create" do
+ it "should create a car" do
+ permit_all_parameters do
+ # This won't raise ForbiddenAttributesError
+ post :create, {:car => valid_attributes}, valid_session
+ end
+
+ # This one will raise
+ post :create, {:car => valid_attributes}, valid_session
+
+ # ...
+ end
+ end
+end
+```
+
+Example without a block:
+
+```ruby
+describe CarsController do
+ include SignedForm::TestHelper
+
+ describe "POST create" do
+ before { permit_all_parameters }
+
+ describe "with valid params" do
+ it "assigns a newly created car as @car" do
+ post :create, {:car => valid_attributes}, valid_session
+
+ assigns(:car).should be_a(Car)
+ assigns(:car).should be_persisted
+ end
+
+ # ...
+ end
+ end
+end
+```
+
+## I want to hear from you
+
+If you're using SignedForm, I'd love to hear from you. What do you like? What could be better? I'd love to hear your
+ideas. Join the mailing list on librelist to join the discussion at [signedform@librelist.com](mailto:signedform@librelist.com).
## Contributing
1. Fork it
2. Create your feature branch (`git checkout -b my-new-feature`)