RBAC framework for model-level authorization gem with very granular control on permissions, using Capabilities, Roles and UserGroups.
Checkout our minimal example app to try it out without the need to set it up yourself.
Only works with Rails 4 and 5.
Add this line to your application's Gemfile:
gem 'strongbolt'
And then execute:
$ bundle
To creates the required migrations, run:
$ rails g strongbolt:install && rake db:migrate
To create a Role and User Group that has full access and assign the group to all users (this allow to get started using StrongBolt), run:
$ rake strongbolt:seed
If you plan on using the built-in view to manage user groups, roles and permissions, also add to your application.js:
//= require strongbolt
You will need to have jQuery or a similar library with Ajax to make it work
There is a minimal example app using Strongbolt made available here. If you experience any issues setting Strongbolt up or configuring it, this example app is already setup for you and might be able to resolve some issues.
The initializer of strongbolt, config/initializers/strongbolt.rb
has some included documentation to help you with configuring the little you need to for Strongbolt to work.
By default, the user class is User
and there is no tenant set.
Strongbolt has been made to be used with the least configuration possible. If given a tenant, it will traverse the graph of dependencies on this tenant to try to configure all the tenant dependent models authorization check ups. However, some models may be totally independant or not belong to (directly or indirectly) to a tenant. You may also have no tenant at all. In these cases, some or all of your models won't be discovered by Strongbolt when running your app. To avoid eager loading the whole application to automatically get the list of models, you can specify in the initializer the models of your application. This list is prefilled when running the install generator.
Strongbolt perform high level authorization on controllers, to avoid testing more granular authorization and increase the performance. For instance, if an user cannot find any Movies, he certainly won't be able to find the movie with the specific id 5.
Strongbolt relies on the usual Rails restful actions to guess the corresponding model action (edit requires update authorization, new requires create authorization, etc.). However, you will sometimes create other custom actions. In that case, you must specify in the controller how to map these custom controller actions to one of the 4 model actions using:
authorize_as_find :action1, :action2
authorize_as_create :action, :action2
authorize_as_update :action, :action2
authorize_as_destroy :action, :action2
You can disable the controller-level authorization checks by using in the controllers:
skip_controller_authorization,
skip_controller_authorization, only: [:index]
skip_controller_authorization, except: [:update]
You can also specify a list of controllers in the initializer config/initializers/strongbolt.rb
. It is useful for third-party controllers, like devise for instance. The syntax is:
config.skip_controller_authorization_for "Devise::Sessions", "Devise::Registrations"
You can also skip ALL authorization checks (BAD IDEA) using:
skip_all_authorization
skip_all_authorization, only: [:index]
skip_all_authorization, except: [:update]
Sometimes you may want to render the views without checking again every single instance you're displaying (for the list of movies for instance, if the user can find ALL movies no need to test separatly each of them). You can skip the authorization checks when rendering pages by using:
render_without_authorization :index, :show
Be careful when using one of this skipping authorization check as it may result in leaked data.
Usually most of your controllers, in a RestFUL design, are backed by a specific model, derived from the name of the controller. In that case Strongbolt will know what model authorization it should test against. Otherwise, it will raise an error unless you specify the model for authorization:
self.model_for_authorization = "Movie"
Usually you will never need to configure anything on the models. In some cases though, to simplify the roles and permissions, you may want to authorize some models as if they were other models (in nested models, sometimes it is safe to assume that the lower level model should have the same permissions than its parent)> To achieve this, use the following within your model:
authorize_as "Movie"
All ActiveRecord models will get the following methods added by Strongbolt.
Returns true if authorization checks are enabled.
Returns true if authorization checks are disabled.
Returns true when the model is owned by user (has a belongs_to association with the user class). Example: Client.owned?
The attribute of the model which is used to determine the owner. Example: Client.owner_attribute
would return :user_id
, User.owner_attribute
would :id
.
Returns true when the model is a tenant (see below for a description of tenants).
See above. Allows a different model to be used for authorization checks instead.
See above. Returns the name of the model to be used for authorization checks.
Your user class (configured in the initializer via config.user_class
) will have the following methods added by Strongbolt.
Returns all capabilities assigned to the user, including inherited ones. The return type is an array of Strongbolt::Capability
instances. Example: user.capabilities
Give the user access to the given tenant (see below for a description of tenants). Example: user.add_tenant Client.find_by(name: 'AMG')
Checks whether the user own the given instance and returns a boolean. instance
can be any instance of an ActiveRecord model. If the model has an attribute user_id
it is compared to the user's ID to decide if he owns it. A user owns his own user instance. Example: user.owns? Client.find_by(name: 'AMG')
, user.owns? user
Return true/false and determines whether the user is authorized to perform action
on instance
. action
has to be a symbol (:find, :create, :update, :destroy
). instance
has to be a class or instance of an ActiveRecord model. attrs
has to be :any
always for now (it could be used for attribute level authorization, but that's not supported yet). all_instance
can be set to true when instance
is a call, to check whether the user can perform action
on all instances of that class.
Examples:
user.can?(:find, Client.find_by(name: 'AMG'))
user.can?(:create, Plan)
user.can?(:update, Client, :any, true)
Inverse of user.can?(...)
ActiveRecord has_may Association with Strongbolt::UserGroup
ActiveRecord has_may Association with Strongbolt::Role
through Strongbolt::UserGroup
ActiveRecord has_may Association with the tenant model. The name is the pluralized version of the tenant model name. Example: User.clients
Method returning all tenants of the type the user has access to. Example: User.accessible_clients
Strongbolt allows the utilization of tenants. Tenants are vertical scopes within your application.
Let's take an example. A project tracking application (like Pivotal) can have several companies as clients, but each company's users can see only what concern them. Company is a tenant of the application.
The initializer let you define the model(s) that should be considered tenants.
Strongbolt comes with a table, strongbolt_users_tenants
, that will store what tenants users have access to.
When a tenant is declared, it will add some features to the User class that has been defined in the initializer.
First, an association between the User class and the Tenant class will be created, named after the Tenant class name. It is a has_many :trough => :users_tenants_
association. You can grant or revoke access to tenants just by interacting with that association.
For instance, a
Company
tenant will generate acompanies
association.
To grant access to
companyA
to the usermyUser
, you just add it to the associationmyUser.companies << companyA
. To revoke access to all companies the user might have, you would usemyUser.companies.clear
.
A convenient instance method will also be created on the User class to directly access the list of Tenant class a User can access. It is name accessible_{tenants}
where {tenants}
is the pluralize version of the Tenant class name.
Company
will create anaccessible_companies
instance method
A tenanted model is a model that belongs indirectly to a Tenant class.
For instance,
Project
is a tenanted model ofCompany
. Now, let's consider abelong_to
association that linksProject
to aCountry
. The list of countries is stored in the tablecountries
and does not belong toCompany
:Country
is not a tenanted model.
Strongbolt will traverse your schema and automatically determine what models in your application is linked to your Tenant class.
In our example, where
Project
belongs toCompany
andCountry
, butCountry
does not belong toCompany
, Strongbolt will automatically determine thatProject
is a tenanted model andCountry
is not.
Strongbolt will then create a has_one
association on every tenanted model, so you can access directly the dependent Tenant
For instance, every
Task
of aProject
will have ahas_one :company, :through => :project
association automatically created (if not already existing).
Strongbolt's capabilites have a boolean attribute, require_tenant_access
, that specify whether the user can access all tenanted models or only the ones that belong to the Tenants he has access to.
Let's look back at the example. Each company has several projects. The normal user, belonging to a company, would only have access to his companies projects. You would then define a capability requiring tenant access for the normal user.
An admin user, on the other hand, like an engineer of the application, could have access to all the companies' projects. An engineer's projects' permissions would then not require tenant access
It will then perform the right permission check based on this requirement and the relationship of the model being checked and the Tenant class. If the model being checked is not linked to any of the Tenant classes, it won't check any dependency.
This means Strongbolt is trying to perform some controller-level authorizations but cannot infer the model from the controller name. In that case you must use in this controller:
self.model_for_authorization = "Movie"
Or skip controller authorizations when it cannot be related to a model or it is not useful (like for Devise controllers), using either one of the methods described in Skipping Authorization.
This happens when a controller is using a custom action which is not one of the 6 usual restful Rails actions (index, new, create, sho, edit, update, destroy). Refer to Custom controller actions to fix this.
- Fork it ( https://github.com//strongbolt/fork )
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create new Pull Request