Bali
Bali is a powerful, framework-agnostic, thread-safe Ruby language authorization library. It is a universal authorization library, in the sense that it does not assume you to use specific Ruby library/gem/framework in order for successful use of this gem.
Bali is short for Bulwark Authorization Library.
Installation
It can be installed directly by using bundler's install:
$ gem install bali
Otherwise, if you are using a framework such as Rails, you can add this into your gemfile:
gem 'bali'
And then execute:
$ bundle
Usage
First things first: defining rules
Rule in Bali is the law determining whether a user (called subtarget) can do or perform specific operation on a target (which is your resource/model).
Bali.map_rules do
rules_for My::Transaction, as: :transaction do
describe(:supreme_user) { can_all }
describe :admin_user do
can_all
# a more specific rule would be executed even if can_all is present
can :cancel,
if: proc { |record| record.payment_channel == "CREDIT_CARD" &&
!record.is_settled? }
end
describe "general user", can: [:update, :edit], cant: [:delete]
describe "finance user" do
can :update, :delete, :edit
can :delete, if: proc { |record| record.is_settled? }
end # finance_user description
describe :guest { cant_all }
describe nil { cant_all }
end # rules_for
end
You may or may not assign an alias name (as). Make sure to keep it unique had you decided to give alias name to your rules group.
Can and Cannot testing
Say:
class My::Transaction
include Bali::Objector
attr_accessor :is_settled
attr_accessor :payment_channel
alias :is_settled? :is_settled
end
class My::Employee
include Bali::Objector
# working experience in the company
attr_accessor :exp_years
end
Assuming that there exist a variable transaction which is an instance of My::Transaction, we can query about whether the subtarget is granted to perform certain operation:
transaction.cant?(:general_user, :delete) # => true
transaction.can("general user", :update) # => true
transaction.can?(:finance_user, :delete) # depend on context
transaction.can?(:monitoring_user, :view) # => true
transaction.can?(:admin_user, :cancel) # depend on context
transaction.can?(:supreme_user, :cancel) # => true
transaction.can?(:guest, :view) # => false
transaction.can?(:undefined_subtarget, :see) # => false
transaction.cant?(:undefined_subtarget, :new) # => true
If a rule is depending on a certain context, then the context will be evaluated to determine whether the subtarget is authorized or not.
In the above example, deletion of transaction is only allowed if the subtarget is a "finance user" and, the transaction itself is already settled.
Also, asking can? on which the subtarget is not yet defined will always return false. In the example above, as undefined_subtarget is by itself has never been defined in describe under My::Transaction rule class, can? for undefined_subtarget will always return false. But, cant on simillar ocassion will return true.
Rule can also be tested on a class:
My::Transaction.can?(:supreme_user, :new) # => true
My::Transaction.can?(:guest, :view) # => false
My::Employee.can?(:undefined_subtarget, :new) # => false, rule class for this is by its own undefined
As we have never define the rules_for My::Employee before, any attempt to can? for My::Employee will return false, so does any attempt to object cant? on which will only return true for any given subtarget and operation.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/saveav/bali. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.
License
Bali is proudly available as open source under the terms of the MIT License.
Changelog
Version 1.0.0beta1
- Initial version
Version 1.0.0rc1
- Fix bug where user can't check on class
- Adding new clause: cant_all
Version 1.0.0rc2
- Fix bug when class's name, as a constant, is reloaded (re-allocated to different address in the memory)
- Allow describing rule for
nil, useful if user is not authenticated thus role is probablynil - Remove pry from development dependency
Version 1.0.0rc3
- Each target class should includes
Bali::Objector, for the following reasons:- Makes it clear that class do want to include the Bali::Objector
- Transparant, and thus less confusing as to where "can?" and "cant" come from
- When ruby re-parse the class's codes for any reasons, parser will be for sure include Bali::Objector
- Return
trueto anycan?for undefined target/subtarget alike - Return
falseto anycant?for undefined target/subtarget alike
Version 1.0.0
- Released the stable version of this gem