Giter Site home page Giter Site logo

nathanl / authority Goto Github PK

View Code? Open in Web Editor NEW
1.2K 29.0 67.0 790 KB

*CURRENTLY UNMAINTAINED*. Authority helps you authorize actions in your Rails app. It's ORM-neutral and has very little fancy syntax; just group your models under one or more Authorizer classes and write plain Ruby methods on them.

License: MIT License

Ruby 99.69% HTML 0.31%
ruby authorization

authority's Introduction

Authority (CURRENTLY UNMAINTAINED)

Authority is now unmaintained. Users who have installed it decided to trust me, and I'm not comfortable transferring that trust to someone else on their behalf. However, if you'd like to fork it, feel free.

Gary Foster has provided a script to migrate to Pundit.

Overview

Authority helps you authorize actions in your Ruby app. It's ORM-neutral and has very little fancy syntax; just group your models under one or more Authorizer classes and write plain Ruby methods on them.

Authority will work fine with a standalone app or a single sign-on system. You can check roles in a database or permissions in a YAML file. It doesn't care! What it does do is give you an easy way to organize your logic and handle unauthorized actions.

If you're using it with Rails controllers, it requires that you already have some kind of user object in your application, accessible via a method like current_user (configurable).

Gem Version Build Status Code Climate Dependency Status Join the chat at https://gitter.im/nathanl/authority

Contents

## Overview

Using Authority, you have:

  • Broad, class-level rules. Examples:
    • "Basic users cannot delete any Widget."
    • "Only admin users can create Offices."
  • Fine-grained, instance-level rules. Examples:
    • "Management users can only edit schedules with date ranges in the future."
    • "Users can't create playlists more than 20 songs long unless they've paid."
  • A clear syntax for permissions-based views. Examples:
    • link_to 'Edit Widget', edit_widget_path(@widget) if current_user.can_update?(@widget)
    • link_to 'Keelhaul Scallywag', keelhaul_scallywag_path(@scallywag) if current_user.can_keelhaul?(@scallywag)
  • Graceful handling of access violations: by default, it displays a "you can't do that" screen and logs the violation.
  • Minimal effort and mess.

Most importantly, you have total flexibility: Authority does not constrain you into using a particular scheme of roles and/or permissions.

Authority lets you control access based on:

  • Roles in your app's database (rolify makes this easy)
  • Roles in a separate, single-sign-on app
  • Users' points (like StackOverflow)
  • Time and date
  • Weather, stock prices, vowels in the user's name, or anything else you can check with Ruby

All you have to do is define the methods you need on your authorizers. You have all the flexibility of normal Ruby classes.

You make the rules; Authority enforces them.

## The flow of Authority

Authority encapsulates all authorization logic in Authorizer classes. Want to do something with a model? Ask its authorizer.

You can specify a model's authorizer one of two ways:

  • specify the class itself: authorizer = SomeAuthorizer
  • specify the class's name: authorizer_name = 'SomeAuthorizer' (useful if the constant isn't yet loaded)

If you don't specify an authorizer, the model will:

  • Look for an authorizer with its name. Eg, Comment will look for CommentAuthorizer.
  • If that's not found, it will use ApplicationAuthorizer.

Models that have the same authorization rules should use the same authorizer. In other words, if you would write the exact same methods on two models to determine who can create them, who can edit them, etc, then they should use the same authorizer.

Some example groupings:

     Simplest case                Logical groups                                 Most granular

  ApplicationAuthorizer        ApplicationAuthorizer                         ApplicationAuthorizer
           +                             +                                             +
           |                    +--------+-------+                 +-------------------+-------------------+
           |                    +                +                 +                   +                   +
           |             BasicAuthorizer   AdminAuthorizer  CommentAuthorizer  ArticleAuthorizer  EditionAuthorizer
           |                    +                +                 +                   +                   +
   +-------+-------+            +-+       +------+                 |                   |                   |
   +       +       +              +       +      +                 +                   +                   +
Comment Article Edition        Comment Article Edition          Comment             Article             Edition

The authorization process generally flows like this:

               current_user.can_create?(Article)                 # You ask this question, and the user
                           +                                     # automatically asks the model...
                           |
                           v
             Article.creatable_by?(current_user)                 # The model automatically asks
                           +                                     # its authorizer...
                           |
                           v
           AdminAuthorizer.creatable_by?(current_user)           # *You define this method.*
                           +                                     # If you don't, the inherited one
                           |                                     # calls `default`...
                           v
    AdminAuthorizer.default(:creatable, current_user)            # *You define this method.*
                                                                 # If you don't, it will use the one
                                                                 # inherited from ApplicationAuthorizer.
                                                                 # (Its parent, Authority::Authorizer,
                                                                 # defines the method as `return false`.)

If the answer is false and the original caller was a controller, this is treated as a SecurityViolation. If it was a view, maybe you just don't show a link.

The authorization process for instances is different in that it calls the instance's default method before calling the class default method. This allows you to define default behaviour that requires access to the model instance to be determined (eg, assume any action on a blog post is allowed if that post is marked 'wiki').

(Diagrams made with AsciiFlow)

## Installation

Starting from a clean commit status, add authority to your Gemfile, then bundle.

If you're using Rails, run rails g authority:install. Otherwise, pass a block to Authority.configure with configuration options somewhere when your application boots up.

## Defining Your Abilities

Edit config/initializers/authority.rb. That file documents all your options, but one of particular interest is config.abilities, which defines the verbs and corresponding adjectives in your app. The defaults are:

config.abilities =  {
  :create => 'creatable',
  :read   => 'readable',
  :update => 'updatable',
  :delete => 'deletable'
}

This option determines what methods are added to your users, models and authorizers. If you need to ask user.can_deactivate?(Satellite) and @satellite.deactivatable_by?(user), add :deactivate => 'deactivatable' to the hash.

## Wiring It Together ### Users
# Whatever class represents a logged-in user in your app
class User
  # Adds `can_create?(resource)`, etc
  include Authority::UserAbilities
...
end
### Models
class Article
  # Adds `creatable_by?(user)`, etc
  include Authority::Abilities

  # Without this, 'ArticleAuthorizer' is assumed;
  # if that doesn't exist, 'ApplicationAuthorizer'
  self.authorizer_name = 'AdminAuthorizer'
  ...
end
### Authorizers

Add your authorizers under app/authorizers, subclassing the generated ApplicationAuthorizer.

These are where your actual authorization logic goes. Here's how it works:

  • Instance methods answer questions about model instances, like "can this user update this particular widget?" (Within an instance method, you can get the model instance with resource).
    • Any instance method you don't define (for example, if you didn't make a def deletable_by?(user)) will fall back to the corresponding class method. In other words, if you haven't said whether a user can update this particular widget, we'll decide by checking whether they can update any widget.
  • Class methods answer questions about model classes, like "is it ever permissible for this user to update a Widget?"
    • Any class method you don't define (for example, if you didn't make a def self.updatable_by?(user)) will call that authorizer's default method.

For example:

# app/authorizers/schedule_authorizer.rb
class ScheduleAuthorizer < ApplicationAuthorizer
  # Class method: can this user at least sometimes create a Schedule?
  def self.creatable_by?(user)
    user.manager?
  end

  # Instance method: can this user delete this particular schedule?
  def deletable_by?(user)
    resource.in_future? && user.manager? && resource.department == user.department
  end
end

# undefined; calls `ScheduleAuthorizer.default(:updatable, user)`
ScheduleAuthorizer.updatable_by?(user)

As you can see, you can specify different logic for every method on every model, if necessary. On the other extreme, you could simply supply a default method that covers all your use cases.

#### Passing Options

Any options you pass when checking permissions will be passed right up the chain. One use case for this would be if you needed an associated instance in order to do a class-level check. For example:

# I don't have a comment instance to check, but I need to know
# which post the user wants to comment on
user.can_create?(Comment, :for => @post)

This would ultimately call creatable_by? on the designated authorizer with two arguments: the user and {:for => @post}. If you've defined that method yourself, you'd need to ensure that it accepts the options hash before doing this, or you'd get a "wrong number of arguments" error.

There's nothing special about the hash key :for; I just think it reads well in this case. You can pass any options that make sense in your case.

If you don't pass options, none will be passed to your authorizer, either.

And you could always handle the case above without options if you don't mind creating an extra model instance:

user.can_create?(Comment.new(:post => @post))
#### Default Methods

Any class method you don't define on an authorizer will call the default method on that authorizer. This method is defined on Authority::Authorizer to simply return false. This is a 'whitelisting' approach; any permission you haven't specified (which falls back to the default method) is considered forbidden.

You can override this method in your ApplicationAuthorizer and/or per authorizer. For example, you might want one that looks up the user's roles and correlates them with permissions:

# app/authorizers/application_authorizer.rb
class ApplicationAuthorizer < Authority::Authorizer

  # Example call: `default(:creatable, current_user)`
  def self.default(able, user)
    has_role_granting?(user, able) || user.admin?
  end

  protected

  def has_role_granting?(user, able)
    # Does the user have any of the roles which give this permission?
    (roles_which_grant(able) & user.roles).any?
  end

  def roles_which_grant(able)
    # Look up roles for the current authorizer and `able`
    ...
  end
end

If your system is uniform enough, this method alone might handle all the logic you need.

#### Testing Authorizers

One nice thing about putting your authorization logic in authorizers is the ease of testing. Here's a brief example.

# An authorizer shared by several admin-only models
describe AdminAuthorizer do

  before :each do
    @user  = FactoryGirl.build(:user)
    @admin = FactoryGirl.build(:admin)
  end

  describe "class" do
    it "lets admins update" do
      expect(AdminAuthorizer).to be_updatable_by(@admin)
    end

    it "doesn't let users update" do
      expect(AdminAuthorizer).not_to be_updatable_by(@user)
    end
  end

  describe "instances" do

    before :each do
      # A mock model that uses AdminAuthorizer
      @admin_resource_instance = mock_admin_resource
    end

    it "lets admins delete" do
      expect(@admin_resource_instance.authorizer).to be_deletable_by(@admin)
    end

    it "doesn't let users delete" do
      expect(@admin_resource_instance.authorizer).not_to be_deletable_by(@user)
    end

  end

end
### Controllers

If you're using Rails, ActionController support will be loaded in through a Railtie. Otherwise, you'll want to integrate it into your framework yourself. Authority's controller is an excellent starting point.

You can check authorization in your controllers in one of two ways:

  • authorize_actions_for Llama protects multiple controller actions with a before_filter, which performs a class-level check. If the current user is never allowed to delete a Llama, they'll never even get to the controller's destroy method.
  • authorize_action_for @llama can be called inside a single controller action, and performs an instance-level check. If called inside update, it will check whether the current user is allowed to update this particular @llama instance.

If either method finds a user attempting something they're not authorized to do, a Security Violation will result.

How does authorize_actions_for know to check deletable_by? before the controller's destroy action? It checks your configuration. These mappings are configurable globally from the initializer file. Defaults are as follows:

config.controller_action_map = {
 :index   => 'read',    # `index` controller action will check `readable_by?`
 :show    => 'read',
 :new     => 'create',  # `new` controller action will check `creatable_by?`
 :create  => 'create',  # ...etc
 :edit    => 'update',
 :update  => 'update',
 :destroy => 'delete'
}

They are also configurable per controller, as follows:

class LlamasController < ApplicationController

  # Check class-level authorizations before all actions except :create
  # Also, to authorize this controller's 'neuter' action, ask whether `current_user.can_update?(Llama)`
  authorize_actions_for Llama, :except => :create, :actions => {:neuter => :update},

  # To authorize this controller's 'breed' action, ask whether `current_user.can_create?(Llama)`
  # To authorize its 'vaporize' action, ask whether `current_user.can_delete?(Llama)`
  authority_actions :breed => 'create', :vaporize => 'delete'

  ...

  def edit
    @llama = Llama.find(params[:id])
    authorize_action_for(@llama)        # Check to see if you're allowed to edit this llama. failure == SecurityViolation
  end

  def update
    @llama = Llama.find(params[:id])
    authorize_action_for(@llama)        # Check to see if you're allowed to edit this llama.
    @llama.attributes = params[:llama]  # Don't save the attributes before authorizing
    authorize_action_for(@llama)        # Check again, to see if the changes are allowed.
    if @llama.save?
    # etc
  end

end

You can pass extra arguments to your authorization checks in these controller helpers:

  • authorize_actions_for(Llama, args: [{:mamma => true}]
  • authorize_action_for(@llama, :sporting => @hat_style)

Generally, though, your authorization will depend on some attribute or association of the model instance, so the authorizer can check @llama.neck_strength and @llama.owner.nationality, etc, without needing any additional information.

Note that you can also call authority_actions as many times as you like, so you can specify one mapping at a time if you prefer:

class LlamasController < ApplicationController
  def breed
    # some code
  end
  authority_actions :breed => 'create'

  def vaporize
    # some code
  end
  authority_actions :vaporize => 'delete'
end

If you have a controller that dynamically determines the class it's working with, you can pass the name of a controller instance method to authorize_actions_for instead of a class, and the class will be looked up when a request is made.

class LlamasController < ApplicationController

  authorize_actions_for :llama_class

  def llama_class
    # This method can simply return a class...
    [StandardLlama, LludicrousLlama].sample

    # ... or an array with a class and some options
    [OptionLladenLlama, {country: 'Peru'}]
  end
end

If you want to authorize all actions the same way, use the special all_actions hash key. For instance, if you have nested resources, you might say "you're allowed to do anything you like with an employee if you're allowed to update their employer".

class EmployeesController < ApplicationController
  authorize_actions_for :parent_resource, all_actions: :update
  private
  def parent_resource
    Employer.find(params[:employer_id])
  end
end

Finally, you can enforce that every controller action runs an authorization check using the class method ensure_authorization_performed, which sets up an after_filter to raise an exception if it wasn't. Any only or except arguments will be passed to after_filter. You can also use if or unless to specify the name of a controller method which determines whether it's necessary.

Since this runs in an after_filter, it obviously doesn't prevent the action, it just alerts you that no authorization was performed. Therefore, it's most useful in development. An example usage might be:

class ApplicationController < ActionController::Base
  ensure_authorization_performed :except => [:index, :search], :if => :auditing_security?, :unless => :devise_controller?

  def auditing_security?
    Rails.env != 'production'
  end
end

If you want a skippable filter, you can roll your own using the instance method, also called ensure_authorization_performed.

### Views

Assuming your user object is available in your views, you can do all kinds of conditional rendering. For example:

link_to 'Edit Widget', edit_widget_path(@widget) if current_user.can_update?(@widget)

If the user isn't allowed to edit widgets, they won't see the link. If they're nosy and try to hit the URL directly, they'll get a Security Violation from the controller.

## The Generic `can?`

Authority is organized around protecting resources. But occasionally you may need to authorize something that has no particular resource. For that, it provides the generic can? method. It works like this:

current_user.can?(:view_stats_dashboard) # calls `ApplicationAuthorizer.authorizes_to_view_stats_dashboard?`
current_user.can?(:view_stats_dashboard, :on => :tuesdays, :with => :tea) # same, passing the options

# application_authorizer.rb
class ApplicationAuthorizer < Authority::Authorizer
  # ...
  def self.authorizes_to_view_stats_dashboard?(user, options = {})
    user.has_role?(:manager) # or whatever
  end
end

Use this very sparingly, and consider it a code smell. Overuse will turn your ApplicationAuthorizer into a junk drawer of methods. Ask yourself, "am I sure I don't have a resource for this? Should I have one?"

## Security Violations & Logging

If you're using Authority's ActiveController integration or have used it as a template for your own, your application will handle unauthorized requests with 403 Forbidden automatically.

If you use Authority to conditionally render links, users will only see links for actions they're authorized to take. If a user deliberately tries to access a restricted resource (for instance, by typing the URL directly), Authority raises and rescues an Authority::SecurityViolation.

When it rescues the exception, Authority calls whatever controller method is specified by your security_violation_handler option, handing it the exception. The default handler is authority_forbidden, which Authority mixes in to your ApplicationController. It does the following:

  • Renders public/403.html
  • Logs the violation to whatever logger you configured.

You can define your own authority_forbidden method on ApplicationController and/or any other controller. For example:

# Send 'em back where they came from with a slap on the wrist
def authority_forbidden(error)
  Authority.logger.warn(error.message)
  redirect_to request.referrer.presence || root_path, :alert => 'You are not authorized to complete that action.'
end

Your method will be handed the SecurityViolation, which has a message method. In case you want to build your own message, it also exposes user, action and resource.

When a user action is successfully authorized, Authority will call authority_success on your controller. By default, this does nothing, but you can override it to log the event or do something else. For instance:

def authority_success(user, action, resource)
  Authority.logger.info "user #{user} was authorized to #{action} resource #{resource}"
end
## Credits, AKA 'Shout-Outs'

Do you like Authority? Has it cleaned up your code, made you more personable, and taught you the Secret to True Happiness? Awesome! I'd love to get email from you - see my Github profile for the address.

Contributing

How can you contribute? Let me count the ways.

1. Publicity

If you like Authority, tell people! Blog, tweet, comment, or even... [shudder]... talk with people in person. If you feel up to it, I mean. It's OK if you don't.

2. Documentation

Add examples to the wiki to help others solve problems like yours.

3. Issues

Tell me your problems and/or ideas.

4. Code or documentation

  1. Have an idea. If you don't have one, check the TODO file or grep the project for 'TODO' comments.
  2. Open an issue so we can talk it over.
  3. Fork this project
  4. Create your feature branch (git checkout -b my-new-feature)
  5. bundle install to get all dependencies
  6. rspec spec to run all tests.
  7. Update/add tests for your changes and code until they pass.
  8. Commit your changes (git commit -am 'Added some feature')
  9. Push to the branch (git push origin my-new-feature)
  10. Create a new Pull Request

authority's People

Contributors

adamhunter avatar awinograd avatar binarypaladin avatar div avatar dteoh avatar emsk avatar erikaxel avatar forced-request avatar humanshell avatar ivor avatar kevmoo avatar mguymon avatar mp211 avatar nathanl avatar paukul avatar sanemat avatar scottmartin avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

authority's Issues

Views of controllers that don't have a resource and passing options

Views of controllers that don't have a resource and passing options

At the wiki, we can see "Using authority on controllers that don't have a resource". But how about the view?

Simple example: I whould like to have a link, on a view, only if the user has rights to it.

Models:
User,Course,Role (from rolify)
Controllers: Course and Teacher

I have a controller where only the course's owner can manage the course's teachers.
I don't have a Teacher Model. A teacher is a teacher on a Course if it has a role teacher on that course.

Since I don't have a Model, I'm using the example at "Using authority on controllers that don't have a resource".

It that example, how can I create a link, on the view, only if the user has rights to acess it?

class TeachersController < ApplicationController 
  before_filter :authenticate_user!
  before_filter :load_course
  authorize_actions_for TeacherAuthorizer, :for => @course

So far so good, but how can I use it on the view?

= link_to 'Manage Teachers', course_teachers_path(@course) if current_user.can_create?(WHAT_HERE?)

I could use the User model, since the Teacher is a User, but it would require to set User.authorizer_name = TeacherAuthorizer.
But that doesn't fell right.

An other approach is to try to call the TeacherAuthorizer, in the view:

= link_to 'Manage Teachers', course_teachers_path(@course) if TeacherAuthorizer.creatable_by?(current_user, :for=>@course)

But I would get a "wrong number of arguments (2 for 1)".

I think, if we could, somehow say at TeacherAuthorizer:

class TeacherAuthorizer < Authority::Authorizer
  def self.auth_symbol = :teacher
  # Or gets from the Authorizer name of the class

And then, if we could:

= link_to 'Manage Teachers', course_teachers_path(@course) if current_user.can_create?(:teacher, :for=>@course)

In the controller, I'm doing this:

@course = Course.find(params[:course_id])
@course.authorizer_name = 'TeacherAuthorizer'
authorize_action_for @course

Can I map controller actions to multiple verbs?

I like the default controller_action_map configuration, however I was wondering if it was also possible to create an additional verb, say "manage" and map "manage" to all the controller actions.

In short, I would like to keep the granularity of your default controller_action_map, but also have a nice shortcut way to group actions together while still keeping granularity of the individual CRUD maps.

Memoizing calls to Authorizer methods

The logic in my Authorizer methods has gotten pretty complex. It checks for various roles, permission levels, etc. and also relies on other Authorizer methods. To give a simple example:

def readable_by?(user)
  #complex logic, or
  updateable_by?(user) || manageable_by?(user)
end

def updateable_by?(user)
  # More complex logic, or
  user.has_role?(:editor, resource) || manageable_by?(user)
end

def manageable_by?(user)
  user.has_role?(:admin, resource)
end

In practice, I've found this to be very slow. For example, when calling readable_by?, first some complex logic happens, then it calls updateable_by? and does more complex logic. If this fails, it calls manageable_by?, which checks for an :admin role. If this fails, it rolls back up the call stack to readable_by? and calls manageable_by? again!

My proposal is to memoize (i.e. cache) calls to Authorizer methods during the lifetime of a single request. My assumptions are:

  1. Permissions are usually only being checked for current_user, or at least for the same user over and over again.
  2. A user's permissions on a resource will usually not change during the lifetime of a single request. If they do change, and we care, the cache can be cleared.

The design in my head involves a Authority::Memoization module to implement the memoization (perhaps using a Thread local variable for caching?), an API to clear the cache, and a middleware or controller after filter to clear the cache after each request. The memoization would be opt-in via a method call somewhere.

I'd be willing to try writing this.

Thoughts?

Remove Rails dependency

Looking through the source, Authority isn't tied too tightly to Rails. ActiveSupport's the core Rails library being used.

While the Railtie and generators are nice, you could use a configuration or auto-detection mechanism to load those on demand/need.

ORM agnosticism is nice, but framework-agnosticism? That's great!

Plus an abstracted framework integration layer would make it easier for people (cough) to contribute new integrations.

Getting Started

OK so I am being thick but I cant get this to work and hopefully you can point me in the right direction. I just keep getting the error below when going to path listings/new

undefined method `can_create?' for nil:NilClass
User Model
class User < ActiveRecord::Base
  include Authority::UserAbilities
  ...
Listings Controller
class ListingsController < ApplicationController
  authorize_actions_for Listing
  ....
Listing Model
class Listing < ActiveRecord::Base
  include Authority::Abilities
  ....
ListingAuthorizer
class ListingAuthorizer < ApplicationAuthorizer
  def creatable_by?(user)
    true
  end

  def self.creatable_by?(user)
    true
  end

end
Authority.rb
Authority.configure do |config|
  config.user_method = :current_user
  config.controller_action_map = {
    :index   => 'read',
    :show    => 'read',
    :new     => 'create',
    :create  => 'create',
    :edit    => 'update',
    :update  => 'update',
    :destroy => 'delete'
  }
  config.abilities =  {
    :create => 'creatable',
    :read   => 'readable',
    :update => 'updatable',
    :delete => 'deletable'
  }
end
ApplicationAuthorizer.rb
class ApplicationAuthorizer < Authority::Authorizer
  def self.default(adjective, user)
    false
  end
end

Hope you can get me on the right path !

Authority not redirecting

Hi,

I have an admin_controller that other namespaced controllers inherit from. IN the admin_controller i want to have a base check (which works). current_user.can?(:login_to_admin). Then in the app authorizer i have

def self.authorizes_to_login_to_admin?(user,options = {})
user.has_role?(:admin)
end

Testing on a user without that role works.

1.9.3-p327 :002 > u.can?(:login_to_admin)
(0.1ms) SELECT COUNT(*) FROM roles INNER JOIN users_roles ON roles.id = users_roles.role_id WHERE users_roles.user_id = 2 AND (((roles.name = 'admin') AND (roles.resource_type IS NULL) AND (roles.resource_id IS NULL)))
=> false

However, the user does not get redirect to a 403 in the app. Is this supposed to happen automatically ?

Authorizers don't automatically reload in development

When I edit the code in an authorizer it isn't picked up by the application (in development) until i've restarted the app. Is there a way to make the authorizers automatically reload with each request in development like models/controllers/views/assets do?

nil is not a symbol

@nathanl

When I use the ruby ruby-1.9.3-p327 and authority 2.4.1, I have this code in my controller.

class MembersController < ApplicationController

  authorize_actions_for Admin
...
end

class AdminsController < MembersController
end

And I get the following error when I visit admins index page.

TypeError in AdminsController#index

nil is not a symbol
Rails.root: /home/xinlangzi/workspace/ads

Application Trace | Framework Trace | Full Trace
authority (2.4.1) lib/authority/controller.rb:99:in `authority_resource'
authority (2.4.1) lib/authority/controller.rb:94:in `run_authorization_check'
activesupport (3.2.11) lib/active_support/callbacks.rb:451:in `_run__3818043125594197570__process_action__2898453861387782050__callbacks'
activesupport (3.2.11) lib/active_support/callbacks.rb:405:in `__run_callback'
activesupport (3.2.11) lib/active_support/callbacks.rb:385:in `_run_process_action_callbacks'
activesupport (3.2.11) lib/active_support/callbacks.rb:81:in `run_callbacks'
actionpack (3.2.11) lib/abstract_controller/callbacks.rb:17:in `process_action'
actionpack (3.2.11) lib/action_controller/metal/rescue.rb:29:in `process_action'
actionpack (3.2.11) lib/action_controller/metal/instrumentation.rb:30:in `block in process_action'
activesupport (3.2.11) lib/active_support/notifications.rb:123:in `block in instrument'
activesupport (3.2.11) lib/active_support/notifications/instrumenter.rb:20:in `instrument'
activesupport (3.2.11) lib/active_support/notifications.rb:123:in `instrument'
actionpack (3.2.11) lib/action_controller/metal/instrumentation.rb:29:in `process_action'
actionpack (3.2.11) lib/action_controller/metal/params_wrapper.rb:207:in `process_action'
activerecord (3.2.11) lib/active_record/railties/controller_runtime.rb:18:in `process_action'
actionpack (3.2.11) lib/abstract_controller/base.rb:121:in `process'
actionpack (3.2.11) lib/abstract_controller/rendering.rb:45:in `process'
actionpack (3.2.11) lib/action_controller/metal.rb:203:in `dispatch'
actionpack (3.2.11) lib/action_controller/metal/rack_delegation.rb:14:in `dispatch'
actionpack (3.2.11) lib/action_controller/metal.rb:246:in `block in action'
actionpack (3.2.11) lib/action_dispatch/routing/route_set.rb:73:in `call'
actionpack (3.2.11) lib/action_dispatch/routing/route_set.rb:73:in `dispatch'
actionpack (3.2.11) lib/action_dispatch/routing/route_set.rb:36:in `call'
journey (1.0.4) lib/journey/router.rb:68:in `block in call'
journey (1.0.4) lib/journey/router.rb:56:in `each'
journey (1.0.4) lib/journey/router.rb:56:in `call'
actionpack (3.2.11) lib/action_dispatch/routing/route_set.rb:601:in `call'
warden (1.2.1) lib/warden/manager.rb:35:in `block in call'
warden (1.2.1) lib/warden/manager.rb:34:in `catch'
warden (1.2.1) lib/warden/manager.rb:34:in `call'
actionpack (3.2.11) lib/action_dispatch/middleware/best_standards_support.rb:17:in `call'
rack (1.4.5) lib/rack/etag.rb:23:in `call'
rack (1.4.5) lib/rack/conditionalget.rb:25:in `call'
actionpack (3.2.11) lib/action_dispatch/middleware/head.rb:14:in `call'
actionpack (3.2.11) lib/action_dispatch/middleware/params_parser.rb:21:in `call'
actionpack (3.2.11) lib/action_dispatch/middleware/flash.rb:242:in `call'
rack (1.4.5) lib/rack/session/abstract/id.rb:210:in `context'
rack (1.4.5) lib/rack/session/abstract/id.rb:205:in `call'
actionpack (3.2.11) lib/action_dispatch/middleware/cookies.rb:341:in `call'
activerecord (3.2.11) lib/active_record/query_cache.rb:64:in `call'
activerecord (3.2.11) lib/active_record/connection_adapters/abstract/connection_pool.rb:479:in `call'
actionpack (3.2.11) lib/action_dispatch/middleware/callbacks.rb:28:in `block in call'
activesupport (3.2.11) lib/active_support/callbacks.rb:405:in `_run__1533354125810535700__call__4474434814390027399__callbacks'
activesupport (3.2.11) lib/active_support/callbacks.rb:405:in `__run_callback'
activesupport (3.2.11) lib/active_support/callbacks.rb:385:in `_run_call_callbacks'
activesupport (3.2.11) lib/active_support/callbacks.rb:81:in `run_callbacks'
actionpack (3.2.11) lib/action_dispatch/middleware/callbacks.rb:27:in `call'
actionpack (3.2.11) lib/action_dispatch/middleware/reloader.rb:65:in `call'
actionpack (3.2.11) lib/action_dispatch/middleware/remote_ip.rb:31:in `call'
airbrake (3.1.8) lib/airbrake/rails/middleware.rb:15:in `call'
actionpack (3.2.11) lib/action_dispatch/middleware/debug_exceptions.rb:16:in `call'
actionpack (3.2.11) lib/action_dispatch/middleware/show_exceptions.rb:56:in `call'
railties (3.2.11) lib/rails/rack/logger.rb:32:in `call_app'
railties (3.2.11) lib/rails/rack/logger.rb:16:in `block in call'
activesupport (3.2.11) lib/active_support/tagged_logging.rb:22:in `tagged'
railties (3.2.11) lib/rails/rack/logger.rb:16:in `call'
quiet_assets (1.0.1) lib/quiet_assets.rb:20:in `call_with_quiet_assets'
actionpack (3.2.11) lib/action_dispatch/middleware/request_id.rb:22:in `call'
rack (1.4.5) lib/rack/methodoverride.rb:21:in `call'
rack (1.4.5) lib/rack/runtime.rb:17:in `call'
activesupport (3.2.11) lib/active_support/cache/strategy/local_cache.rb:72:in `call'
rack (1.4.5) lib/rack/lock.rb:15:in `call'
actionpack (3.2.11) lib/action_dispatch/middleware/static.rb:62:in `call'
airbrake (3.1.8) lib/airbrake/user_informer.rb:16:in `_call'
airbrake (3.1.8) lib/airbrake/user_informer.rb:12:in `call'
railties (3.2.11) lib/rails/engine.rb:479:in `call'
railties (3.2.11) lib/rails/application.rb:223:in `call'
rack (1.4.5) lib/rack/content_length.rb:14:in `call'
railties (3.2.11) lib/rails/rack/log_tailer.rb:17:in `call'
thin (1.5.0) lib/thin/connection.rb:81:in `block in pre_process'
thin (1.5.0) lib/thin/connection.rb:79:in `catch'
thin (1.5.0) lib/thin/connection.rb:79:in `pre_process'
thin (1.5.0) lib/thin/connection.rb:54:in `process'
thin (1.5.0) lib/thin/connection.rb:39:in `receive_data'
eventmachine (1.0.0) lib/eventmachine.rb:187:in `run_machine'
eventmachine (1.0.0) lib/eventmachine.rb:187:in `run'
thin (1.5.0) lib/thin/backends/base.rb:63:in `start'
thin (1.5.0) lib/thin/server.rb:159:in `start'
rack (1.4.5) lib/rack/handler/thin.rb:13:in `run'
rack (1.4.5) lib/rack/server.rb:268:in `start'
railties (3.2.11) lib/rails/commands/server.rb:70:in `start'
railties (3.2.11) lib/rails/commands.rb:55:in `block in <top (required)>'
railties (3.2.11) lib/rails/commands.rb:50:in `tap'
railties (3.2.11) lib/rails/commands.rb:50:in `<top (required)>'
script/rails:6:in `require'
script/rails:6:in `<main>'

I change the code as follow, then it works.

class MembersController < ApplicationController

  authorize_actions_for Admin
...
end

class AdminsController < MembersController
  authorize_actions_for Admin
end

So I think this code does not consider about the inheritance:

 def authority_resource
      return self.class.authority_resource       if self.class.authority_resource.is_a?(Class)
      send(self.class.authority_resource)
    rescue NoMethodError => e
      raise MissingResource.new(
          "Trying to authorize actions for '#{self.class.authority_resource}', but can't. \
          Must be either a resource class OR the name of a controller instance method that \
          returns one.".squeeze(' ')
      )
    end

Authority and Capybara

I am not sure this is a problem in the gem, but just not sure where to post a question, so hoping you could help me.

I have a Company resource which has
self.authorizer_name = "OperationsAuthorizer"

In the companies controller I have authorize_actions_for(Company)

In my specs, I log in as an operations user, and even check that
expect(Company.creatable_by?(user)).to be_truthy
which passes

But when I submit the form in my feature spec (using capybara), I get an error:
User is not authorized to create this resource: Company
And if I add debugger in my create action, I don't even get to it.
And the path stays on the new view.

I do not get this error when I check the form in the browser.

What am I missing?

namespaces

Hi,
is it possible to use authority in app with some namespaces? For example - i have 3 namespaces:
/posts
/admin/posts
/moderator/posts

and i'd like to separate authorisers for different namespaces? How to do it?

More complex layering authorization

Hi,

i'm migrate from CanCan to Authority and i stuck on one thing.

My App has one more layer than only user and role authorizing. It is Account authorizing.

So every user should belongs to more accounts with different roles.

Account, User, Role
  has_many    :account_user_roles
end

AccountUserRole
  belongs_to :account
  belongs_to :user
  belongs_to :role
end

Account is layer, that separate clients against each other. Like

For example:
user 1 belongs to account 1 with role admin
user 1 belongs to account 2 with role author

User must be switched only in one account. So if i'm switched in Account 2 i have permissions only as author for that account.

Can you please hint me, how to do this?

In my controller i have helpers current_account, current_role

With CanCan it was ease, because there should be initialize with more arguments:

Ability.new(current_user, current_account, current_role, ...etc)

and in ability.rb do for example

can :manage, Media::Document, user_id: @current_user.id, account_id: @current_account.id

Thank you for help.

R.

Can you add an example for a controller's index action that includes filtering by user?

I have an application where I need to limit the index on a controller to only return items that a user created. Right now I can't figure out how to use this gem to do that. It appears that I am still required to do something like..

current_user.widgets.all

I'd rather just set it up to apply permissions to filter the widgets and then do Widgets.all. In this way I don't have to worry about anyone forgetting filter the widgets to only current_user.

Authorizing a given action

Hi,

I'm using Authority for a complex project and found that sometimes it's authorisation model is too restrictive. "authorize_action_for" doesn't work if I want to authorize manually rather than rely on action name.

For example, authorising a user to edit it's profile. I'd have to create authorizers (or a shared authorizer) for children resources. IMO it's easier and as DRY to check if user can edit user object and be done with it. I added a "authorize_for(action, object, *options)" method and it works pretty well for me.

Would you add such a method? I can make a pull request once I prepare tests.

Deployment issue with Capistrano

This is a cross-post from me at stackoverflow.

http://stackoverflow.com/questions/19238540/error-when-deploying-the-authority-gem-with-capistrano

If a get a response from there I will post it back here.

I'm deploying a rails4 app from a dev machine to a server. This deployment has been working smoothly.

I have added authorisation to my application using the authority gem. When I run ./bin/cap deploy I now get the error in the unicorn log that is listed below.

All of the files created with rails g authority:install on the dev machine have been committed to git and are being deployed to the server using ./bin/cap deploy.

Any help appreciated.

I, [2013-10-08T14:06:42.579240 #2021]  INFO -- : worker=0 spawning...
I, [2013-10-08T14:06:42.583257 #3594]  INFO -- : worker=0 spawned pid=3594
I, [2013-10-08T14:06:42.583904 #3594]  INFO -- : Refreshing Gem list
E, [2013-10-08T14:06:42.743225 #3588] ERROR -- : uninitialized constant Authority (NameError)
/home/deployer/apps/app-name/releases/20131008015646/config/initializers/authority.rb:1:in `<top (required)>'
/home/deployer/apps/app-name/shared/bundle/ruby/2.0.0/gems/railties-4.0.0/lib/rails/engine.rb:609:in `block (2 levels) in <class:Engine>'
/home/deployer/apps/app-name/shared/bundle/ruby/2.0.0/gems/railties-4.0.0/lib/rails/engine.rb:608:in `each'
/home/deployer/apps/app-name/shared/bundle/ruby/2.0.0/gems/railties-4.0.0/lib/rails/engine.rb:608:in `block in <class:Engine>'
/home/deployer/apps/app-name/shared/bundle/ruby/2.0.0/gems/railties-4.0.0/lib/rails/initializable.rb:30:in `instance_exec'
/home/deployer/apps/app-name/shared/bundle/ruby/2.0.0/gems/railties-4.0.0/lib/rails/initializable.rb:30:in `run'
/home/deployer/apps/app-name/shared/bundle/ruby/2.0.0/gems/railties-4.0.0/lib/rails/initializable.rb:55:in `block in run_initializers'
/home/deployer/.rbenv/versions/2.0.0-p0/lib/ruby/2.0.0/tsort.rb:150:in `block in tsort_each'
/home/deployer/.rbenv/versions/2.0.0-p0/lib/ruby/2.0.0/tsort.rb:183:in `block (2 levels) in each_strongly_connected_component'
/home/deployer/.rbenv/versions/2.0.0-p0/lib/ruby/2.0.0/tsort.rb:210:in `block (2 levels) in each_strongly_connected_component_from'
/home/deployer/.rbenv/versions/2.0.0-p0/lib/ruby/2.0.0/tsort.rb:219:in `each_strongly_connected_component_from'
/home/deployer/.rbenv/versions/2.0.0-p0/lib/ruby/2.0.0/tsort.rb:209:in `block in each_strongly_connected_component_from'
/home/deployer/apps/app-name/shared/bundle/ruby/2.0.0/gems/railties-4.0.0/lib/rails/initializable.rb:44:in `each'
/home/deployer/apps/app-name/shared/bundle/ruby/2.0.0/gems/railties-4.0.0/lib/rails/initializable.rb:44:in `tsort_each_child'
/home/deployer/.rbenv/versions/2.0.0-p0/lib/ruby/2.0.0/tsort.rb:203:in `each_strongly_connected_component_from'
/home/deployer/.rbenv/versions/2.0.0-p0/lib/ruby/2.0.0/tsort.rb:182:in `block in each_strongly_connected_component'
/home/deployer/.rbenv/versions/2.0.0-p0/lib/ruby/2.0.0/tsort.rb:180:in `each'
/home/deployer/.rbenv/versions/2.0.0-p0/lib/ruby/2.0.0/tsort.rb:180:in `each_strongly_connected_component'
/home/deployer/.rbenv/versions/2.0.0-p0/lib/ruby/2.0.0/tsort.rb:148:in `tsort_each'
/home/deployer/apps/app-name/shared/bundle/ruby/2.0.0/gems/railties-4.0.0/lib/rails/initializable.rb:54:in `run_initializers'
/home/deployer/apps/app-name/shared/bundle/ruby/2.0.0/gems/railties-4.0.0/lib/rails/application.rb:215:in `initialize!'
/home/deployer/apps/app-name/shared/bundle/ruby/2.0.0/gems/railties-4.0.0/lib/rails/railtie/configurable.rb:30:in `method_missing'
/home/deployer/apps/app-name/releases/20131008015646/config/environment.rb:5:in `<top (required)>'
config.ru:4:in `require'
config.ru:4:in `block in <main>'
/home/deployer/apps/app-name/shared/bundle/ruby/2.0.0/gems/rack-1.5.2/lib/rack/builder.rb:55:in `instance_eval'
/home/deployer/apps/app-name/shared/bundle/ruby/2.0.0/gems/rack-1.5.2/lib/rack/builder.rb:55:in `initialize'
config.ru:1:in `new'
config.ru:1:in `<main>'
/home/deployer/apps/app-name/shared/bundle/ruby/2.0.0/gems/unicorn-4.6.3/lib/unicorn.rb:48:in `eval'
/home/deployer/apps/app-name/shared/bundle/ruby/2.0.0/gems/unicorn-4.6.3/lib/unicorn.rb:48:in `block in builder'
/home/deployer/apps/app-name/shared/bundle/ruby/2.0.0/gems/unicorn-4.6.3/lib/unicorn/http_server.rb:722:in `call'
/home/deployer/apps/app-name/shared/bundle/ruby/2.0.0/gems/unicorn-4.6.3/lib/unicorn/http_server.rb:722:in `build_app!'
/home/deployer/apps/app-name/shared/bundle/ruby/2.0.0/gems/unicorn-4.6.3/lib/unicorn/http_server.rb:595:in `init_worker_process'
/home/deployer/apps/app-name/shared/bundle/ruby/2.0.0/gems/unicorn-4.6.3/lib/unicorn/http_server.rb:615:in `worker_loop'
/home/deployer/apps/app-name/shared/bundle/ruby/2.0.0/gems/unicorn-4.6.3/lib/unicorn/http_server.rb:500:in `spawn_missing_workers'
/home/deployer/apps/app-name/shared/bundle/ruby/2.0.0/gems/unicorn-4.6.3/lib/unicorn/http_server.rb:511:in `maintain_worker_count'
/home/deployer/apps/app-name/shared/bundle/ruby/2.0.0/gems/unicorn-4.6.3/lib/unicorn/http_server.rb:277:in `join'
/home/deployer/apps/app-name/shared/bundle/ruby/2.0.0/gems/unicorn-4.6.3/bin/unicorn:126:in `<top (required)>'
/home/deployer/apps/app-name/shared/bundle/ruby/2.0.0/bin/unicorn:23:in `load'
/home/deployer/apps/app-name/shared/bundle/ruby/2.0.0/bin/unicorn:23:in `<main>'
E, [2013-10-08T14:06:42.758457 #2021] ERROR -- : reaped #<Process::Status: pid 3588 exit 1> worker=1

License missing from gemspec

RubyGems.org doesn't report a license for your gem. This is because it is not specified in the gemspec of your last release.

via e.g.

spec.license = 'MIT'
# or
spec.licenses = ['MIT', 'GPL-2']

Including a license in your gemspec is an easy way for rubygems.org and other tools to check how your gem is licensed. As you can imagine, scanning your repository for a LICENSE file or parsing the README, and then attempting to identify the license or licenses is much more difficult and more error prone. So, even for projects that already specify a license, including a license in your gemspec is a good practice. See, for example, how rubygems.org uses the gemspec to display the rails gem license.

There is even a License Finder gem to help companies/individuals ensure all gems they use meet their licensing needs. This tool depends on license information being available in the gemspec. This is an important enough issue that even Bundler now generates gems with a default 'MIT' license.

I hope you'll consider specifying a license in your gemspec. If not, please just close the issue with a nice message. In either case, I'll follow up. Thanks for your time!

Appendix:

If you need help choosing a license (sorry, I haven't checked your readme or looked for a license file), GitHub has created a license picker tool. Code without a license specified defaults to 'All rights reserved'-- denying others all rights to use of the code.
Here's a list of the license names I've found and their frequencies

p.s. In case you're wondering how I found you and why I made this issue, it's because I'm collecting stats on gems (I was originally looking for download data) and decided to collect license metadata,too, and make issues for gemspecs not specifying a license as a public service :). See the previous link or my blog post about this project for more information.

Authority: Passing options in controllers

I'd like to use authorize_actions_for on top of all my controllers to establish an authorization pattern at all my site. However I need to pass an option to authorizers and I can't do it using this method.

My system can be used for multiple schools. So, I need to authorize resources for an specific school. the argument is an school_id.

What's the right way to do it? Authority can accomplish it?

I don't want to rewrite authorize_action_for at every action.

authorize controller without resources ?

Is it possible to authorize a controlleur without a resource ?

For example, I have a PrivateController which has in index the summary table of all the administrative resources. I would like to allow administrators and managers to access this page.

the authorizer is easy :

class PrivateAuthorizer < ApplicationAuthorizer
  def self.readable_by?(user)
    user.has_any_role?(:admin, :manager)
  end
end

What should I put in the controller to use this authorizer. As I do not realy have model resources associated with my controller, I do not know what I have to put in authorize_action_for.

defining authorisations with has_many / belongs_to association

Sorry for all the noob questions and thank ya'll for helping
I have nested models

class Orgunit < ActiveRecord::Base
  has_many :departments, :dependent => destroy
  ...
end

class Department < ActiveRecord::Base
  has_many :groups, :dependent => destroy
  belongs_to :orgunit
  ...
end

class Group < ActiveRecord::Base
  belongs_to :department
  ...
end

I am implementing Authority with instance authorisations.
I use Rolify and grant authority per resource.

class InstanceAuthorizer < ApplicationAuthorizer
  def self.default(able, user)
    false
  end

  def updatable_by?(user)
    (user.has_role_for_resource? :admin, resource) || (user.has_role? :superadmin)
  end

the method has_role_for_resource? is defined in the user model:

def has_role_for_resource?(role_name, resource = nil)
  self.roles.detect { |r| r.name == role_name.to_s && (r.resource == resource) }.present?
end

I would like to give a role for a top resource (Orgunit) and that the user will be have authorisation for all it's child resources.
Is there a quick and easy way to accomplish this?

Custom violation messaging

While I'm in here, I made another tiny change for my own use that may be of worth to others as well. It allows for custom SecurityViolation messaging to be optionally returned by able_by? methods in the authorizers:

class PostAuthorizer < ApplicationAuthorizer
  def readable_by(user)
    if user.has_role?(:reader)
      true
    else
      return false, I18n.t('app.posts.auth.readfail')
    end
  end
end

That sort of thiing. Which would just use the translation string over the SecurityViolation default message when raised. The only thing folks would need to remember is that it requires an explicit return.

Happy to contribute this as well, just say the word.

handling nested resources - can_create?

I need to be able to ask whether a user can create a child object (class-level) for a given instance. So something like current_user.can_create?(@post, Comment) where I can check the post's instance data to determine whether to go ahead with the operation or not. Is this currently possible?

Thoughts on Authority and Rails 4 view caching?

Besides helping you declare permissions logic and enforce it, Authority facilities the familiar style of checking permissions in views: show a delete link if current_user.can_delete?(@widget).

In DHH's Railsconf keynote, he talked about how view caching runs into issues when different users need to see different things, as in our example here. He gives a quick example of a link with "data-visible-to" => 'admin creator' and says that Javascript can be used to "decorate" the page and show/hide those links as needed.

My question is: what, if anything, should Authority do to support this new approach in Rails 4?

Whatever we do, I also want to continue making Authority more usable outside of Rails, as @christhekeele has been helping with.

Using multiple current_user models

Hi there,

I'm using Devise for my authentication. It allows me to have multiple 'user' models. I have an Admin model and a User model. I have successfully wired up Authority with my backend Admin controllers and views. The Authority config has current_user as current_admin - as per Devise instructions.

However, I now need to authorize my Users on some front end controllers (viewing their orders, updating their contact details etc) but don't seem to have a way to instruct Authority to now use current_user instead of current_admin.

Is there a way to configure Authority to use current_admin when working in a certain namespace (i.e. /admin/admin_controller) but use current_user when working in the front end? (i.e. orders/index would only allow the current_user to view their own orders).

Thanks in advance for any suggestions.

Shaun

Authority DSL

What do you think about packaging Authority with a simple little DSL for making long chains of conditionals in Authorizer methods more readable?

As my authorizers have grown more complex, I realized I could re-use the mini DSL from my Strong Parameters Sanitizer classes.

You can find the writeup here.

Undefined method 'authorize_actions_for'

I'm getting undefined method 'authorize_actions_for' in the controller when using rails-api. Any ideas what I need to include? Here's my code:

Gemfile:

...
gem 'authority', '~> 2.9.0'
gem 'rails-api', '~> 0.1.0'
...

app/authorizers/session_authorizer.rb:

class SessionAuthorizer < ApplicationAuthorizer
  def deletable_by?(user)
    user.sessions.include?(resource)
  end
end

app/controllers/v1/sessions_controller.rb:

class V1::SessionsController < ApplicationController
  authorize_actions_for Session
  ...
end

Multi tenant apps against the current_site?

I am having a little problem with getting it all together in a multi tenant app. Passing the site as options is little cumbersome and seems completely impossible.

Has anyone used authority for building the authorization for a multitenant app when its impossible to pass the current site or any params to the authorizer?

Correction to README

# Send 'em back where they came from with a slap on the wrist
def authority_forbidden(exception)
  Authority.logger.warn(error.message)
  redirect_to request.referrer.presence || root_path, :alert => 'You are not authorized to complete that action.'
end

should be...

# Send 'em back where they came from with a slap on the wrist
def authority_forbidden(exception)
  Authority.logger.warn(exception.message)
  redirect_to request.referrer.presence || root_path, :alert => 'You are not authorized to complete that action.'
end

error is a typo.

Allow for application controller level authorize_actions_for when using authority on controllers that don't have a resource

I've followed the instructions here to use authority on controllers that don't have a resource but I want to do this for every controller in my app except the authentication pages ( I am using devise ).

Right now it seems I have to include an authorize file for every controller and add the authorize_actions_for filter to the top of every controller. To DRY things up it would be nice if I could do something like this in my application controller:

authorize_actions_for ApplicationAuthorizer :unless => :devise_controller?

Trying that at the moment just throws the error:

undefined method `ApplicationAuthorizer' for ApplicationController:Class

undefined local variable or method `resource' for SchoolAuthorizer:Class

Hi,
I try using authority (great gem and concept).
I have followed the readme.
When I try to check an instance, I get the following error:

undefined local variable or method `resource' for SchoolAuthorizer:Class

My Group model looks like:

class Group < ActiveRecord::Base

  resourcify
  include Authority::Abilities
  self.authorizer_name = 'SchoolAuthorizer'
  ...

My GroupController looks like:

class GroupsController < ApplicationController
  authorize_actions_for Group
  ...
  def show
    @group = Group.find(params[:id])
    authorize_action_for @group
    @users = @group.users.where("first_name ilike :name OR last_name ilike :name", {:name => "%%#{@q}%%"}).paginate(:page => params[:page], :per_page => params[:limit] || Settings.users_per_page)
    @title = @group.name
  end

And my SchoolAuthorizer:

class SchoolAuthorizer < ApplicationAuthorizer
  def self.creatable_by?(user)
    user.has_role?("admin")
  end

  def self.readable_by?(user)
    true
  end

  def self.updatable_by?(user)
    user.has_role?("admin", resource)
  end
end

And I get the error above (undefined local variable or method `resource' for SchoolAuthorizer:Class )
What am I doing wrong?
Thanks for helping

Can abilities be defined on a per model/authorizer basis?

Controllers get a lot of love for customizing. However, in looking through the docs and the code it appears that the abilities that get added to a model are all or nothing.

What's the best method to handle this? For instance, in an app I'm working on I have some models that fit the very simple CRUD permissions, however there are some other models that have some fairly granular permissions in terms of what specifically can be read and/or updated.

What ends up happening is that while most models need a few delegated methods, ALL models get the full list even though many don't even apply.

At the moment it's not a functional problem because methods that aren't needed can be ignored, but for the sake of clarity it's a bit of an issue. It would be nice if I could define abilities on the authorizer itself or if, during inclusion it all of the "is_*_by" methods could automatically forwarded.

authorize_action_for: wrong number of arguments

I'm having some issues implementing Authority for my controller actions. It seems that Ruby chokes somewhere in the method_missing call stack. I am using Ruby 2.0, could it be related to this?

Here's the stack trace:

ArgumentError - wrong number of arguments (2 for 1):
   ~/.rbenv/versions/2.0.0-p0/lib/ruby/2.0.0/forwardable.rb:171:in `readable_by?'
   ~/.rbenv/versions/2.0.0-p0/lib/ruby/2.0.0/forwardable.rb:171:in `readable_by?'
   (gem) authority-2.5.0/lib/authority/user_abilities.rb:15:in `can_read?'
   (gem) authority-2.5.0/lib/authority.rb:43:in `action_authorized?'
   (gem) authority-2.5.0/lib/authority.rb:35:in `enforce'
   (gem) authority-2.5.0/lib/authority/controller.rb:76:in `authorize_action_for'
   app/controllers/accounts_controller.rb:7:in `call'

It all started with resource not being available in AccountAuthorizer#readable_by?, so I tried setting some options, this broke things:

authorize_action_for(accounts.first, :testing => accounts)

Allow multiple `authorize_actions_for`

I'm thinking of doing this in my controller:

class UsersController < ApplicationController
  authorize_actions_for User, only: %w(index create)
  authorize_actions_for :user, except: %w(index create)

  ...

  protected

  def user
    @user ||= User.find(params[:id])
  end
end

Unfortunately, it looks like invoking authorize_actions_for a second time completely overrides the authority_resource to :user.

Right now, to get away with this, this is how I get away with this issue:

class UsersController < ApplicationController
  authorize_actions_for :authority_resource

  ...

  protected

  def authority_resource
   params[:action].in?(%w(index create)) ? User : user
  end

  def user
    @user ||= User.find(params[:id])
  end
end

Overriding class method?

Apologies if I'm missing something really obvious here.

I want any user to be able to visit the new page for an Event, but only be able to create the Event if they own the chosen Event's Venue. I thought this might work

in view

= link_to "Add Event", new_event_path if current_user.can_create? Event

in authorizer

def self.creatable_by?(user)
  true
end

def creatable_by?(user)
  resource.venue.owner == user
end

but I think the class method is always taking precedence and the controller is ignoring the def creatable_by condition. So right now the user can create an event for any venue.

Whitelisting controller actions

First of all, great gem. I've recently switched over from CanCan to Authority because of the lack of updates for CanCan, but even more so because of the awesome structure of Authority. So two ๐Ÿ‘ 's up.

Having implemented Authority in my first model, I really like the flow of the authorization, and especially like the unit tests that are possible with this gem (here's a short example):

require 'spec_helper'

describe AccountAuthorizer do
  include_examples 'AuthorizedModel'
  Given(:account) { authorizer_account }

  context 'for account with active membership' do
    context '#updatable_by?' do
      Then { account.authorizer.should be_updatable_by(owner) }
    end

    context '#creatable_by?' do
      Then { account.authorizer.should restrict_all_access_to(:creatable) }
    end
  end

  context 'blocks all access for guests' do
    Then { account.authorizer.should restrict_all_access_for(guest) }
  end

  context 'blocks all access for user of different account' do
    Given(:account) { Account.new }
    Then { account.authorizer.should restrict_all_access_for(owner) }
  end
end

I just started implementing Authority for my controllers, but I couldn't find any mention in the documentation regarding whitelisting (even though it is possible on a model level, with the ApplicationAuthorizer.

Is there a nice and clean way to enable whitelisting? From what I could read, I have to manually add authorize_actions_for for class-level authorization. I would like all access to be restricted by default and open the gates from there.

How to handle index actions

Hi

How do you handle index actions with Authority? In my app I have User which can be either 'user' or 'admin'. They belong to a Company.

So, a 'user' can only visit the user#show action if it's their own user record. And an 'admin' can view all users from her company:

  def readable_by?(user)
    if user.role == 'admin'
      user.company == resource.company
    elsif user.role == 'user'
      user == resource
    else
      false
    end
  end

But what about the index action? I want an 'admin' to be able to access the user#index action but not the 'user'. I tried to use this:

  def self.readable_by?(user)
    user.role == 'admin'
  end

And added: "authorize_actions_for User" in the controller
But that doesn't work since it restricts a 'user' to read any user.

Am I supposed to just use authorize_action_for and pass the class instead of an instance? It seems to work.

  def index
    authorize_action_for User
    @users = current_user.company.users.order("name ASC")
  end

But then I'm a bit confused about when to use the "authorize_actions_for User" filter?

Authorize any_instance stubbing does not work on RSpec 2.14+

Hello!

in case it helps someone else, I just noticed that the following spec is not passing anymore when using Authority together with RSpec 2.14+:

    it 'verifies authorization' do
      MembershipAuthorizer.any_instance.stub(:deletable_by?).and_return(false)
      expect {
        post :destroy, valid_params
      }.to raise_error(Authority::SecurityViolation)
    end

Rolling back to RSpec 2.13.0 makes the spec pass again.

Note that this is not necessarily an issue with Authority itself, but I thought I would share it here in case, to track that down and share it with others, since it took me a bit of time to track it initially.

Using authority with inherited_resources

Authority looks really simple and succinct but I was wondering if you had any ideas on how it could be used with inherited_resources so that it could authorise the resource after it has been loaded. From what I can tell one needs to insert these checks manually which would defeat inherited_resources code minimisation strategy.

I dig that you want this to be as agnostic as possible, but just wondered if you'd come across this use-case already?

Passing options and authorize_action_for

I'm using Authority (like it!) on a project that has some nesting of resources, and have a question about authorizing controller actions. Is there a way to use authorize_action_for in a controller, and pass options with it?

I set up my authorizer classes to handle user.can_update?(Comment, for: @post), and it's working great.

If I try something like this:

authorize_action_for(Comment, for: @post)

I get argument error. Am I holding it wrong? Should I just use can_update? and friends, and raise an exception myself if the user cannot do the thing?

Limiting the scope of models by ability?

Say I have a list of users. I want to select users for editing in a view and the following rules apply.

A user can see and edit their own record (maybe with some exceptions like they can't change their salary).

A manager can see their employees records and can edit everything including the salary.

A regional manager can see and edit all users but only in their assigned region.

The HR manager can see and edit all records.

Is there an elegant way to get the users for the current_user?

Allow resource to specify the authorizer by class

Add the ability to define the authorizer by class, such as

self.authorizer_class = GreatAuthorizer

in addition to

self.authorizer_name = "GreateAuithorizer"

This will allow runtime classes to be used as the Authorizer.

For my scenario, we have a authorizer generator that tweaks the authorizer at runtime for each class.

add can_*? methods to nil/not-logged-in-user class

If we added can_<action>? methods (returning 'false' by default) to the nil class, that would allow current_user.can_<action>? even when user is not logged in. We could then dispense with long-winded checks like user_signed_in? && current_user.can_read?(resource)

I'm new to ruby so I'm not sure whether it's good practice to add methods to the nil class.

We could also add a configuration parameter to set the class which represents a non-logged-in user, in case it's not nil.

If I'm somehow using authority incorrectly and there is already an elegant way to avoid this, please let me know!

If this issue is approved I'm happy to fork, implement and send a pull request.

Add scopes for authorized resources

While this will correctly return posts a user is authorized to read:

Post.all.select{ |post| current_user.can_read? post }

...it would obviously be more efficient to let the database do this filtering. Several people have asked about Authority supporting scopes to return only items the user is authorized to access.

I hadn't tried to support that for two reasons: 1) I wanted to remain database and ORM agnostic, and 2) the phrase "authorized to access" is ambiguous; someone may be authorized to read but not edit, so should the item be shown in the index view or not?

However, I think I've changed my mind. The first objection is solved by doing scoping the way we do it in Searchlight, and the second objection can be answered with "the scope can do some minimal filtering, and it's up to the user to decide what."

I slapped my forehead when I realized that the approach from Searchlight was so obviously applicable here, and I realized it only after seeing it being done in Pundit, a library I recently stumbled across and frankly am wondering if it obsoletes Authority because it's so simple and clear. I haven't used it yet, though.

Anyway, I think I'd like to add authorization scopes in a similar fashion.

Models could assume an authorizer based on name, if it exists

A possible feature for consideration: models currently start out assuming that their authorizer is ApplicationAuthorizer. The Comment model doesn't automatically look for a CommentAuthorizer because it often makes sense to group multiple models under the same authorizer.

However, wouldn't it be sensible to at least check for the existence of an authorizer with a corresponding name? It's unlikely that you'd create a Comment model and a CommentAuthorizer and not want to hook them together.

If Comment hasn't been told what authorizer to use, should it look for CommentAuthorizer first, then failing that, assume ApplicationAuthorizer?

Error messages when the 'User' is nil in OmniAuth

An Application using Omniauth will either have the current_user method set to the user record, or nil (no one logged in)

While accessing am Authority protected resource, it will return the error

undefined_method 'can_read?' for nil:nilClass

Is there a way to say if the current_user (or the user method specified) returns nil, the methods as a default should just return false, rather than erroring ?

Rendering html output when raising exception

Hi,

I've just added Authority to a web services project that I'm working on. I've run into an issue I'm not sure how to resolve given my current experience with ruby and rails.

I've got an app that uses RocketPants as the framework for delivering JSON services. When authorisation fails through authorize_action_for in the controller it renders html with a http status of 500 rather than honouring the json output.

Take a look at https://gist.github.com/jasonelston/4721540 for some more details.

If I overwrite authorize_action_for I obviously get the correct JSON rendered output.

If I overwrite authority_forbidden I get the html rendered error (with the JSON error text).

Obviously there's something that's defaulting to rendering html output when handling the exception but I can't seem to find it. The only reference to render I can find is in the authority_forbidden.

At this point I'm at the ends of my current ability. Any help to solve this would be greatly appreciated.

Thanks,
Jason

Grouping actions

Hi @nathanl, hope you're having a great day :)

I was wondering if there is any capability to group actions but still allow for fine grained checking of abilities.

For example, CUD actions could be grouped as :manage and R actions could be grouped as :view or something like that. It's arbitrary, but is it possible to do as the current code stands?

In my case I just have :view and :edit as abilities, but in the example of admin resources it'd be nice to have :manage instead (encompassing both :view and :edit) so that I can say manageble_by?. Right now I can just say viewable_by? and know that that implies editable_by? and therefore manageable_by? but it seems less clear.

I'm not sure if I've thought this through well enough though so any ideas are welcome. I'm only in this habit through using Cancan :)

Views of controllers that don't have a resource and passing options

Views of controllers that don't have a resource and passing options

At the wiki, we can see "Using authority on controllers that don't have a resource". But how about the view?

Simple example: I whould like to have a link, on a view, only if the user has rights to it.

Models:
User,Course,Role (from rolify)
Controllers: Course and Teacher

I have a controller where only the course's owner can manage the course's teachers.
I don't have a Teacher Model. A teacher is a teacher on a Course if it has a role teacher on that course.

Since I don't have a Model, I'm using the example at "Using authority on controllers that don't have a resource".

It that example, how can I create a link, on the view, only if the user has rights to acess it?

class TeachersController < ApplicationController
before_filter :authenticate_user!
before_filter :load_course
authorize_actions_for TeacherAuthorizer, :for => @Course

So far so good, but how can I use it on the view?

= link_to 'Manage Teachers', course_teachers_path(@Course)
if current_user.can_create?(WHAT_HERE?)

I could use the User model, since the Teacher is a User, but it would require to set User.authorizer_name = TeacherAuthorizer.
But that doesn't fell right.

An other approach is to try to call the TeacherAuthorizer, in the view:

= link_to 'Manage Teachers', course_teachers_path(@Course)
if TeacherAuthorizer.creatable_by?(current_user, :for=>@Course)

But I would get a "wrong number of arguments (2 for 1)".

I think, if we could, somehow say at TeacherAuthorizer:
class TeacherAuthorizer < Authority::Authorizer
def self.auth_symbol = :teacher
Or gets from the Authorizer name of the class

And then, if we could:

= link_to 'Manage Teachers', course_teachers_path(@Course)
if current_user.can_create?(:teacher, :for=>@Course)

Hide warnings in capybara feature specs?

If I have this feature spec spec/features/brands/adding_a_brand.rb

require 'spec_helper'

feature "Adding a brand" do

  scenario "as a visitor" do
    visit new_brand_path
    expect(page.status_code).to eq(403)
  end

end

It will pass, but I will get the following WARN message in my logs

Adding a brand
W, [2013-12-01T20:37:55.842741 #45068]  WARN -- :   is not authorized to create this resource: #<Brand:0x007fc2f7c0d980>
  as a visitor

Is there a way to suppress these warnings?

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.