Spree Commerce

Try It Now

Unified Security Scheme

Posted on January 13, 2009 by Sean Schofield

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/easy_role_permissions.yml to 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>&#8216;Admin::BaseController&#8217;:<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>&#8216;UsersController&#8217;:<br />
  permission1:<br />
    roles : [admin]<br />
    options :<br />
      except : [new, create]<br />
      unless : &#8220;current_user.id == object.id&#8221;</p>

This declaration says that the UsersController is restricted to the admin role except for the new and 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 edit and update your own account.

A similar approach is needed for orders.

<p>&#8216;OrdersController&#8217;:<br />
  permission1:<br />
    roles : [admin]<br />
    options :<br />
      except : [new, create]<br />
      unless : &#8220;object.user_id == nil || current_user.id == object.user_id&#8221;</p>

Admin users can see any order. If you’re not an admin then you are restricted to new or 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 =&amp;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:

	<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.