Project

skill_tree

0.0
No commit activity in last 3 years
No release in over 3 years
A simple and complete ACL system for Rails, that allows to specify a role for an user for a single resource.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

Runtime

< 5, ~> 4
< 5, ~> 4
< 5, ~> 4
 Project Readme

Build Status Code Climate Test Coverage

Role Play

A resource-based ACL system for Rails.

Getting started

SkillTree is tested on Rails 4.1+, but should run also on previous versions. You can add it to your Gemfile with:

gem 'skill_tree'

Run the bundle command to install it.

After you install SkillTree and add it to your Gemfile, you need to run the generator:

rails generate skill_tree:install

And finally run:

rake db:migrate

To run new migrations.

Configuration

SkillTree requires at least two models, a Subject and a Resource. The Subject is who could have a role, a Resource is who has an acl.

For example we could have a User and a Post

Let's setup them:

class User < ActiveRecord::Base
  as_skill_tree_subject
  ...
end

class Post < ActiveRecord::Base
  as_skill_tree_resource
  ...
end

You can also define a default admin role for that post. Example:

class Post < ActiveRecord::Base
  belongs_to :user
  as_skill_tree_resource admin: :user
  ...
end

Now, let's define an acl and a list of role for a post.

Open config/acl.rb:

acl :posts_acl, default_for: :posts do |a|
  a.role :guest do |r|
    r.can :read
  end

  a.role :user do |r|
    r.can :create, :read
  end

  a.role :editor do |r|
    r.inherit :user
    r.can :write
  end

  a.role :admin do |r|
    r.inherit :editor
    r.can :update, :delete
  end
end

Let's take a look: :guest role is a special one: it's the default role for users with no-user (example: users not logged in).

:user is the default logged-user role.

Then we have an :editor and an :admin.

default_for means that all posts will have this acl set as default one.

In fact you can have multiple acl for a single resource type. For example a private post could have an acl like this:

acl :private_post do |a|
  a.role :guest do |r|
  end

  a.role :user do |r|
    r.can :create, :read
  end

  a.role :editor do |r|
    r.inherit :user
    r.can :read, :write
  end

  a.role :admin do |r|
    r.inherit :editor
    r.can :update, :delete
  end
end

And you can change it:

post = Post.first
post.acl = Acl.find_by_name('private_post')
post.save!

As default behaviour, the acl are updated each time you startup your application.

To avoid this, you can add a version number to your acl, so it'll be updated only where the version number changes.

acl :post, version: 1 do |a|
  ...
end

acl :post2, version: 3 do |a|
  ...
end

Model methods

Now, let's dig into methods:

user = User.first
resource = Post.first

Set a role to an user:

user.role! :editor, resource

Check permissions:

user.can? :write, resource # => true
user.can? :update, resource # => false

Remove roles:

user.unrole! :editor, resource
user.can? :write, resource # => false
user.can? :read, resource # => true (fallback to :user role)

Check roles:

user.role! :editor, resource
user.role? :editor, resource # => true
user.role? :admin, resource # => false

List all posts given a permission

Post.where_user_can(user, :read)

Change a resource acl

resource.acl! :private_post

Controller methods

First of all, let's include SkillTree in ApplicationController

  include SkillTree::Controller

As a default behaviour, SkillTree negates access to any action of any controller (security by default).

You can use the allow method to decide which action allow to be accessed.

Here's an example:

class MyController < ApplicationController
  allow(:index, :show) { true } # Everyone can access to index and key
  allow(:create) { current_user } # Only logged user can create
  allow(:update) { can?(:update, resource) } # Only logged user can create
  allow(:destroy) { can?(:destroy, resource) } # Only logged user can create

  def index; end
  def show; end
  def create; end
  def update; end
  def destroy; end

  private

  def resource
    @resource ||= Post.find(params[:id])
  end
end

You can use the keyword :all to specify the default behaviour for all actions.

class MyController < ApplicationController
  allow(:all) { !current_user.nil? }

  ...
end

Authorization errors handling

When an allow(...) {} block returns false, SkillTree::NotAuthorizedError is raised.

You can capture it in your application controller and define a custom behaviour.

class ApplicationController < ActionController::Base
  rescue_from SkillTree::NotAuthorizedError do
    raise ActionController::RoutingError, 'not found' # This will raise a 404 error
  end
end

##Contributing

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request

##License See LICENSE