Git Product home page Git Product logo

action_policy's Introduction

Gem Version Build JRuby Build Documentation Coverage Status

Action Policy

Authorization framework for Ruby and Rails applications.

Composable. Extensible. Performant.

๐Ÿ“‘ Documentation

Sponsored by Evil Martians

Resources

  • RubyRussia, 2019 "Welcome, or access denied?" talk (video [RU], slides)

  • Seattle.rb, 2019 "A Denial!" talk (slides)

  • RailsConf, 2018 "Access Denied" talk (video, slides)

Integrations

Installation

Add this line to your application's Gemfile:

gem "action_policy"

And then execute:

bundle install

Usage

Action Policy relies on resource-specific policy classes (just like Pundit).

First, add an application-specific ApplicationPolicy with some global configuration to inherit from:

class ApplicationPolicy < ActionPolicy::Base
end

This may be done with rails generate action_policy:install generator.

Then write a policy for a resource. For example:

class PostPolicy < ApplicationPolicy
  # everyone can see any post
  def show?
    true
  end

  def update?
    # `user` is a performing subject,
    # `record` is a target object (post we want to update)
    user.admin? || (user.id == record.user_id)
  end
end

This may be done with rails generate action_policy:policy Post generator. You can also use rails generate action_policy:policy Post --parent=BasePolicy to make the generated policy inherits from BasePolicy.

Now you can easily add authorization to your Rails* controller:

class PostsController < ApplicationController
  def update
    @post = Post.find(params[:id])
    authorize! @post

    if @post.update(post_params)
      redirect_to @post
    else
      render :edit
    end
  end
end

* See Non-Rails Usage on how to add authorize! to any Ruby project.

When authorization is successful (i.e., the corresponding rule returns true), nothing happens, but in case of authorization failure ActionPolicy::Unauthorized error is raised.

There is also an allowed_to? method which returns true or false, and could be used, in views, for example:

<% @posts.each do |post| %>
  <li><%= post.title %>
    <% if allowed_to?(:edit?, post) %>
      <%= link_to post, "Edit">
    <% end %>
  </li>
<% end %>

Read more in our Documentation.

Alternatives

There are many authorization libraries for Ruby/Rails applications.

What makes Action Policy different? See this section in our docs.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/palkan/action_policy.

License

The gem is available as open source under the terms of the MIT License.

action_policy's People

Contributors

brendon avatar deniszackharov avatar dmitrytsepelev avatar fwolfst avatar goktugozturk avatar harrykiselev avatar kevynlebouille avatar killondark avatar lokideos avatar matsales28 avatar meatherly avatar mreinsch avatar nicolas-brousse avatar npupko avatar palkan avatar pboling avatar pdfrod avatar petergoldstein avatar pirj avatar rainerborene avatar rmacklin avatar sampokuokkanen avatar sander-deryckere avatar sdemjanenko avatar skojin avatar snuggs avatar somenugget avatar spone avatar stevenharman avatar yaroslav 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

action_policy's Issues

Overriding the inferred policy at controller level

Currently the policy name is inferred from the controller class for authorize! and allowed_to? like so:

controller_name.classify.safe_constantize

There's no way to override this globally (per controller run) currently. Would you be open to adding this as a feature?

My use case is that I have a controller that is re-used in several contexts in the site. It is aware of its context, and depending on the context a different policy should be used. I can supply the context to authorise! easy enough with the :with option, but it's much harder to do so with the view helper allowed_to? because sometimes this method is called with helpers shared across wide areas of the application and rely on the inferred controller to function cleanly.

I thought about passing the context into the controllers inferred policy, then splitting out the policy check from there but didn't get too far.

Usage of authorized_scope returns an immutable scope (using method where! raise ActiveRecord:: ImmutableRelation)

Tell us about your environment

Ruby 2.5.1
Rails 5.1.6.2
Action Policy 0.4.3

What did you do?

I added the ActionPolicy gem to my project. In index action of my controller I used methods: where! and order!.

What did you expect to happen?

When I use authorized_scope(User.all) with the method where! I expect it to change my scope.

What actually happened?

If I use authorized_scope(User.all) with where!, I get the exception: ActiveRecord:: ImmutableRelation.

Can I get immutable relation after using authorized_scope(User.all)?
I have to used method clone in my Policy like this: relation_scope { |relation| relation.where('id > 0').clone } to resolve this problem.
I think more correctly to use method clone here: https://github.com/palkan/action_policy/blob/master/lib/action_policy/authorizer.rb#L33

Thank you.

Action aliases in the controller

Here's another idea:

In my application I authorise a lot of controllers based on a simple (user editable) permissions system, so the policy structure is quite simple and has nothing to do with these controller items and more to do with their ultimate position within a tree structure and the permissions applied there. To that end I'm calling something like authorise! ComponentInstance to kick off the policy check.

Now that's all fine and good and I can define rule aliases in the policy for common actions:

  authorize :component_instance

  alias_rule :index?, :show?, to: :view?
  alias_rule :new?, :create?, :update?, :destroy?, to: :edit?

  def view?
    component_instance.has_permission_to(:view, user)
  end

  def edit?
    component_instance.has_permission_to(:edit, user)
  end

However, in some of my controllers there are other actions (e.g. sort) and some that are injected by concerns. In that case, it's illogical to keep adding those rule aliases to the policy, it'd be better to pass in the alias from the controller and I can see one can do that:

authorize! ComponentInstance, to: :view?

I was wondering what you thought of creating a method for defining rule aliases at the controller level, so I could do something like:

alias_action :sort, :jumble, to_rule: :edit?

So when you're identifying the rule to execute you could check this first, then move down to the rule aliases in the policy?

Issue calling alias_rule when initializing policies

Is intended that we can't call alias_rule from a policy instance?

class ArticlePolicy < ApplicationPolicy
  alias_rule :edit?, to: :update?

  def update?
    true
  end
end

policy = ArticlePolicy.new(Article.first, user: User.first)

This works fine:
policy.apply(:update?)
=> true

but this through an error:
policy.apply(:edit?)
=> NoMethodError (undefined method `edit?' for #ArticlePolicy:0x00007fc4bc564158)

Thanks for your help.

Best,
Benoit

Ruby Version: 2.5.1
Framework Version (Rails, whatever): 5.1.6
Action Policy Version: 0.2.0

[question] JSON of permissions per object

Hello,

This is not an issue, but rather a request for advice. I would like to have an API endpoint that returns JSON of actions allowed for an object (for example a posting) and I am going to use that information in order to enable/disable actions in frontend.

So far I've come up with something like the following.

posts_controller.rb:

  def permissions
    load_post!
    authorize! @post

    hash = %w[destroy edit full index new permissions preheader quote raw text show].map do |k|
      [k, allowed_to?("#{k}?".to_sym, @post)]
    end.to_h

    render formats: :json, json: hash.as_json
  end

which will result in something like

{"destroy": false,
 "edit": false,
 "full": true,
 "index": true,
 "new": true,
 "permissions": true,
 "preheader": true,
 "quote": true,
 "raw": true,
 "text": true,
 "show": true}

Can I do it better? I would appreciate any advices on the approach and its implementation.

Regards,
Alexander

public access

is there a way in ActionPolicy to provide public access?

e.g. in the case a user does not exist.

or should there always be a user in ActionPolicy and that is outside the scope of authorization?

Authorization subject dependent objects injection

Extracted from #1.

It's pretty easy to add support for explicit context though, e.g.:
ruby authorize! record, context: { user: @user }

I think this is what I was looking for.
Some code that might be written (partially inspired by real app that's using pundit):

class RequirementsDocPolicy
  def create?
    user.documenter?(project) &&
      (user.requirement_doc.blank? || user.requirement_doc.versions.empty?) &&
      project.document_set.state == 'preparing' &&
      user.google.uploader? # assume this makes API call
  end
end

(And now imagine not using ActiveRecord.)
We need to fetch a lot of entities actually (user, project, document_set) and make external API call.

class RequirementsDocsController
  def create
    find_user_in_google
    authorize! RequirementsDoc, context: { user: current_user,
                                           user_in_google: @user_in_google }
  end
end

If I can do so, it's ok. :)

Support Rails-compatible cache initialization

Tell us about your environment

Ruby Version: 2.5

Framework Version (Rails, whatever): Rails 5.2.1

Action Policy Version: 0.2.3

What did you do?

I have initialized Action Policy's cache in exactly the same way as Rails documentation recommends:

config.action_policy.cache_store = :mem_cache_store, ENV['MEMCACHE_SERVERS'], {namespace: "r2", compress: true, pool: 32}

What actually happened?

Exception:

vendor/bundle/jruby/2.5.0/gems/action_policy-0.2.3/lib/action_policy/policy/cache.rb:45:in `block in apply_with_cache': undefined method `read' for #<Array:0xd0fabc8> (NoMethodError)
    from org/jruby/RubyKernel.java:1301:in `yield_self'
    from vendor/bundle/jruby/2.5.0/gems/action_policy-0.2.3/lib/action_policy/policy/cache.rb:44:in `apply_with_cache'
    from vendor/bundle/jruby/2.5.0/gems/action_policy-0.2.3/lib/action_policy/policy/cache.rb:58:in `apply'
    from vendor/bundle/jruby/2.5.0/gems/action_policy-0.2.3/lib/action_policy/policy/cached_apply.rb:18:in `apply'
    from vendor/bundle/jruby/2.5.0/gems/action_policy-0.2.3/lib/action_policy/policy/reasons.rb:98:in `allowed_to?'
    from app/policies/topic_policy.rb:7:in `show?'

Global admin namespace?

Playing around with this great gem, and I feel like I'm missing something. What's the recommended way of applying say an Admin namespace to every action in a controller?

So if I have

class OrdersController
   def index
   end

   def show
   end
end

How to I make every action in the controller respond to a policy such as

class AdminPolicy < ActionPolicy::Base
  authorize :admin

  default_rule :admin?

  def admin?
    allow! if user.admin?
  end
end

I tried things like authorize :admin and authorize! :admin in the controller, but that can't find a policy, and I feel like I'm missing something obvious. Thanks!

Q: How to authorize render json?

I'm using Ajax Datatables Rails gem and was wondering, how do I apply Action Policy for my index action where I would need to respond with JSON? I'd like to restrict access to the path and getting records via DataTable (retrieves JSON).

My offers_controller.rb index action looks like this:

  def index
    respond_to do |format|
      format.html
      format.json { render json: Seller::OfferDatatable.new(
        params, view_context: view_context,
        status: params[:type],
        sellers: current_user.companies.ids
        ) }
    end
  end

In offer_datatable.rb I'm getting my records with:

  def get_raw_records
    Seller::Offer.status(status).sellers(sellers)
                 .order(published_at: :desc)
  end

and I don't have instance variable @offers to authorize.

I'd be happy for any hint. Thank you.

Does not reload code changes when using namespaces

Hi, I have a controller(Subscriptions) inside the Dashboard module with action manage, eg (app/controllers/dashboard/subscriptions_controller.rb)

module Dashboard
  class SubscriptionsController < BaseController
     def manage
        authorize! Subscription.new
     end
  end
end

And my policies structure app/policies/dashboard/subscription_policy.rb

module Dashboard
  class SubscriptionPolicy < ApplicationPolicy
    def manage?
       true
    end
  end
end

When I change the policy(manage?) to false, then the changes do not apply, but everything works fine when I do not use namespaces, and I take subscription_policy to the root of the app / policies directory

Ruby 2.5.1
Rails 5.2.0
Action Policy 0.2.0

Scope support

Provide ability to scope relations (e.g. ActiveRecord).

Defining scopes

class UserPolicy < ApplicationPolicy
   # use DSL for defining scopes
   # default scope
   scope do
     # this block is executed within a policy object,
     # so all the methods are available (e.g. authorization contexts)
     # `relation` is a passed to a helper object
     relation.joins(:accesses).where(accesses: { user_id: user.id })
   end

   # named scop
   scope :own do
     relation.where(user_id: user.id)
   end
end

Applying scopes

def index
  @posts = scoped(Post.all)
   
  # or
  
  @posts = scoped(Post.all, :own)
end

To discuss

  • Is relation a good name for a passed scope within a policy?

  • Is scoped a good name for a helper? I don't like Pundit's policy_scope, it's too verbose and doesn't sound like English, IMO.

default_rule and manage? does not work as expected

Tell us about your environment

Ruby Version: 2.6.1

Framework Version (Rails, whatever): Rails 5.2.2

Action Policy Version: 0.3.0

What did you do?

I have a rather boring Ruby on Rails application with following setup:

# custom "base" controller
class NeserituController < ApplicationController
  # ...
  verify_authorized if !Rails.env.production?
  # ...
  rescue_from ActionPolicy::Unauthorized do |exception|
      redirect_back fallback_location: root_path,
        flash: {alert: :not_authorized }
    end
  # ...
  end
# application_policy.rb
class ApplicationPolicy < ActionPolicy::Base
  authorize :user, through: :current_user, allow_nil: true
end
# seminar_kind_policy.rb
class SeminarKindPolicy < ApplicationPolicy
  #default_rule :manage? #(doesnt make a difference if uncommented)
  def manage?
    user.admin?
  end
end
class Admin::SeminarKindsController < NeserituController
  before_action :authenticate_user!           

  def index
    @seminar_kinds = SeminarKind.all                         
    authorize!                                                     
  end  

What did you expect to happen?

According to https://actionpolicy.evilmartians.io/#/aliases , the controller should ask the policy if index? is good to go, which should resolve to manage?. Logged in as an admin-user (user.admin == true), I should be able to visit the path and see the correct view.

What actually happened?

But when visiting the relevant path to the index-action of last controller (admin_seminar_kinds_path), I get redirected and a "not authorized" message.
If I rename the manage? definition in the policy to index?, or manually create an alias (alias_rule :index?, to: :manage?) it works.

Rule aliases in an inheriting policy don't override aliases in parent policies

Tell us about your environment

Ruby Version:
2.4.3 (in Docker)

Framework Version (Rails, whatever):
Rails 5.2.0.rc2

Action Policy Version:
0.2.0

What did you do?

class ApplicationPolicy < ActionPolicy::Base; end

class GroupPolicy < ApplicationPolicy
  def new?
    user.staff?
  end
  alias_rule :create?, to: :new?
end

What did you expect to happen?

For GroupPolicy#create? to be aliased to GroupPolicy#new?

What actually happened?

GroupPolicy#create? was actually still aliased to ActionPolicy::Base#new? (because of the alias_rule :create, to: :new? in ActionPolicy::Policy::Defaults, I think) and so always returned false. The solution was to define another create? method that called new? explicitly.

(Sorry, I accidentally submitted this before I'd finished it)

Add policy generator

Hi!

I'm currently locking to use action_policy instead of pundit for our new projects.
And I notice that there is no generator to create new policies rails g policy user.

Is there any particular reason? Does it something that could interest you?

mistype and default_rule and manage?

I just have problem, because I make mistype, and instead of create? type create.
And I expected that it will fail, but instead it fallback to default manage? rule. For me this behaviour looks strange and add unexpected magic.

I off this behaviour if default_rule nil

Ability to pass decorated object

It will be great to pass decorated object to the public methods in case we use something like drapper.

To implement this you need to try to_model on record before accessing record.
You can utilize ActionView::ModelNaming and convert_to_model(record)

Policy Caching will trigger a database request when using `authorized`.

Tell us about your environment

Ruby Version:
ruby 2.5.3p105 (2018-10-18 revision 65156) [x86_64-darwin17]

Framework Version (Rails, whatever):
Rails 5.1.6.1

Action Policy Version:
Master Branch, 913b662

What did you do?

Lets say we have a model EventRecord, with hundred of thousands of records in the database.

Consider this simple policy:

class EventRecordPolicy < ApplicationPolicy
  relation_scope do |scope|
    if user.admin?
      scope
    else
      scope.none
    end
  end
end

And using authorized(EventRecord.all) in the controller to have scope type inferred automatically.

What did you expect to happen?

No request is made until the scope is actually used.

What actually happened?

The following request is always issued, whether or not the scope is actually used.
SELECT COUNT(*) AS "size", MAX("events"."updated_at") AS timestamp FROM "events". This request has the potential of being really expensive if there's a large quantity of records being queried.

Why does it happen?

As part of the Memoization that happens here,

def __policy_memoize__(record, with: nil, namespace: nil, **_opts)
record_key = record._policy_cache_key(use_object_id: true)
cache_key = "#{namespace}/#{with}/#{record_key}"

The cache_key method is invoked on the record, which in this case is an ActiveRecord::Relation
return cache_key if respond_to?(:cache_key)

ActiveRecord::Relation happens to also implement a cache_key method, which will trigger the above request since the collection isn't already loaded. In the end, even if the scope isn't used (ex: chained with .none like in the policy above), the potentially costly request will already have fired.

Exception information missing

Tell us about your environment

Ruby Version: 2.4

Framework Version (Rails, whatever): 5.0

Action Policy Version: 0.1.4

What did you do?

Handling an authorization exception as described in the documentation

What did you expect to happen?

Failure reason messages to be returned

What actually happened?

There's no reasons.messages. The string messages appears exactly twice in the source code โ€” in a class file comment and the doc file that is generated from it.

I18n integration

  • Exception message:
rescue_from ActionPolicy::Unauthorized do |ex|
    p ex.message #=> "You do not have access to the stage"
end
  • Reasons integration:
# when we have the reasons object
reasons.details #=> { stage: [:show?] }
# we can generate localized messages with i18n
reasons.full_messages #=> ["You do not have access to the stage"]

In locales:

en:
  action_policy:
    policy:
      stage:
        show?: "You do not have access to the stage"
        # default message for exceptions and reasons
    default_message: "You are not authorized to perform this action"

RSpec view spec, missing policy authorization context

Tell us about your environment

Ruby Version: ruby 2.6.5p114 (2019-10-01 revision 67812) [x86_64-darwin18]

Framework Version (Rails, whatever): Rails 6.0.0

Action Policy Version: 0.3.2

What did you do?

I'm testing some views of my app that use allowed_to? helper.

What did you expect to happen?

Having my test working normally. Or to know what could I change in my test to avoid this.

What actually happened?

Failures:

  1) admin/users/edit.html.slim displays user#edit correctly 
     Failure/Error: - if allowed_to?(:edit?, @user)
     
     ActionView::Template::Error:
       Missing policy authorization context: user
     # ./app/views/admin/users/_form.html.slim:19:in `block in _app_views_admin_users__form_html_slim__2614983762995680549_70232191255660'
     # ./app/views/admin/users/_form.html.slim:9:in `_app_views_admin_users__form_html_slim__2614983762995680549_70232191255660'
     # ./app/views/admin/users/edit.html.slim:1:in `_app_views_admin_users_edit_html_slim__69430724083846117_70232190867720'
     # ./spec/views/admin/users/edit.html.slim_spec.rb:13:in `block (3 levels) in <top (required)>'
     # ------------------
     # --- Caused by: ---
     # ActionPolicy::AuthorizationContextMissing:
     #   Missing policy authorization context: user
     #   ./app/views/admin/users/_form.html.slim:19:in `block in _app_views_admin_users__form_html_slim__2614983762995680549_70232191255660'

authorize! with no arguments not resolving namespace

Tell us about your environment

Ruby Version:
2.4.3

Framework Version (Rails, whatever):
Rails 5.2.0.rc2

Action Policy Version:
0.1.4

What did you do?

Called authorize! from Groups::AnnouncementsController#show (/app/controllers/groups/announcements_controller.rb) with no arguments.

What did you expect to happen?

For the policy Groups::AnnouncementPolicy (/app/policies/groups/announcement_policy.rb) or Group::AnnouncementPolicy (/app/policies/group/announcement_policy.rb) (tried both) to be found and to authorise #show.

What actually happened?

ActionPolicy::NotFound in Groups::AnnouncementsController#show
Couldn't find policy class for Announcement(...)

Moving the policy out of a directory and calling it ::AnnouncementPolicy works just fine, but I'd rather keep the namespace :)

For reference, the policy as-is:

class AnnouncementPolicy < ApplicationPolicy
  def show?
    true
  end
end

Policy actions for model association managed through nested resource controller

I have a NewsPost model which has a HABTM association to Users as :readers.
The purpose is basically to mark posts read to know who and how many have read it.

This is an API so I have the follow request:
GET /api/v1/news/:news_post_id/readers # Get list of users who read
POST /api/v1/news/:news_post_id/readers # Add the current user to readers
GET /api/v1/news/:news_post_id/readers/count # Just get the latest readers count

This maps to a NewsPostReadersController with index, create, count actions.
A before_action sets the news post for all these actions and calls authorize!

So here is the conundrum. Do I...

  1. Create a NewsPostReaderPolicy just for this "virtual resource"?
  2. Use the NewsPostPolicy but map associated actions?

1 didn't feel right because I was authorizing a NewsPost, so I went with 2:

def set_news_post
  @news_post = NewsPost.find(params[:news_post_id])
  policy_action = :"#{action_name}_readers?"
  authorize! @news_post, to: policy_action
  ...
end

This means index_readers?, create_readers? and count_readers?

What surprised me was that without defining those in my policy, they all passed. I think this was because I defined manage?? Is that correct?

Is there a better way to handle this situation with action_policy or should there be some api in the policy to define actions done on associations?

Thanks for your time! Saw your AnyCable presentation at Ruby Kaigi in Sendai and thought it was one of the best presentations. ;)

Custom Authorization Context is Broken in Rails Production Mode

When running a Rails app in production mode, and we've set up a custom authorization context in the ApplicationController, a boot-time exception is raised.

`<class:ApplicationController>': undefined method `authorize' for ApplicationController:Class (NoMethodError)

Looking into it, it seems the ActionPolicy Railtie's config.after_initialize isn't firing in time. Meaning the ActiveSupport.on_load(:action_controller) block isn't being run in time. Thus, when our ApplicationController is loaded, the ::authorize method isn't yet defined.

I've reproduced this with a vanilla Rails 5.2 app, as laid out below.

Tell us about your environment

Ruby Version: 2.5.1

Framework Version: Rails 5.2.0

Action Policy Version: 0.3.1

What did you do?

Create a new Rails app and launch it in production mode:

rails new blog
cd blog
bin/rails db:migrate
bin/rails db:setup
bin/rails g controller Welcome index
RAILS_ENV=production bin/rails s

Then open a browser to http://localhost:3000/welcome/index and you'll see the generated welcome page. Now, add ActionPolicy by adding gem 'action_policy' to the Gemfile. Then:

bundle
RAILS_ENV=production bin/rails s

Then open a browser to http://localhost:3000/welcome/index and you'll see the generated welcome page. This is as expected. Next, customize the authorization context thusly:

class ApplicationController < ActionController::Base
  authorize :account, through: :current_account

  def current_account
    OpenStruct.new(name: 'Alice')
  end
end

Again, start rails in production mode. This time an exception is raised when trying trying to boot.

RAILS_ENV=production bin/rails s
=> Booting Puma
=> Rails 5.2.0 application starting in production
=> Run `rails server -h` for more startup options
Exiting
Traceback (most recent call last):
	83: from bin/rails:4:in `<main>'
	82: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activesupport-5.2.0/lib/active_support/dependencies.rb:283:in `require'
	81: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activesupport-5.2.0/lib/active_support/dependencies.rb:249:in `load_dependency'
	80: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activesupport-5.2.0/lib/active_support/dependencies.rb:283:in `block in require'
	79: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/bootsnap-1.3.0/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:29:in `require'
	78: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/bootsnap-1.3.0/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:20:in `require_with_bootsnap_lfi'
	77: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/bootsnap-1.3.0/lib/bootsnap/load_path_cache/loaded_features_index.rb:65:in `register'
	76: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/bootsnap-1.3.0/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:21:in `block in require_with_bootsnap_lfi'
	75: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/bootsnap-1.3.0/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:21:in `require'
	74: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/railties-5.2.0/lib/rails/commands.rb:18:in `<main>'
	73: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/railties-5.2.0/lib/rails/command.rb:46:in `invoke'
	72: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/railties-5.2.0/lib/rails/command/base.rb:65:in `perform'
	71: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/thor-0.20.0/lib/thor.rb:387:in `dispatch'
	70: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/thor-0.20.0/lib/thor/invocation.rb:126:in `invoke_command'
	69: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/thor-0.20.0/lib/thor/command.rb:27:in `run'
	68: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/railties-5.2.0/lib/rails/commands/server/server_command.rb:142:in `perform'
	67: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/railties-5.2.0/lib/rails/commands/server/server_command.rb:142:in `tap'
	66: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/railties-5.2.0/lib/rails/commands/server/server_command.rb:147:in `block in perform'
	65: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/railties-5.2.0/lib/rails/commands/server/server_command.rb:53:in `start'
	64: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/rack-2.0.4/lib/rack/server.rb:283:in `start'
	63: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/rack-2.0.4/lib/rack/server.rb:354:in `wrapped_app'
	62: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/railties-5.2.0/lib/rails/commands/server/server_command.rb:27:in `app'
	61: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/rack-2.0.4/lib/rack/server.rb:219:in `app'
	60: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/rack-2.0.4/lib/rack/server.rb:319:in `build_app_and_options_from_config'
	59: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/rack-2.0.4/lib/rack/builder.rb:40:in `parse_file'
	58: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/rack-2.0.4/lib/rack/builder.rb:49:in `new_from_string'
	57: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/rack-2.0.4/lib/rack/builder.rb:49:in `eval'
	56: from config.ru:in `<main>'
	55: from config.ru:in `new'
	54: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/rack-2.0.4/lib/rack/builder.rb:55:in `initialize'
	53: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/rack-2.0.4/lib/rack/builder.rb:55:in `instance_eval'
	52: from config.ru:3:in `block in <main>'
	51: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/bootsnap-1.3.0/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:44:in `require_relative'
	50: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activesupport-5.2.0/lib/active_support/dependencies.rb:283:in `require'
	49: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activesupport-5.2.0/lib/active_support/dependencies.rb:249:in `load_dependency'
	48: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activesupport-5.2.0/lib/active_support/dependencies.rb:283:in `block in require'
	47: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/bootsnap-1.3.0/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:29:in `require'
	46: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/bootsnap-1.3.0/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:20:in `require_with_bootsnap_lfi'
	45: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/bootsnap-1.3.0/lib/bootsnap/load_path_cache/loaded_features_index.rb:65:in `register'
	44: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/bootsnap-1.3.0/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:21:in `block in require_with_bootsnap_lfi'
	43: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/bootsnap-1.3.0/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:21:in `require'
	42: from /Users/alice/code/tmp/blog/config/environment.rb:5:in `<main>'
	41: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/railties-5.2.0/lib/rails/application.rb:361:in `initialize!'
	40: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/railties-5.2.0/lib/rails/initializable.rb:60:in `run_initializers'
	39: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/2.5.0/tsort.rb:205:in `tsort_each'
	38: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/2.5.0/tsort.rb:226:in `tsort_each'
	37: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/2.5.0/tsort.rb:347:in `each_strongly_connected_component'
	36: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/2.5.0/tsort.rb:347:in `call'
	35: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/2.5.0/tsort.rb:347:in `each'
	34: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/2.5.0/tsort.rb:349:in `block in each_strongly_connected_component'
	33: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/2.5.0/tsort.rb:431:in `each_strongly_connected_component_from'
	32: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/2.5.0/tsort.rb:350:in `block (2 levels) in each_strongly_connected_component'
	31: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/2.5.0/tsort.rb:228:in `block in tsort_each'
	30: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/railties-5.2.0/lib/rails/initializable.rb:61:in `block in run_initializers'
	29: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/railties-5.2.0/lib/rails/initializable.rb:32:in `run'
	28: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/railties-5.2.0/lib/rails/initializable.rb:32:in `instance_exec'
	27: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/railties-5.2.0/lib/rails/application/finisher.rb:69:in `block in <module:Finisher>'
	26: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/railties-5.2.0/lib/rails/application/finisher.rb:69:in `each'
	25: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/railties-5.2.0/lib/rails/engine.rb:356:in `eager_load!'
	24: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/railties-5.2.0/lib/rails/engine.rb:475:in `eager_load!'
	23: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/railties-5.2.0/lib/rails/engine.rb:475:in `each'
	22: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/railties-5.2.0/lib/rails/engine.rb:477:in `block in eager_load!'
	21: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/railties-5.2.0/lib/rails/engine.rb:477:in `each'
	20: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/railties-5.2.0/lib/rails/engine.rb:478:in `block (2 levels) in eager_load!'
	19: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activesupport-5.2.0/lib/active_support/dependencies.rb:242:in `require_dependency'
	18: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/bootsnap-1.3.0/lib/bootsnap/load_path_cache/core_ext/active_support.rb:59:in `depend_on'
	17: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activesupport-5.2.0/lib/active_support/dependencies.rb:326:in `depend_on'
	16: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activesupport-5.2.0/lib/active_support/dependencies.rb:348:in `require_or_load'
	15: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activesupport-5.2.0/lib/active_support/dependencies.rb:37:in `load_interlock'
	14: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activesupport-5.2.0/lib/active_support/dependencies/interlock.rb:13:in `loading'
	13: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activesupport-5.2.0/lib/active_support/concurrency/share_lock.rb:151:in `exclusive'
	12: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activesupport-5.2.0/lib/active_support/dependencies/interlock.rb:14:in `block in loading'
	11: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activesupport-5.2.0/lib/active_support/dependencies.rb:37:in `block in load_interlock'
	10: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activesupport-5.2.0/lib/active_support/dependencies.rb:370:in `block in require_or_load'
	 9: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activesupport-5.2.0/lib/active_support/dependencies.rb:283:in `require'
	 8: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activesupport-5.2.0/lib/active_support/dependencies.rb:249:in `load_dependency'
	 7: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activesupport-5.2.0/lib/active_support/dependencies.rb:283:in `block in require'
	 6: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/bootsnap-1.3.0/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:29:in `require'
	 5: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/bootsnap-1.3.0/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:20:in `require_with_bootsnap_lfi'
	 4: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/bootsnap-1.3.0/lib/bootsnap/load_path_cache/loaded_features_index.rb:65:in `register'
	 3: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/bootsnap-1.3.0/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:21:in `block in require_with_bootsnap_lfi'
	 2: from /Users/alice/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/bootsnap-1.3.0/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:21:in `require'
	 1: from /Users/alice/code/tmp/blog/app/controllers/application_controller.rb:1:in `<main>'
/Users/alice/code/tmp/blog/app/controllers/application_controller.rb:2:in `<class:ApplicationController>': undefined method `authorize' for ApplicationController:Class (NoMethodError)

What did you expect to happen?

I would expect the app to boot and for ActionPolicy to be hooked up, with a customized authorization context.

What actually happened?

An exemption at boot time.

Support only/except options for additional context authorizations

The explicit context feature (https://actionpolicy.evilmartians.io/#/authorization_context?id=explicit-context) is useful for passing in additional context params that determine the result of a Policy rule.

class FooPolicy < ApplicationPolicy
  authorize :another_param, allow_nil: true

  def route_one?
    another_param == 'bar'
  end

  def route_two?
    true
  end
end

However, I am finding that the default ApplicationPolicy#authorize definition is a bit too restrictive. If I have some routes that need the additional contexts, but some don't, then I need to pass nil in in order to avoid the ActionPolicy::AuthorizationContextMissing error.

Am I missing something regarding how to implement this? It would be great if we could define something like authorize :another_context, except: route_two?.

Ruby 2.3 compatibility

Tell us about your environment

Ruby Version: 2.3.7

Framework Version (Rails, whatever): Rails 5.1.4

Action Policy Version: 0.1.3

What did you do?

I use namespaces in my application.

What did you expect to happen?

I expected Action Policy to work under Ruby 2.3

What actually happened?

This line uses String#match? method, which is not available in Ruby 2.3. That's why I've got

Processing by Admin::HomeController#index as HTML
Completed 500 Internal Server Error in 8ms (ActiveRecord: 0.0ms)


  
NoMethodError (undefined method `match?' for "Admin":String
Did you mean?  match):
  
app/controllers/admin/home_controller.rb:5:in `index'

Update 1

I've run the library test suite and apparently it's the only method from Ruby 2.5 and missing in 2.3. I could submit a fix, but I need a hint on what would be a correct idiomatic solution.

Update 2

This exception is raised in non-namespaced controllers as well. This means ActionPolicy cannot be used under 2.3. I guess the only workaround is to construct a custom base policy without namespace support.

Apply rule defaults and aliases more thoroughly?

I'm wondering if there's a reason not to have Core#__apply__ call resolve_rule before public_send, like this:

def __apply__(rule)
  rule = resolve_rule(rule)
  public_send(rule)
end

This would support default rules and rule aliases across the board instead of just for controller actions, including calling check?/allowed_to? from within policies, policy.apply from anywhere, and supporting 3rd party gems (like ActiveAdmin) that may use their own mechanisms to authorize controller actions.

Override the inferred record class at the controller level

Would you consider adding the functionality where one could define the model class at the controller level? Currently I have to do something like this:

class Admin::ActivitiesController < Global::AdminController
  
  private

  def authorize!
    super PublicActivity::Activity
  end

  def allowed_to?(rule)
    super rule, PublicActivity::Activity
  end
end

Perhaps we could define this class at a class level once?

Question: Using next vs return in Scoping

Hey!

I was looking at the doc and wondering if there's a difference between using next vs return in the _scope blocks. The example are all using next.

eg:

class PostPolicy < ApplicationPolicy
  relation_scope do |relation|
    next relation if user.admin?
    relation.where(user: user)
  end
end

vs

class PostPolicy < ApplicationPolicy
  relation_scope do |relation|
    return relation if user.admin?
    relation.where(user: user)
  end
end

Dependent policy loses track of dependee failure reason

Running from master, with ruby 2.5.3 and rails 5.2.1.

If I have two policies, one of which is dependent on the other:

class DependeePolicy
  def do_it?
    check?(:a?) && check?(:b?)
  end

  def a?
    true
  end

  def b?
    false
  end
end
class DependerPolicy
  def do_it?
    check?(:do_it?, record.dependee) && check?(:c?)
  end

  def c?
    true
  end
end

Sometimes I authorize the Dependee on its own:

begin
  authorize! dependee, to: :do_it?
rescue ActionPolicy::Unauthorized => e
  puts e.result.reasons.details # { :dependee => [:b?] }
end

Sometimes I authorize the Depender instead, but:

begin
  authorize! depender, to: :do_it?
rescue ActionPolicy::Unauthorized => e
  puts e.result.reasons.details # { :dependee => [:do_it?] }
end

I would expect the reason to still be { :dependee => [:b?] }, or a chain of failures { :depender => [:do_it?], dependee => [:do_it?, :b?] }. We lose track on the underlying real reason otherwise, especially for i18n messages.

(Awesome gem by the way!)

Delay pre-check short-circuit until all pre-cheks are done?

Currently calling allow! or deny! in a pre-check short-circuits and returns the verdict immediately. Would you consider the concept of perhaps delaying the short-circuit until all pre-checks have run (especially useful in the case of some pre-checks being in sub-classes.

Here's my example:

ApplicationPolicy has a pre-check for user.administrator? and will allow! if so.
A SubClass has another pre-check for object.deleted? and will deny! if so. Unfortunately this never sees the light of day with an administrator and they'll be able to muck around with deleted objects.

Perhaps instead, all pre-checks can run, then if any returned deny! then deny, otherwise if any return allow! then allow. If none of the pre-checks return a verdict then process the policy action as usual.

What do you think?

Best practice for authorising Rails controller methods without a model

We have a number of Rails controllers that are not directly tied to a model. For example, in our app, the AdminController does not have a corresponding Admin model and the StaffController links ultimately to the User model. In these instances, we can use something like authorize! with: AdminPolicy to specify.

Is there a way to do this more tidily, without using with:? For example, by falling back on a policy that matches the controller name instead of a model name?

We've tried to do it by adding a custom probe to the lookup chain, but the record in these cases is nil and we can't find a robust way of accessing the controller name from the probe.

New === in matches? method inside testing.rb fails to test class equality

Tell us about your environment

Ruby Version:
2.6.1
Framework Version (Rails, whatever):
Rails 5.2
Action Policy Version:
0.40.0

What did you do?

Upgraded from 0.31.1 to 0.40.0

What did you expect to happen?

Spec be_authorized_to to pass as usual.

What actually happened?

Spec failed with following error:

expected Finance::ChargeTypes::ChargeType(id: uuid, name: string, mode_of_transport: string, charge_basis: string, created_at: datetime, updated_at: datetime, vat_applicable: boolean, twenty_ft_dv: boolean, forty_ft_dv: boolean, forty_ft_hc: boolean, forty_five_ft_hc: boolean, hazardous: boolean, overweight: boolean, reefer: boolean, deleted_at: datetime) to be authorized with ChargeTypePolicy#index?, but the following calls were encountered:
       - Finance::ChargeTypes::ChargeType(id: uuid, name: string, mode_of_transport: string, charge_basis: string, created_at: datetime, updated_at: datetime, vat_applicable: boolean, twenty_ft_dv: boolean, forty_ft_dv: boolean, forty_ft_hc: boolean, forty_five_ft_hc: boolean, hazardous: boolean, overweight: boolean, reefer: boolean, deleted_at: datetime) was authorized with ChargeTypePolicy#index?

Here's the spec code:

it 'is authorized' do
    expect { subject }.to be_authorized_to(:index?, ::Finance::ChargeTypes::ChargeType).with(ChargeTypePolicy)
end

And here's the code that runs in subject:

def call(**_any)
    authorize!(::Finance::ChargeTypes::ChargeType, to: :index?, with: ::ChargeTypePolicy)
    # .... rest of code
end

I understand the need to check for an_instance_of(Post) for which === makes sense, however I would suggest to change the matches? method in testing.rb code to:

def matches?(policy_class, actual_rule, target)
    policy_class == policy.class &&
      (target === policy.record || target == policy.record) &&
      rule == actual_rule
end

or something similar.

Thank you in advance

doc examples unclear: singular or plural?

Tell us about your environment

Framework Version (Rails, whatever):
Rails 5.2.2

Action Policy Version:
0.2.4

What did you do?

Read the docs

What did you expect to happen?

Consistency, or mentioning of plural vs singular usage of Post(s)Policy in the introductory examples.

What actually happened?

After interpreting the examples I became confused.

While it could make sense (and probably is mentioned deeper down in the doc), that
authorize! (@users = User.all) authorizes with the UsersPolicy (note the ss), it apparently tries to do so against UserPolicy.

If you want, I can adjust the examples and write a line that in principle plural naming is possible, but the infered model name is singular and thus automatic lookup will not work. Or maybe, I miss something.

undefined method `executor'

Tell us about your environment

Ruby Version:
ruby 2.3.6p384 (2017-12-14 revision 61254) [x86_64-darwin17]

Framework Version (Rails, whatever):
Rails 4.2.10

Action Policy Version:
action_policy (0.2.0)

What did you do?

Trying to boot the application after installing action_policy.

What did you expect to happen?

The application starts.

What actually happened?

The application crashes:

XYZ[80286]: bundler: failed to load command: puma (/Users/brendon/.rbenv/versions/2.3.6/bin/puma)
XYZ[80286]: NoMethodError: undefined method `executor' for #< XYZ::Application:0x00007f80f2ecfd40>
XYZ[80286]:   /Users/brendon/.rbenv/versions/2.3.6/lib/ruby/gems/2.3.0/gems/action_policy-0.2.0/lib/action_policy/railtie.rb:48:in `block in <class:Railtie>'

Is action_policy compatible with Rails 4.2? :)

Policy lookup does not work with SimpleDelegator

Tell us about your environment

Ruby Version:

2.6.3

Framework Version (Rails, whatever):

None.

Action Policy Version:

0.3.1

What did you do?

Used SimpleDelegator to decorate an object and send it to the policy.

What did you expect to happen?

Expect the correct policy to be resolved.

What actually happened?

~/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/action_policy-0.3.1/lib/action_policy.rb:32:in `lookup': Couldn't find policy class for #<Post:0x00007fe5234431c0> (ActionPolicy::NotFound)

How to reproduce?

#!/usr/bin/env ruby

require "bundler/inline"

gemfile true do
  source "https://rubygems.org"

  gem "action_policy"
end

require "action_policy"

class User
end

class Post
end

class PostPolicy < ActionPolicy::Base
  def update?
    true
  end
end

class PostDecorator < SimpleDelegator
end

class PostUpdateAction
  include ActionPolicy::Behaviour

  # provide authorization subject (performer)
  authorize :user

  attr_reader :user

  def initialize(user)
    @user = user
  end

  def call(post)
    authorize! post, to: :update?
  end
end

user = User.new
post = Post.new

puts PostUpdateAction.new(user).call(post)

post_decorator = PostDecorator.new(post)

puts PostUpdateAction.new(user).call(post_decorator)

Question: Calling a scope from another scope

Hey!

We are currently migrating from Pundit to ActionPolicy and I found a case I'm not sure how to handle. Here is an simplified example of what I'm trying to do

relation_scope(:edit) do |scope|
  # The next line is what I would like to do
  teachers = authorized(Teacher.all, as: :edit)
  scope
    .joins(:teachers)
    .where(teacher_id: teachers)
end

Is there a way currently to do that?

Namespaced policy not found

Current scenario:

# models/appointment.rb
class Appointment
end

# policies/care/appointment_policy.rb
module Care
  class AppointmentPolicy < ApplicationPolicy
  end
end

# controllers/care/appointments_controller.rb
module Care
  class AppointmentsController < BaseController
    def index
      authorize!
    end
  end
end

# => ActionPolicy::NotFound in Care::AppointmentsController#index 
# => Couldn't find policy class for Appointment

Ruby version: 2.5.0
Rails: 5.2.0

Errors seems to be that when this check is made:

mod.const_defined?(policy_name, false)

The Care namespace only contains this constants:

mod.constants
=> [:AppointmentsController, :BaseController]

The weirdest thing is that my test are working:

# frozen_string_literal: true

require "rails_helper"

RSpec.describe "GET /care/appointments", type: :request do
  before { sign_in(user) }

  subject { get care_appointments_path }

  context "as a caretaker" do
    let(:user) { create(:caretaker, :confirmed) }

    it "is authorized" do
      expect { subject }.to be_authorized_to(:index?, Appointment)
        .with(Care::AppointmentPolicy)
    end
  end
end

Any idea what could be the issue here?

Intermittent `UnrecognizedScopeTarget` under JRuby

Environment

Ruby Version: JRuby 9.2.7
Framework Version (Rails, whatever): Rails 5.2.3
Action Policy Version: 0.3.1

What did you do?

I am observing ActionPolicy::UnrecognizedScopeTarget occasionally upon the application's start. It happens for different models. A typical stacktrace is

vendor/bundle/jruby/2.5.0/gems/action_policy-0.3.1/lib/action_policy/policy/scoping.rb:105:in `resolve_scope_type': Couldn't infer scope type for Topic::ActiveRecord_Relation instance (ActionPolicy::UnrecognizedScopeTarget)
    from vendor/bundle/jruby/2.5.0/gems/action_policy-0.3.1/lib/action_policy/behaviours/scoping.rb:28:in `authorization_scope_type_for'
    from vendor/bundle/jruby/2.5.0/gems/action_policy-0.3.1/lib/action_policy/behaviours/scoping.rb:17:in `authorized_scope'
    from app/controllers/topics_suggest_controller.rb:44:in `topic_load'

Where topic_load is

  def topic_load
    @topic = authorized_scope(Topic.all).find params[:id]
    authorize! @topic
  end

This never happens during tests or development, being it MRI or JRuby. The application production always runs under JRuby.

Experiments

Since JRuby has fair threads, I thought it could be caused by some lack of thread safety. I have seen a similar problem before rails/rails#23699 (comment) , and I already have a special Rails route to pre-warm the application after start up. So I have simply put the following into that controller action:

    # warm up Action Policy scopes
    authorized_scope(Post.all)

The errors are gone. This makes me think that a problem with tread-safety in resolving scopes is very likely.

Regards,
Alexander

Authorizing method access

Hey there,

in GraphQL there's a way to do field level authorization, is it possible to do the same with action_policy?

e.g.

if I have model Post that has a description, title and is_draft

I want to say title is accessible to everyone and description is only accessible if the post is not a draft

how would you do this in the policy?

cannot load such file -- action_policy/middleware (LoadError) after upgrading to 0.2.1

**Ruby Version: 2.5.1

**Framework Version (Rails, whatever): rails 5.2.0

**Action Policy Version: 0.2.1

What did you do?

updating from Action Policy 0.2.0 to 0.2.1

What did you expect to happen?

normal behavior.

What actually happened?

i can't launch rails server
`require': cannot load such file -- action_policy/middleware (LoadError)

Rollback

rolling back to Action Policy 0.2.0 resolves the problem.

Scoping ActionController::Parameters doesn't work when passing a custom policy with `with:`

Hey!

I came across a potential problem when scoping params with authorized_scope, when trying to use a specific policy, for example:

class CourseContentsController
  def create
    ...
    authorized_scope(params.require(:course_content), with: CoursePolicy)
    ...
  end
end

Will raise a ActionController::UnfilteredParameters: unable to convert unpermitted parameters to hash error.

Also related, but maybe not a bug, but the default implicit_authorization_target injected here:

def implicit_authorization_target
controller_name.classify.safe_constantize
end

will output Couldn't find policy class for nil when unable to safe_constantize the controller. Maybe that is not possible, but I think it would be helpful for the message to read Couldn't find policy class for CourseContent or maybe a different message since it is not able to find a matching class?

I created a PR with failing tests to better demonstrate the issue.

Testing authorization of not yet persisted records

Tell us about your environment

Ruby Version:
2.6.5

Action Policy Version:
0.3.4

What did you do?

Consider the following controller:

class PostsController < ApplicationController
  def create
    @post = Post.new(post_params)
    authorize! @post
    if @post.save
      redirect_to @post
    else
      render :show
    end
  end
end

What did you expect to happen?

it "authorize the post do
  expect { post :create, params: { post: post_payload } }
    .to be_authorized_to(:create?, what_do_i_use_here)
end

The examples all cover an existing object, but i'm not exactly sure of what I should be comparing in what_do_i_use_here?

Passing mutation arguments to policy

Is there a easier way to pass mutation arguments to policy

Right now im wrapping record and arguments to hash

def resolve(id:, attributes:)
    record = Record.find(id)

    authorize!({ record: record, attributes: attributes }, with: RecordPolicy, to: :edit?)

I tried to add attributes to authorization context, but queries also start require attributes even if i made it nullable

authorize :attributes, allow_nil: true

authorization context is broken in 0.4.x version

Tell us about your environment

Ruby 2.4.7
Rails 6.0.2.1
Action Policy 0.4.3

What did you do?

I upgraded ActionPolicy from 0.3.x and Rails 6.0.1 to newer version

What did you expect to happen?

my app works as usual

What actually happened?

there is error with authorization_context method

so I have

authorize :current_store
verify_authorized

on my base controller

but current_store is nil in athorization_context[:current_store]

so I have to add authorization_context[:current_store] = current_store in before_action callback to manually set current_store

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.