# Regulator [![Build Status](https://travis-ci.org/coryodaniel/regulator.svg)](https://travis-ci.org/coryodaniel/regulator) [![Code Climate](https://codeclimate.com/github/coryodaniel/regulator/badges/gpa.svg)](https://codeclimate.com/github/coryodaniel/regulator) [![Test Coverage](https://codeclimate.com/github/coryodaniel/regulator/badges/coverage.svg)](https://codeclimate.com/github/coryodaniel/regulator/coverage) Regulator is a clone of the [Pundit](https://github.com/elabs/pundit) gem and provides a pundit compatible DSL that has **controller namespaced** authorization polices instead of *model namespaced*. It uses Ruby classes and object oriented design patterns to build a simple, robust and scaleable authorization system. Existing pundit policies can be used, although they will have to be namespaced properly, or have the controller accessing set ```Controller.policy_class``` or ```Controller.policy_namespace``` I built this because I believe authorization should be controller-based, not model based, but really enjoyed using the Pundit DSL and I was over [monkey-patching](https://gist.github.com/Systho/3d7632b5aa999cf88d87) pundit in all of my projects to make it work the way I want. Why not contribute to pundit? [It's](https://github.com/elabs/pundit/issues/12) [been](https://github.com/elabs/pundit/issues/178) an [on going](https://github.com/elabs/pundit/search?q=namespace&type=Issues&utf8=%E2%9C%93) 'issue' in pundit and it doesn't look [like it'll be reality.](https://github.com/elabs/pundit/pull/190#issuecomment-53052356) ## Installation Add this line to your application's Gemfile: ```ruby gem 'regulator' ``` And then execute: $ bundle Or install it yourself as: $ gem install regulator Include Regulator in your application controller: ``` ruby class ApplicationController < ActionController::Base include Regulator protect_from_forgery end ``` ## Generators Install regulator ``` sh rails g regulator:install ``` Create a new policy and policy test/spec ``` sh rails g regulator:policy User ``` Regulator comes with a generator for creating an ActiveAdmin adapter ``` sh rails g regulator:activeadmin ``` This will create an adapter in your ```lib``` folder. Be sure to set the following in your ActiveAdmin initializer: ```ruby config.authorization_adapter = "ActiveAdmin::RegulatorAdapter" # Optional # Sets a scope for all ActiveAdmin polices to exist in # # Example # app/policies/admin_policies/user_policy.rb #=> AdminPolicies::UserPolicy # # config.regulator_policy_namespace = "AdminPolicies" config.regulator_policy_namespace = nil # Optional # Sets the default policy to use if no policy is found # # config.regulator_default_policy = BlackListPolicy config.regulator_default_policy = nil ``` ## Policies Regulator is focused around the notion of policy classes. We suggest that you put these classes in `app/policies`. This is a simple example that allows updating a post if the user is an admin, or if the post is unpublished: ``` ruby class PostPolicy attr_reader :user, :post def initialize(user, post) @user = user @post = post end def update? user.admin? or not post.published? end end ``` Regulator makes the following assumptions about this class: - The class has the name `Scope` and is nested under the policy class. - The first argument is a user. In your controller, Regulator will call the `current_user` method to retrieve what to send into this argument. - The second argument is a scope of some kind on which to perform some kind of query. It will usually be an ActiveRecord class or a `ActiveRecord::Relation`, but it could be something else entirely. - Instances of this class respond to the method `resolve`, which should return some kind of result which can be iterated over. For ActiveRecord classes, this would usually be an `ActiveRecord::Relation`. You'll probably want to inherit from the application policy scope generated by the generator, or create your own base class to inherit from: ``` ruby class PostPolicy < ApplicationPolicy class Scope < Scope def resolve if user.admin? scope.all else scope.where(:published => true) end end end def update? user.admin? or not post.published? end end ``` You can now use this class from your controller via the `policy_scope` method: ``` ruby def index @posts = policy_scope(Post) end ``` Just as with your policy, this will automatically infer that you want to use the `PostPolicy::Scope` class, it will instantiate this class and call `resolve` on the instance. In this case it is a shortcut for doing: ``` ruby def index @posts = PostPolicy::Scope.new(current_user, Post).resolve end ``` You can, and are encouraged to, use this method in views: ``` erb <% policy_scope(@user.posts).each do |post| %>
<%= link_to post.title, post_path(post) %>
<% end %> ``` ## Manually specifying policy classes Sometimes you might want to explicitly declare which policy to use for a given class, instead of letting Regulator infer it. This can be done like so: Regulator supports the Pundit-style model "policy_class", but also implements it at the controller level. You can also set a controller's policy_namespace if you want to use an alternate namespace to the one the controller is in. ``` ruby # Model level class Post def self.policy_class PostablePolicy end end ``` ``` ruby # Controller level class Api::Post # By default, Regulator will look for Api::PostPolicy def self.policy_class PostPolicy end end ``` ``` ruby # Here the admin namespace could be told to use the same policy as the API namespace class Admin::Post # By default, Regulator will look for Admin::PostPolicy def self.policy_class PostPolicy end # You can also set it at the instance level def policy_class if current_user.is_a_high_paying_member? HighClassPostPolicy else LowClassPostPolicy end end end ``` ``` ruby class Admin::Comment def self.policy_namespace # Will make regulator look for ActiveAdmin::CommentPolicy instead of # Admin::CommentPolicy ActiveAdmin end end ``` Of course ```policy_namespace``` and ```policy_class``` can be used together. ## Policy Namespaces This table explains what policies Regulator will look for in different scenarios: | Controller Name | Model Name | Expected Policy | | -------------------------------------------------------|------------------| -------------------------------| | AlbumController | Album | AlbumPolicy | | Api::AlbumController | Album | Api::AlbumPolicy | | Admin::AlbumController | Album | Admin::AlbumPolicy | | Admin::AlbumController.policy_namespace = 'SuperUser' | Album | SuperUser::AlbumPolicy | | Admin::AlbumController.policy_namespace = nil | Album | AlbumPolicy | | Admin::AlbumContoller | MySongGem::Album | Admin::MySongGem::AlbumPolicy | | SongController#policy_class = TrackPolicy | Song | TrackPolicy | | SongController.policy_class = Legacy::TrackPolicy | Song | Legacy::TrackPolicy | ```policy_class``` at the controller-level is king. Setting it will override all logic for determining the policy to use. ## ActiveAdmin Auth Adapter There is a generator and an included adapter. Using the generator will place a more complex customizable adapter in your ```lib``` directory. A simple adapter is also provided, to use add the following to your active_admin initializer: ``` ruby ActiveAdmin::Dependency.regulator! require 'regulator' require 'regulator/active_admin_adapter' ActiveAdmin.setup do |config| config.authorization_adapter = "Regulator::ActiveAdminAdapter" ... ``` ## Development After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake rspec` to run the tests. You can also 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`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). ## License The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). ## Contributors * [Cory O'Daniel](http://linkedin.com/in/coryodaniel) * All the hard work done on [Pundit](https://github.com/elabs/pundit) Thanks to Warren G for the inspiration, bro. ![Regulator](https://upload.wikimedia.org/wikipedia/commons/a/ac/Nat_Powers_%26_Warren_G.jpg) ## TODOs * [ ] documentation * [ ] yard doc * [ ] Lotus examples * [ ] Grape examples * [ ] ROM examples * [ ] Custom permissions examples * [ ] RoleModel gem examples * [ ] rolify gem examples * [ ] contributing wiki