User permissions

Architecture overview

For authorization management, Solidus uses CanCanCan (a fork of the popular, now unmaintained CanCan library), a Ruby/Rails authorization library that allows you to define granular permissions on collection and individual resources. This is combined with an in-house user role system that allows you to assign certain permissions to certain groups of users.

At a high level, here are the main concepts in the authorization system:

  • The ability is a CanCan class that stores information about the permissions a user has on certain collections or resources, and the conditions under which such permissions must be granted. Solidus has its own ability class which has some custom logic to support permission sets.

  • Permission sets are Solidus classes that activate certain sets of permissions on the user's ability. For instance, the StockManagement permission set allows the user to manage stock items and locations. They are a neat way to encapsulate groups of related permissions.

  • The role configuration is a configuration class that associates user roles (stored through the Spree::RoleUser model) with a collection of permission sets. By default, a Solidus store has two roles: default and admin, which have customer and super-admin permissions respectively.

Solidus only uses the DefaultCustomer and SuperUser permission sets internally: all the others are provided for your own convenience, in case you want to define custom roles with more granular permissions.

The process Solidus follows to determine the current user's permissions is pretty simple:

  1. If the user is authenticated, it retrieves the current user's roles. If the user isn't authenticated, it assumes the user only has the default role.

  2. It collects all the permission sets for the user's roles.

  3. It applies all the permission sets to the current ability.

This simple but flexible system allows you to create custom permissions and roles, both in extensions and in your main application, as well as do things such as store permission sets in the database rather than in Ruby code, allowing the administrator to change them via the UI.

Let's see how we can leverage some of that flexibility.

Custom user roles

Let's start with something simple: we'll define a new customer_service role that has limited access to the Solidus backend. Customer service representatives will only be able to display users, products and stock locations.

The first step is to create the Spree::Role record that we'll use to store our role. In order to do that, you can simply add the following to your seeds:

# ...
Spree::Role.where(name: 'customer_service').first_or_create!

Then, run the seeds:

$ rails db:seeds

Now that you have defined your role, you just need to associate it with the desired permission sets in your Solidus configuration. You can do this in the Solidus initializer:

config/initializers/spree.rb
Spree.config do |config|
# ...
config.roles.assign_permissions :customer_service, [
Spree::PermissionSets::OrderDisplay,
Spree::PermissionSets::UserDisplay,
Spree::PermissionSets::ProductDisplay,
]
end

Now, if you assign the customer_service role to a user and sign into the backend with their credentials, you'll see that you only have limited access to user profiles, orders and products.

Custom permission sets

Let's take it one step further and assume that we also want customer service representatives to be able to update user profiles, perhaps in case a customer forgets their password and wants a reset.

Since there isn't a permission set that does what we need, we'll need to create our own:

app/models/awesome_store/permission_sets/user_update.rb
module AwesomeStore
module PermissionSets
class UserUpdate < PermissionSets::Base
def activate!
can :update, Spree::User
end
end
end
end

Now that we have our permission set, the last step is to associate it to the customer_service role by updating the role definition we created:

config/initializers/spree.rb
Spree.config do |config|
# ...
config.roles.assign_permissions :customer_service, [
Spree::PermissionSets::OrderDisplay,
Spree::PermissionSets::UserDisplay,
Spree::PermissionSets::ProductDisplay,
AwesomeStore::PermissionSets::UserUpdate,
]
end

That's it! Now, customer service representatives can also update user profiles.

Permission sets delegate a lot of their implementation to CanCanCan, which provides a very powerful API for defining which actions a user can and cannot perform. For a complete overview of what you can accomplish, refer to the CanCanCan documentation.