Kantox::Roles
Kantox Roles is the library to transparently handle an authorization. It is fully backend-agnostic. Current implementation contains a working example of pundit wrapping.
The main goal is to separate the wheat from the chaff and not to pollute model/controller classes with authorization stuff.
With Kantox Roles one is to define the rules in one or more yaml files:
'Kantox::Managed':
:yo : 'aspect'
:yo2 : :aspect
:yo3 :
:runner: 'Kantox::Strategies::Wrapper#wrap'
:yo5 :
:lambda: '->(context, im) { 42 }'
:yo6 :
:kantox_managedhandler:
:params: ['param1', 'param2']
'Kantox::WildManaged':
'y*' : :aspect
There are four different kinds of handlers available:
- simple — like
aspect
above. There should beKantox::Strategies#aspect
module function available. It will be called with parameterscontext
andim
supplying the context instance (usually an instance of guarded controller class) and the name of the guarded method inModule::Class#method
notation. The aforementioned function should raise an exception of typeKantox::Strategies::StrategyError
whether the authority check is not passed. - runner — the most powerful yet complicated guard. Should have the
executable module function as parameter in
Module::Class#method
notation. This function will be called, yieldingcontext
,im
and the guarded method, converted toproc
as parameters. The typical usage:
def my_runner context, params = nil
fail Kantox::Strategies::MyRunnerError unless check_passed
params[:user] = :demo if demo_mode
params[:credit_card].gsub /\d/, 'X'
yield *params if block_given?
end
As seen above, the full control over context is provided by this guard.
- lambda — the simple lambda, getting
context
andim
. Is actually a syntax sugar for the simple guard - object — used when there is a need to pass complicated parameters and/or
some other stuff to the guard. The class
Kantox::Managedhandler
must have a constructor, accepting one argument (the hash of parameters) andto_proc
method, accepting two arguments (context
,im
). The class will be instantiated with a parameters list andto_proc
would be called on context.
Methods to guard
Wildcard notation is allowed. That said, 'y*'
will guard all the methods
starting with y
, and '*'
will guard everything on the respective class.
Deny ⇒ Allow
As soon as a method guard is specified in yaml file, the method is considered as guarded. If there was an error finding the guard (class not exists, method can not be instantiated etc,) the authorization request will be rejected.
Rails integration
# controllers/admin/admin_controller.rb
require 'kantox/roles'
# Specify the top-parent class to guard
Kantox::Roles.init Admin::AdminController
# load strategies
Dir['strategies/**/*.yml'].each do |f|
Kantox::Roles.configure f
end
# load stopwords for logger
Kantox::Helpers.logger_stopwords File.join 'strategies', 'stopwords.txt'
Kantox::Helpers.info "Strategies were read: #{Kantox::Roles.}"
# config/application.rb
# Policies are to be loaded on init explicitly
Dir[File.('../../app/policies/**/*', __FILE__)].each do |f|
require f[/(.*?)\.rb$/, 1] unless File.directory? f
end
# strategiest/roles.yml
'Admin::TodosController' :
'*' : :pundit
...
Pundit plug-in
This section shows how to integrate pundit
to act as backend guard.
Everything one needs is to implement policies.
Everything in TodosController
is to be handled by pundit. So, it’s time
to implement our pundit
guard. Besides some syntax sugar stuff it is (complete implementation for kantox-flow
may be seen here:
# app/policies/pundit.rb
def pundit context, im
begin # Whoever does not reply on classify would be punished :)
model = context.instance_eval 'controller_path.classify'
policy = PolicyFactory.lookup model.split('::').last
unless policy.new(context.current_user, model).send("#{im.split('#').last}?")
fail PunditError.new(context, im, policy)
end
rescue NameError => e
Kantox::Helpers.err "Error punditing «#{context}». Will reject request.\nOriginal error: #{e}"
throw PunditError.new(context, im)
end
end
module_function :pundit
Needless to say, the above handler is to be written once per backend. Pundit one is shipped with Kantox::Roles
. The last but not least is to specify a strategy:
# app/policies/todo_policy.rb
module Kantox
module Policies
def historic?
@user.admin? and [true, false].sample
end
alias_method :index?, :historic?
end
end
end
The above will randomly accept admin’s requests to a controller. I am pretty sure one would do more sophisticated check here.
That’s it.
Policies Generator
Kantox::Roles
has it’s generator for creating policies.
Generator invocation
$ bundle exec rails generate kantox:pundit_policy Todo --users Administrator SalesPerson
The above will generate (assuming that Todo
is a proper model name and TodosController
exists:
- Policy itself, in
app/policies
, - Spec for a policy in
spec/policies
, - Default strategy in
strategies
. - [optional] if this is a first run, the
pundit:install
generator will be invoked to generate default punditApplicationPolicy
.
By default all the public controller methods will be guarded with method?
pundit guards,
returning true
if the current user was listed during generation process.
Generated specs
The generator above would generate smart specs, more or less ready to use. For an example above, it would generate specs, checking whether all the specified users are allowed to access the controller, and all others are restricted.
Vandal-proof
The generator will gracefully reject a request to damage anything as well as to generate policies for inexisting controller.
Installation
gem 'kantox-roles'
TODOs
- generate policies and rspecs by
yaml
- ~~pointcuts syntax — DSL vs YAML/JSON~~
- automatic tests for pointcuts — by adding them to descriptions
- allow⇒deny vs deny⇒allow
- ~~supersupervisor to change rights of supervisors~~
- ~~groups with roles, or smth morre sophisticated? What?~~
- ~~where to store rights? 3rd party?~~
- edit rights from the interface.
Usage
Add a line to configuration
file (or explicitly by calling Kantox::Roles.configure
with either hash, or passing a block.)
Optionally, one may specify a custom handler as denoted by :runner
key in config.
Development
After checking out the repo, run bin/setup
to install dependencies. Then, run bin/console
for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install
. To release a new version, update the version number in version.rb
, and then run bundle exec rake release
to create a git tag for the version, push git commits and tags, and push the .gem
file to rubygems.org.
Contributing
- Fork it ( https://github.com/[my-github-username]/kantox-roles/fork )
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create a new Pull Request