Consent plugin
Plugin details
Documentation
ruby script/plugin install git://github.com/jcoglan/consent.git
Decision blocks
=======================
Decision blocks always appear at the end of a rule, and should return true if the request is allowed and false if it should be denied. Within the block, you have access to the request, params and session objects so you can use them to make decisions about whether to allow the request.
Within a decision block, you can use the words deny and allow to clean code up a bit. Both these keywords cause the block to return early without processing any other instructions; deny denies the request and allow, well, allows it. For example, the following rule blocks requests for :id between 45 and 60, except for if :id is 54:
users.update(:id => 45..60) do allow if params[:id].to_i == 54 false end
If :id is 54, the rule allows the request; allow makes the block return early so it does not return the value false from its last expression.
Be careful with allow and deny
-----------------------------------
Note that allow and deny are only intended for returning early from rule blocks, and are provided because you can’t actually use the return statement in Ruby blocks without running into some weird differences between procs, blocks and lambdas. You might be tempted to use them to write more expressive single-line rules, but this can lead to ambiguous rules. For example:
# Rule A tags.create { allow unless request.get? } # Rule B tags.create { deny if request.get? }
These look like they might do the same thing, but they don’t. In the face of a GET request, rule A won’t call allow and will simply return nil, while rule B will call deny and block the request. Since deny is a stronger command than allow (requests are allowed by default!), Consent ignores a nil response to a rule as this makes sure deny rules like B work all the time.
In short: if you want a request to be blocked, the rule must call deny or redirect, or evaluate to false. Anything else will be ignored. The best way to write the above rules is:
tags.create { !request.get? }
Redirects
------------------
You can also perform redirects from rule blocks using the redirect keyword. Again, this keyword blocks execution of the rest of the block, and it allows you to use the same shorthand for action expressions as is used for matching requests (see above). For example, here’s a simple rule to block all requests to ProfilesController, redirecting to SiteController#hello:
profiles { redirect site.hello }
Note that if you only specify a controller with no action to redirect to, the Rails convention is to use the index action. For example, this rule redirects to UsersController#index if there is no user logged in, otherwise the request is allowed:
profiles do redirect users unless session[:user] allow end
Throttling
------------------
Rule blocks can be used to throttle traffic to actions using identifier keys. For example, you may have a controller that exposes a web service API to your application, and users need to use an API key to make requests to it. If you wanted to limit each client to a maximum of a thousand requests per day to ApiController, you could write:
api { throttle params[:api_key], 1000.per_day }
This monitors incoming requests matching the expression api, splits the traffic up by the value of params[:api_key], and makes sure that no one key can make more than 1000 requests in any 24-hour period. You’re saying, "Each value of params[:api_key] can make 1000 requests per day to ApiController".
In general, you’ll want to throttle on a per-client basis (where "client" could mean an API key, a user in the database, an IP address, a referring URL, etc) but if you want a global throttle just put in a constant value:
graphs.generate { throttle :all, 200.per(15.minutes) }
This means that GraphsController#generate will fulfill no more than 200 requests in any 15-minute period in total for all incoming traffic, not just per user.
The full throtlling API is as follows:
* throttle takes two arguments. The first must be a string or symbol, and the second must be a rate expression.
* Rate expressions are generated by calling per_second, per_minute, per_hour, per_day or per_month on an integer. You can also simply use per, as shown above, which takes a number of seconds.
Here are some more examples. Don’t take these as gospel as being the best way of doing various things, they’re only meant to make the API clearer…
# Slow down naive dictionary attacks from single machines users.login { throttle request.remote_ip, 1.per(5.seconds) } # Each subdomain can serve 500/day profiles*html { throttle request.subdomains.first, 500.per_day } # Stop Digg taking your site down pages.expensive_action { throttle request.referrer, 20.per_second } # Slow site down for one particular user photos { throttle :all, 3.per_hour if session[:user] == "joker" }
Note the last one throttles all requests for one chosen user, rather than for all users. You’re calling throttle conditionally based on the session data, rather than creating a blanket rule for all users.
Also note that at present Consent stores request history in memory, so memory usage will increase if you use long time periods. This feature has not been load-tested in production yet, but if people report problems I’ll consider storing requests in SQLite.
Helper methods
To make it easier to write clean rules and reduce repetition, Consent allows you to define helper methods in the rule block that you can then use within rules to make decisions. For example, let’s say we want a method to grab the current user from the session:
helper(:user) { User.find(session[:user_id]) }
We can then use this helper in our rules:
profiles.update { user && user.is_admin? }
Consent provides a few built-in helpers to access commonly used data for performing access control. They are as follows:
* request, params and session provide access to the objects of the same name that are commonly accessed in controller code.
* format returns a StringInquirer for testing the response format. For example, format.json? returns true if JSON format has been requested.
* development?, production? and test? each return true or false in response to the environment the app is currently running in.
For example, if you want DebugController accessible only during development, this rule will do just that:
debug { development? }
Extra: request expressions aren’t just for your consent.rb
====================================================================
For the sake of being extra specially helpful, Consent gives you the ability to use the request expression language described above in your controllers, views, and in your routes file. For example, you can map a route like so:
# Shorthand expression for # map.connect "foo", :controller => "tags", :action => "list", :format => "xml" map.connect "foo", tags.list.xml
Or, you can use expressions with methods that expect URLs in controllers and views:
# Redirects to :controller => "users", :action => "create", :name => "jcoglan" redirect_to users.create(:name => "jcoglan") # Opens a form whose action is :controller => "posts", :action => "update" form_for :post, :url => posts.update
These expressions make heavy use of method_missing and operator overloading, so if you really care about performance or you find they cause any other problems, go into this plugin’s init.rb and comment out any line that looks like:
include Consent::Extensions::Xxx
Further Documentation
There is currently no advanced documentation for this plugin.
New documentationEdit plugin | (0 older versions) | Last edited by: hardway, about 1 year ago


