Code Monkey home page Code Monkey logo

bali's Introduction

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

bali's People

Contributors

adamnoto avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.