π Simple, declarative, role-based access control system for Rails and Ruby
This gem is a simple, declarative, role-based access control system for
Rails that works great with devise!
SimonSays can be installed via your Gemfile.
gem 'simon_says'
SimonSays consists of two parts:
First, we need to define some roles on a model. Roles are stored as an
integer and bitmasking
is used to determine the roles assigned for given record. SimonSays
provides a generator for creating a new migration for this required
attribute:
rails g model User # if and only if this model does not yet exist
rails g active_record:simon_says User
rails db:migrate
Now we can define some roles in our User model. For example:
class User < ActiveRecord::Base
include SimonSays::Roleable
has_roles :add, :edit, :delete
end
# > User.new.roles
# => []
# > u = User.create(roles: %i[add edit])
# => #<User ...>
# > u.roles
# => [:add, :edit]
# > u.has_add?
# => true
# > u.has_delete?
# => false
# > u.update roles: %i[delete add edit]
# > u.save # save record with roles_mask of 7
The attribute name can be customized by using the :as
option as seen
here in the Admin model:
class Admin < ActiveRecord::Base
include SimonSays::Roleable
has_roles :design, :support, :moderator, as: :access
end
# > Admin.new.access
# => []
# > Admin.new(access: :support).access
# => [:support]
Make sure to generate a migration using the correct attribute name if
:as
is used. For example:
rails g active_record:simon_says Admin access
We can also use has_roles
to define roles on a join through model
which is used to associate a User with a resource.
class Permission < ActiveRecord::Base
include SimonSays::Roleable
belongs_to :user
belongs_to :document
has_roles :download, :edit, :delete,
end
# > Permission.new(roles: Permission::ROLES).roles
# => [:download, :edit, :delete]
Roleable
also creates two scopes that can be used to find records that
have a given set roles. Using the default attribute name, the two scopes
generated would be with_roles
and with_all_roles
. Both methods
accept one or more role symbols as its arguments. The first scope,
with_roles
, will find any record with one or more the supplied roles.
The second scope, with_all_roles
will only find record that have all
of the supplied roles.
It is useful to note the various dynamically generated methods as well
the ROLES
constant, which is used in the Permission example. Take a
look at the Roleable
source code
to see how methods and scopes are dynamically generated with
has_roles
.
The Authorizer
concern provides several methods that can be used within
your controllers in a declarative manner.
Please note, certain assumptions are made with Authorizer
. Building
upon the above User and Admin model examples, Authorizer
would assume
there is a current_user
and current_admin
method. If these models
correspond to devise scopes this would be the case by default.
Additionally there would need to be an authenticate_user!
and
authenticate_admin!
methods, which devise provides as well.
Eventually, we would like to see better customization around the
authentication aspects. This library is intended to solve the problem of
authorization and access control. It is not an authentication library.
In general, the Authorizer
concern provides four core declarative methods
to be used in controllers. All of these methods accept the :only
and
:except
options which end up being used in a before_action
callback.
authenticate(scope, opts): Declarative convenience method to setup authenticate
before_action`find_resource(resource, opts)
: Declarative method to find a resourceauthorize_resource(resource, *roles)
: Authorize resource for givenfind_and_authorize(resource, *roles)
: Find a resource and then tryWhen find resources, the default_authorization_scope
is used. It can
be customized on a per-controller basis. For example:
class ApplicationController < ActionController::Base
include SimonSays::Authorizer
self.default_authorization_scope = :current_user
end
To authorize resources against a given role, we use either authorize
or find_and_authorize
. For example, consider this
DocumentsController
which uses an authenticated User
resource and a
Permission
through model:
class DocumentsController < ApplicationController
authenticate :user
find_and_authorize :document, :edit, through: :permissions, only: [:edit, :update]
find_and_authorize :document, :delete, through: :permissions, only: :destroy
end
This controller will find a Document resource and assign it to the
@document
instance variable. For the :edit
and :update
actions,
itβll require a permission with an :edit
role. For the :destroy
method, a permission with the :delete
role is required. Since the
:through
option is used, a @permission
instance variable will also
be created.
The find_resource
method may raise an ActiveRecord::RecordNotFound
exception. The authorize
method may raise a
SimonSays::Authorizer::Denied
exception if there is insufficient role
access. As a result, the find_and_authorize
method may raise either
exception.
We can also use a different authorization scope with the :from
option for find_resource
and find_and_authorize
. For example:
class ReportsController < ApplicationController
authorize_resource :admin, :support
find_resource :report, from: :current_admin, except: [:index, :new, :create]
end
Please refer to the
docs
for more information on the various declarative methods provided by the
Authorizer
.
git checkout -b my-new-feature
)git commit -am 'Add some feature'
)git push origin my-new-feature
)