Spree now has a new unified security scheme. Until recently, security in Spree was achieved through a collection of ad hoc approaches. At times we were using the
role_requirement plugin. At other times we were using the
has_role? method of the
restful_authentication plugin. We even used a third approach in
OrdersController to limit users to viewing only their own orders.
In the very early days of the Spree project, Paul Saieg contributed a modified version version of
role_requirement. He called it
easy_role_permissions and the approach involved using a single YAML file to define all of the access restrictions for the entire site. This actually represented a fourth way of handling security in Spree.
After studying all four approaches, I recently received new insight into the matter while working on a real world Spree deployment. This particular client had many different employees accessing their backend and they needed to restrict access to several different controllers based on role. During this exercise it became clear to me that the single YAML file was the superior approach. I renamed the configuration file from
config/spree_permissions.yml and consolidated all of the fragmented security measures into a single approach.
Lets examine the three basic security declarations in
config/spree_permissions.yml to see how this works.
<p>‘Admin::BaseController’:<br /> permission1:<br /> roles : [admin]</p>
This declaration says that you must have the admin role to access any of the methods in
Admin::BaseController. Not only does this apply to
Admin::BaseController but it applies to all controllers that inherit from it unless they are given explicit permissions to the contrary. So that single declaration walls off all admin functionality from the general public.
Now we need to restrict users so that can only see and edit their own accounts.
<p>‘UsersController’:<br /> permission1:<br /> roles : [admin]<br /> options :<br /> except : [new, create]<br /> unless : “current_user.id == object.id”</p>
This declaration says that the
UsersController is restricted to the admin role except for the
create actions. We want to allow those actions because anybody should be able to create a new account. You are not limited to these actions, however, if the id of the current user matches the id of the user in question. In other words, you can
update your own account.
A similar approach is needed for orders.
<p>‘OrdersController’:<br /> permission1:<br /> roles : [admin]<br /> options :<br /> except : [new, create]<br /> unless : “object.user_id == nil || current_user.id == object.user_id”</p>
Admin users can see any order. If you’re not an admin then you are restricted to
create unless you are the user associated with that order.
One missing feature of this security mechanism is that you cannot prohibit certain actions outright, regardless of role. For instance, the
index action should not be allowed at all. Only the
admin would ever need to see the list of all orders, and for that, there is another separate name-spaced controller (
Admin::OrdersController). Right now we use
actions :all, :except =&gt; :index in the
OrdersController but it would be nice to someday support something like
prohibit: index in the YAML.
This single YAML file approach is ideal for real world deployments. Take the case where you have sales agents but you do not want them to be able to add new products or edit existing ones. Using the old approach, you would have to override the entire
Admin::ProductsController (or at least use
class_eval to amend significant parts of it.) So now you have monkey-patched a controller just to change the security settings. Seems a shame since you didn’t even need to introduce any extra functionality. With the YAML security file you can now just add this:
<p>‘Admin::ProductsController’:</p> <ol> <li>admin and sales can access admin/products<br /> permission1:<br /> roles : [admin, sales]</li> <li>sales is restricted to index and show<br /> permission2:<br /> roles : [admin]<br /> options :<br /> except : [index, show]<br />
Spree is designed for extensive customization but its always going to be case that the less of the Spree core you change, the better. This approach allows you to keep a single site-specific configuration file for your security settings and leave the original Spree controllers intact.