# frozen_string_literal: true ActiveAdmin::Dependency.pundit! require "pundit" # Add a setting to the application to configure the pundit default policy ActiveAdmin::Application.inheritable_setting :pundit_default_policy, nil ActiveAdmin::Application.inheritable_setting :pundit_policy_namespace, nil module ActiveAdmin class PunditAdapter < AuthorizationAdapter def authorized?(action, subject = nil) policy = retrieve_policy(subject) action = format_action(action, subject) policy.respond_to?(action) && policy.public_send(action) end def scope_collection(collection, action = Auth::READ) # scoping is applicable only to read/index action # which means there is no way how to scope other actions Pundit.policy_scope!(user, namespace(collection)) rescue Pundit::NotDefinedError => e if default_policy_class&.const_defined?(:Scope) default_policy_class::Scope.new(user, collection).resolve else raise e end end def retrieve_policy(subject) target = policy_target(subject) if (policy = policy(namespace(target)) || compat_policy(subject)) policy elsif default_policy_class default_policy(subject) else raise Pundit::NotDefinedError, "unable to find a compatible policy for `#{target}`" end end def format_action(action, subject) # https://github.com/varvet/pundit/blob/main/lib/generators/pundit/install/templates/application_policy.rb case action when Auth::READ then subject.is_a?(Class) ? :index? : :show? when Auth::DESTROY then subject.is_a?(Class) ? :destroy_all? : :destroy? else "#{action}?" end end private def policy_target(subject) case subject when nil then resource when Class then subject.new else subject end end # This method is needed to fallback to our previous policy searching logic. # I.e.: when class name contains `default_policy_namespace` (eg: ShopAdmin) # we should try to search it without namespace. This is because that's # the only thing that worked in this case before we fixed our buggy namespace # detection, so people are probably relying on it. # This fallback might be removed in future versions of ActiveAdmin, so # pundit_adapter search will work consistently with provided namespaces def compat_policy(subject) return unless default_policy_namespace target = policy_target(subject) return unless target.class.to_s.include?(default_policy_module) && (policy = policy(target)) policy_name = policy.class.to_s ActiveAdmin.deprecator.warn "You have `pundit_policy_namespace` configured as `#{default_policy_namespace}`, " \ "but ActiveAdmin was unable to find policy #{default_policy_module}::#{policy_name}. " \ "#{policy_name} will be used instead. " \ "This behavior will be removed in future versions of ActiveAdmin. " \ "To fix this warning, move your #{policy_name} policy to the #{default_policy_module} namespace" policy end def namespace(object) if default_policy_namespace && !object.class.to_s.start_with?("#{default_policy_module}::") [default_policy_namespace.to_sym, object] else object end end def default_policy_class ActiveAdmin.application.pundit_default_policy&.constantize end def default_policy(subject) default_policy_class.new(user, subject) end def default_policy_namespace ActiveAdmin.application.pundit_policy_namespace end def default_policy_module default_policy_namespace.to_s.camelize end def policy(target) policies[target] ||= Pundit.policy(user, target) end def policies @policies ||= {} end end end