Project

bali

0.0
No commit activity in last 3 years
No release in over 3 years
Bali (Bulwark Authorization Library) is a to-the-point authorization library for Rails.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

Runtime

~> 2, >= 2.2
 Project Readme

Bali

Build Status Maintainability

Bali is a to-the-point authorization library for Rails. Bali is short for Bulwark Authorization Library.

Why I created Bali?

  • Defining authorization rules are complicated, I want to make it natural and so much simper like Ruby
  • I want to easily segment authorization rules per roles
  • I don't want to marry rules with controllers' actions
  • I want an intuitive DSL
  • I want to print those rules if I want, to see who can do what
  • On top of that, it integrates well with Rails (also, RSpec)

Internally, Bali is quite complicated, but externally, Bali should be very easy and intuitive to use.

Supported versions

  • Ruby 2.4.4 until Ruby 2.7 (trunk)
  • Rails 5.0, Rails 6.0, until Rails edge (master)

Installation

Add this into your gemfile:

gem 'bali'

And then execute:

$ bundle

To generate a rule class, for example for a model named User:

$ bundle rails g rules user

We can suplant User with something else

Usage

In a nutshell, authorization rules are to be defined in a class extending Bali::Rules (located in app/rules). We use can? and cant? to check against those rules we define using can, cant, can_all, and cant_all. Unscoped rules are inherited, otherwise we can scope rules by defining them within a role block.

Given a model as follows:

# == Schema Information
#
# Table name: transactions
#
#  id               :bigint           not null, primary key
#  is_settled       :boolean          not null
class Transaction < ApplicationRecord
  alias :settled? :is_settled
end

And given the TransactionRules defined as follows:

class TransactionRules < Bali::Rules
  can :update, :unsettle
  can :print

  # overwrites :unsettle
  can :unsettle do |record, current_user|
    record.settled?
  end

  # will inherit update, print
  role :supervisor, :accountant do
    # will always be able to unsettle
    can :unsettle
  end

  role :accountant do
    # must not be able to perform an update operation
    cant :update
  end

  role :supervisor do
    can :comment
  end

  role :clerk do
    cant_all
    can :unsettle
  end

  role :admin do
    can_all
  end
end

We can do authorization in this way:

transaction = Transaction.new
TransactionRules.can?(current_user, :update, transaction)
TransactionRules.cant?(current_user, :update, transaction)
TransactionRules.can?(:archive, transaction)
TransactionRules.can?(:accept_new_transaction)

Inside a controller or a view; we can also do:

if can? current_user, :update, transaction
  # snip snip
end

Bali can automatically detect the rule class to use for such a query. That way, we don't have to manually spell out TransactionRules when it is clear that the transaction is a Transaction.

We may also omit current_user to make the call shorter and more concise:

if can? :update, transaction
  # snip snip
end

For more coding examples, please take a look at the written test files. Otherwise, if you may encounter some unclear points, please feel free to suggest for edits. Thank you.

Testing through RSpec

Bali is integrated into RSpec pretty well. There's a be_able_to matcher that we can use to test the rule:

let(:transaction) { Transaction.new }
let(:accountant) { User.new(:accountant) }

# expectation on an instance of a class
it "allows accountant to print, but not update, transaction" do
  expect(accountant).to be_able_to :print, transaction
  expect(accountant).not_to be_able_to :update, transaction
end

# expectation on a class
it "allows User to sign in" do
  expect(User).to be_able_to :sign_in
end

Scoping data to role

Some user can access all data, and some can only access to data belonging to them. This can be achieved by using a scope block.

An example of defining a scope block for the TransactionRules.

class TransactionRules < Bali::Rules
  scope do |data, current_user|
    unless current_user.role == "admin"
      data.where(user_id: current_user.id)
    end
  end

  # ...
end

We can use rule_scope to execute the scope for a given data:

transactions = TransactionRules.rule_scope(Transaction.all, current_user)

Inside a controller/view, we may also omit current_user to make the call concise.

transactions = rule_scope(Transaction.all)

It is important to note that a scope block must not be defined within a role block.

Usage outside of Rails

This authorization library might be used outside of Rails, such as with Grape or Sinatra projects. However, although it should work as expected, such integration is not natively supported by Bali itself. Hence, additional works for such integration is needed.

If ActiveRecord is not used, we need to add the following code into a user class.

class User
  extend Bali::Statics::Record
  extract_roles_from :roles
end

On the controller, we should add these helper modules:

include Bali::Statics::ScopeRuler
include Bali::Statics::Authorizer

Doing so allow us to use the can? and other functions on the controller. Bali should also be able to deduce the user's roles properly.

Printing defined roles

puts Bali::Printer.printable

Or execute:

$ rails bali:print_rules

Will print, for example, this definition:

===== Transaction =====

      By default
      --------------------------------------------------------------------------------
        1. By default can update
        2. By default can unsettle, with condition
        3. By default can print
      Supervisor
      --------------------------------------------------------------------------------
        1. Supervisor can unsettle
        2. Supervisor can comment
      Accountant
      --------------------------------------------------------------------------------
        1. Accountant can unsettle
        2. Accountant cant update
      Clerk
      --------------------------------------------------------------------------------
        1. Clerk can unsettle
      Admin
      --------------------------------------------------------------------------------
        1. Admin can do anything except if explicitly stated otherwise


===== User =====

      By default
      --------------------------------------------------------------------------------
        1. By default can see_timeline, with condition
        2. By default can sign_in, with condition


Printed at 2020-01-01 12:34AM +00:00

License

Bali is available as open source under the terms of the MIT License.

Changelog

Please refer to CHANGELOG.md to see it