-
Notifications
You must be signed in to change notification settings - Fork 45
Quickstart
To setup an app with CanTango you must complete the following steps:
- Install and Configure CanTango
- Create and register a User model
- Setup Authentication
- Optionally
- Configure use of Roles
- Define Guest user logic
- Create Permits
- Define your Authorization logic in Views and Controllers
- Optionally: Extract static Permit rules to Permission store
- Create a CanTango initializer
rails g cantango:install
Generates an initializer in config/initializers/cantango.rb
- Turn on the engines you want to use in the CanTango initializer
initializers/cantango.rb
(see Configuration) - It's a good idea also to turn on debugging mode so you can follow the execution flow and determine any problems that might occur due to configuration errors.
- You should also limit the types of permits used (permits.disable)
- You might also want to shut off the cache and permission engine until you have your permits working.
CanTango.configure do |config|
config.debug.set :on
config.permits.disable :account, :special, :role_group
config.engines.all :on
# config.engine(:permission).set :off
# config.engine(:cache).set :off
end
First you must have a User model. Use the tango_user
macro in order to register a user class with CanTango. CanTango will then generate User APIs methods such as #user_can? for the User class, admin_can?
for a registered Admin
user class etc.
class User
# register as a "user class" for CanTango
tango_user
end
For each registered user class, you should ensure that you have a #current_[user class]
method available for views and controllers. Devise generates such methods for each Devise model. For other authentication systems you might have to implement your own #current_[user class]
methods.
Note: Some authentication systems (such as authlogic) only have a single #current_user method. If this is the case and you have multiple user classes, you will have to do some customization to have the extra #current_[user class]
methods available.
Here is a very simplistic example of some authentication setup:
class ApplicationController
# user_can? and cannot? methods require that the following method is available and
# returns the current logged in user or nil if no user is logged in for this session
def current_user
session[:current_user]
end
end
This method logic assumes that you have already have a login form (or similar/other authentication functionality) which populates session[:current_user]
with a user object (or perhaps retrieving via the id stored in the session?).
If you plan to allow users to have dynamic roles, you can easily integrate a role system. Otherwise you can have the User class act as a static role for the user objects.
If you use dynamic roles, you must ensure that a #roles_list
method (which returns a list of symbols for each role) is available on any user class. Alternatively you can make a #has_role?(role)
method available.
class User
tango_user
# returns/calculates list of symbols, where each symbol is a name of a role the user currently has
def roles_list
[:admin, :editor, :publisher]
end
# optional
def has_role? role
roles_list.include? role.to_sym
end
end
You can also combine static and dynamic roles for more advanced cases! Simply define a permit both for the roles and the user class (see Permits for details).
It can be useful to have your app always fallback to use a Guest user or Guest account. Otherwise you would have to implement specific logic to handle when there is no user (null), which tends to get ugly and messy. For details see Guest users and accounts
Example:
class User < ActiveRecord::Base
# returns Guest user instance
def self.guest
session[:guest_user] ||= Guest.new # create Guest model (usually not to be persisted!)
end
end
This requires that you have a Guest
model. In most cases you don't want to persist the Guest user (save to data store), hence it is recommended to have a separate Guest model, also to ensure you have the Guest logic in its own container. You can also define the logic to create a Guest user directly in the CanTango Configuration.
Note that CanTango also generates methods in the form of #session_[user_class]
, fx session_user
which returns the current_user
or if no user is logged in, falls back to retrieve the Guest user. This is very useful in order to always ensure you have a user object at hand. This allows a Guests to change language etc. and have this state stored on the Guest user instead of doing custom 'null object' case logic and store this data directly in the session, an anti-pattern!
You should normally have a "hook" when a user logs in, which clears the session for the Guest user (unless you want to reuse that guest user in case the user logs out again).
In order to have the Guest
model simulate a normal Rails model, without being persisted, you can include and extend specific ActiveModel
modules. Here an example:
class Guest
extend ActiveModel::Naming
include ActiveModel::Conversion
include ActiveModel::Validations
def initialize
# basic config of guest user!
end
def create options = {}
# new instance and set valid attributes?
end
def has_role? role
role.to_sym == :guest
end
def persisted?
false
end
end
It is recommended that you start off by using Permits to define your ability logic. Permits are more flexible and easier to debug. You can then later extract your static Permit rules into a Permissions store.
Permits are classes stored in app/permits
(see Permits.
You can use the Permit Generators to help generate various kinds of permits.
Example use of permit generators:
rails g cantango:user_permit User --read all --edit Comment
rails g cantango:user_permit Guest --read all --not_write Article
rails g cantango:role_permit Admin --manage all
Example User type permit:
class UserPermit < CanTango::UserPermit
def initialize ability
super
end
protected
def permit_rules
can :read, Article
cannot :write, Article
can :manage, [Post, Comment]
end
end
This permit would apply for any user that is an instance of the User model (class).
Example role permit:
class GuestRole < CanTango::RolePermit
# ...
def permit_rules
can :read, :all
end
end
This permit would apply for any user that has a role :guest
from calling the #roles_list
method.
The User APIs and User Account APIs are available in your Views and Controllers. Start out simple, in order to test that everything is configured and works correctly. CanTango comes with some convenient debugging helper methods to help you determine if your permits work as expected. Note: You can also debug via your specs or test suite.
Now lets scaffold an Article model, with a REST controller and views:
rails g scaffold Article title:string description:text
class ArticlesController < ApplicationController
# for simple demo simulate a user is logged in!
def index
# remove the line below to have no current user and thus simulate a guest (visitor)
session[:current_user] = User.first
# print permits that were registered correctly
CanTango.debug_permits_registry
# print all permits that allow/deny that user to perform that action/ability
CanTango.debug_ability(current_user, :read, Article)
end
Example view with CanTango Access Control logic in place:
# views/articles/index.html.erb
<%= Article.all.inspect if user_can?(:read, Article) %>
<%= link_to "Edit", edit_article_path(Article.first) if user_can?(:edit, Article) %>
After you have ensured that your Permits work, you can convert your static Permit rules to permissions stored in a Permission store. Try out the default a yaml store: config/permissions.yml
. (See Permissions)
The yaml permission store file should already be available if you used the cantango:install
generator without any arguments.
Advanced ability rules that require dynamic logic must be defined using a block construct. Dynamic rules can thus only be defined as ruby code and hence only using Permit classes.
Example of dynamic rule:
def permit_rules
can :edit, Project |project|
project.manager == user && project.published?
end
end
This kind of dynamic rule can NOT be stored in a permissions file! When you extract static Permit ability rules, do it in small steps, and make sure that your ability logic still works as expected after each step!
Example permission store:
# config/permissions.yml
roles:
guest:
can:
read: Article
user_types:
user:
can:
read: Article
manage: [Post, Comment]
cannot:
write: Article
To test the permissions, you can now disable the UserPermit like this.
class UserPermit < CanTango::UserPermit
def initialize ability
super
disable!
end
end
You can also disable permits from the cantango initializer like this:
config.permits.disable_for :user, [:user, :admin]
This disables the User type permits UserPermit and AdminPermit.
If you manage to migrate all your Permits to a Permission store you should disable the Permits engine completely:
config.engine(:permit).set :off
or config.permit_engine.set :off
If your ability logic works without the permit engine enabled you can then delete your permit classes.
Next, go to Quickstart CanTango with Devise to see how to adjust this example for use with the Devise authentication system. The next quickstart also demonstrates other more advanced use cases.