8000 Authorization fails with Admin namespaced decorator · Issue #7933 · activeadmin/activeadmin · GitHub
[go: up one dir, main page]

Skip to content
Authorization fails with Admin namespaced decorator #7933
Open
@rogerkk

Description

@rogerkk

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 )

Update 2024-01-13

I realized the old template wasn't running properly against latest version of ActiveAdmin, so I added an updated version:

Original version of template
# 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
# 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", "~> 8.0.0"

  gem "sprockets", "~> 4.0"
  gem "importmap-rails", "~> 2.0"
  gem "sqlite3", force_ruby_platform: true, platform: :mri
  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.hosts << ".example.com"
  config.session_store :cookie_store, key: "cookie_store_key"
  config.secret_key_base = "secret_key_base"
  config.eager_load = false

  config.logger = Logger.new($stdout)
  Rails.logger = config.logger
end

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

class ApplicationRecord < ActiveRecord::Base
  primary_abstract_class

  def self.ransackable_attributes(_auth_object = nil)
    authorizable_ransackable_attributes
  end

  def self.ransackable_associations(_auth_object = nil)
    authorizable_ransackable_associations
  end
end

class User < ApplicationRecord
end

class UserPresenter
  attr_reader :user

  delegate_missing_to :user
  delegate :to_param, to: :user

  def initialize(user)
    @user = user
  end

  def decorated?
    true
  end

  def model
    user
  end
end

module Admin
  class UserPresenter < UserPresenter; end
end

class NonNamespacedUserPresenter < UserPresenter; 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      0