Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Authorization fails with Admin namespaced decorator #7933

Open
rogerkk opened this issue Apr 21, 2023 · 1 comment · May be fixed by #7934
Open

Authorization fails with Admin namespaced decorator #7933

rogerkk opened this issue Apr 21, 2023 · 1 comment · May be fixed by #7934

Comments

@rogerkk
Copy link
Contributor

rogerkk commented Apr 21, 2023

I have a working Pundit setup, but I'm now trying to put my pundit policies under the Admin namespace (config.pundit_policy_namespace = :admin).

I'm decorating some objects with a decorator class that is also in the Admin namespace, and this seems to cause authorization to fail.

I suspect that this line in PunditAdapter#namespace is where things go sour since it's passed an instance of the (namespaced) decorator.

Could a possible fix here be to make sure to undecorate any resource passed to AuthorizationAdapter#initialize, for example using ResourceController::Decorators.undecorate(resource)?

Expected behavior

When calling PunditAdapter#authorized? the return value should be based on the policy for the decorated resource.

Actual behavior

PunditAdapter#authorized? looks for a policy for the decorator class, not finding it, and thus ends up raising an error (or basing the return value on the default policy, if defined).

How to reproduce

I've set up a template below that tests 3 cases:

  1. Subject decorated with a decorator in the Admin namespace ( ❌ pundit can't find policy )
  2. Subject decorated with a decorator in the top level namespace ( ✔️ succeeds )
  3. Subject not decorated ( ✔️ succeeds )
# frozen_string_literal: true
require "bundler/inline"

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

  # Use local changes or ActiveAdmin master.
  if ENV["ACTIVE_ADMIN_PATH"]
    gem "activeadmin", path: ENV["ACTIVE_ADMIN_PATH"], require: false
  else
    gem "activeadmin", github: "activeadmin/activeadmin", require: false
  end

  # Change Rails version if necessary.
  gem "rails", "~> 7.0.0"

  gem "sprockets", "~> 3.7"
  gem "sassc-rails"
  gem "sqlite3", platform: :mri
  gem "activerecord-jdbcsqlite3-adapter", platform: :jruby

  gem "pundit"

  # Fixes an issue on CI with default gems when using inline bundle with default
  # gems that are already activated
  # Ref: rubygems/rubygems#6386
  if ENV["CI"]
    require "net/protocol"
    require "timeout"

    gem "net-protocol", Net::Protocol::VERSION
    gem "timeout", Timeout::VERSION
  end
end

require "active_record"

ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Base.logger = Logger.new(STDOUT)

ActiveRecord::Schema.define do
  create_table :active_admin_comments, force: true do |_t|
  end

  create_table :users, force: true do |t|
    t.string :full_name
  end
end

require "action_controller/railtie"
require "action_view/railtie"
require "active_admin"

class TestApp < Rails::Application
  config.root = __dir__
  config.session_store :cookie_store, key: "cookie_store_key"
  secrets.secret_token = "secret_token"
  secrets.secret_key_base = "secret_key_base"

  config.eager_load = false
  config.logger = Logger.new($stdout)

  config.hosts = "www.example.com"
end

class ApplicationController < ActionController::Base
  include Rails.application.routes.url_helpers
end

class User < ActiveRecord::Base
end

module Admin
  class UserPresenter
    attr_reader :user
    delegate_missing_to :user
    delegate :to_param, to: :user

    def initialize(user)
      @user = user
    end
  end
end

class UserPresenter
  attr_reader :user
  delegate_missing_to :user
  delegate :to_param, to: :user

  def initialize(user)
    @user = user
  end
end

module Admin
  class ApplicationPolicy
    attr_reader :user, :record

    def initialize(user, record)
      @user = user
      @record = record
    end

    def index?
      false
    end

    def show?
      false
    end

    def create?
      false
    end

    def new?
      create?
    end

    def update?
      false
    end

    def edit?
      update?
    end

    def destroy?
      false
    end

    class Scope
      def initialize(user, scope)
        @user = user
        @scope = scope
      end

      def resolve
        raise NotImplementedError, "You must define #resolve in #{self.class}"
      end

      private

      attr_reader :user, :scope
    end
  end
end

module Admin
  class UserPolicy < ApplicationPolicy
    def index?
      true
    end
  end
end


ActiveAdmin.setup do |config|
  # Authentication disabled by default. Override if necessary.
  config.authentication_method = false
  config.current_user_method = false

  config.authorization_adapter = ActiveAdmin::PunditAdapter
#  config.pundit_default_policy = 'Admin::ApplicationPolicy'
  config.pundit_policy_namespace = :admin
end

Rails.application.initialize!

Rails.application.routes.draw do
  ActiveAdmin.routes(self)
end

require "minitest/autorun"
require "rack/test"
require "rails/test_help"

# Replace this with the code necessary to make your test fail.
class BugTest < ActionDispatch::IntegrationTest

  def test_authorization_with_decorator_in_admin_namespace
    user = User.create!
    subject = Admin::UserPresenter.new(user)
    adapter = ActiveAdmin::PunditAdapter.new(subject, nil)

    assert adapter.authorized?(:index, subject)
  end

  def test_authorization_with_decorator_without_namespace
    user = User.create!
    subject = NonNamespacedUserPresenter.new(user)
    adapter = ActiveAdmin::PunditAdapter.new(subject, nil)

    assert adapter.authorized?(:index, subject)
  end

  def test_authorization_with_plain_subject
    user = User.create!
    subject = user
    adapter = ActiveAdmin::PunditAdapter.new(subject, nil)

    assert adapter.authorized?(:index, subject)
  end


  private

  def app
    Rails.application
  end
end
@rogerkk rogerkk changed the title Pundit authorization fails when using Admin namespaced decorator Authorization fails when using Admin namespaced decorator Apr 24, 2023
@rogerkk rogerkk changed the title Authorization fails when using Admin namespaced decorator Authorization fails with Admin namespaced decorator Apr 24, 2023
rogerkk added a commit to rogerkk/activeadmin that referenced this issue Apr 24, 2023
When retrieving auth policies it is unfortunate if the policy
is wrapped in a decorator.

This uses an existing undecoration method to undecorate the target
before asking pundit to fetch the policy.

Related to activeadmin#7933
rogerkk added a commit to rogerkk/activeadmin that referenced this issue Apr 24, 2023
When retrieving auth policies it is unfortunate if the policy
is wrapped in a decorator.

This uses an existing undecoration method to undecorate the target
before asking pundit to fetch the policy.

Related to activeadmin#7933
rogerkk added a commit to rogerkk/activeadmin that referenced this issue Oct 11, 2023
When retrieving auth policies it is unfortunate if the policy
is wrapped in a decorator.

This uses an existing undecoration method to undecorate the target
before asking pundit to fetch the policy.

Related to activeadmin#7933
@javierjulio
Copy link
Member

@rogerkk what would this look like in the app, path wise? Instead of declaring policy objects in app/policies, you want it to be app/policies/admin instead? Is the issue with the name of "admin" as the namespace? Or with any namespace you would still encounter your issue? While I do use policies, it's all within app/policies. This is a feature I don't use so I'm not familiar with.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants