Last active
August 27, 2018 14:01
-
-
Save ciembor/8985278ee02bb25b4bf9c2ad68839571 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require "pundit/version" | |
require "active_support/concern" | |
require "active_support/core_ext/string/inflections" | |
require "active_support/core_ext/object/blank" | |
require "active_support/core_ext/module/introspection" | |
require "active_support/dependencies/autoload" | |
module Pundit | |
class PolicyFinder | |
attr_reader :object | |
def initialize(object) | |
@object = object | |
end | |
def scope | |
"#{policy}::Scope".safe_constantize | |
end | |
def policy | |
klass = find(object) | |
klass.is_a?(String) ? klass.safe_constantize : klass | |
end | |
def scope! | |
scope or raise NotDefinedError, "unable to find scope `#{find(object)}::Scope` for `#{object.inspect}`" | |
end | |
def policy! | |
policy or raise NotDefinedError, "unable to find policy `#{find(object)}` for `#{object.inspect}`" | |
end | |
def param_key | |
model = object.is_a?(Array) ? object.last : object | |
if model.respond_to?(:model_name) | |
model.model_name.param_key.to_s | |
elsif model.is_a?(Class) | |
model.to_s.demodulize.underscore | |
else | |
model.class.to_s.demodulize.underscore | |
end | |
end | |
private | |
def find(subject) | |
if subject.is_a?(Array) | |
modules = subject.dup | |
last = modules.pop | |
context = modules.map { |x| find_class_name(x) }.join("::") | |
[context, find(last)].join("::") | |
elsif subject.respond_to?(:policy_class) | |
subject.policy_class | |
elsif subject.class.respond_to?(:policy_class) | |
subject.class.policy_class | |
else | |
klass = find_class_name(subject) | |
"#{klass}#{SUFFIX}" | |
end | |
end | |
def find_class_name(subject) | |
if subject.respond_to?(:model_name) | |
subject.model_name | |
elsif subject.class.respond_to?(:model_name) | |
subject.class.model_name | |
elsif subject.is_a?(Class) | |
subject | |
elsif subject.is_a?(Symbol) | |
subject.to_s.camelize | |
else | |
subject.class | |
end | |
end | |
end | |
SUFFIX = "Policy".freeze | |
module Generators; end | |
class Error < StandardError; end | |
class NotAuthorizedError < Error | |
attr_reader :query, :record, :policy | |
def initialize(options = {}) | |
if options.is_a? String | |
message = options | |
else | |
@query = options[:query] | |
@record = options[:record] | |
@policy = options[:policy] | |
message = options.fetch(:message) { "not allowed to #{query} this #{record.inspect}" } | |
end | |
super(message) | |
end | |
end | |
class InvalidConstructorError < Error; end | |
class AuthorizationNotPerformedError < Error; end | |
class PolicyScopingNotPerformedError < AuthorizationNotPerformedError; end | |
class NotDefinedError < Error; end | |
extend ActiveSupport::Concern | |
class << self | |
def authorize(user, record, query, policy_class: nil) | |
policy = policy_class ? policy_class.new(user, record) : policy!(user, record) | |
raise NotAuthorizedError, query: query, record: record, policy: policy unless policy.public_send(query) | |
record | |
end | |
def policy_scope(user, scope) | |
policy_scope = PolicyFinder.new(scope).scope | |
policy_scope.new(user, pundit_model(scope)).resolve if policy_scope | |
rescue ArgumentError | |
raise InvalidConstructorError, "Invalid #<#{policy_scope}> constructor is called" | |
end | |
def policy_scope!(user, scope) | |
policy_scope = PolicyFinder.new(scope).scope! | |
policy_scope.new(user, pundit_model(scope)).resolve | |
rescue ArgumentError | |
raise InvalidConstructorError, "Invalid #<#{policy_scope}> constructor is called" | |
end | |
def policy(user, record) | |
policy = PolicyFinder.new(record).policy | |
policy.new(user, pundit_model(record)) if policy | |
rescue ArgumentError | |
raise InvalidConstructorError, "Invalid #<#{policy}> constructor is called" | |
end | |
def policy!(user, record) | |
policy = PolicyFinder.new(record).policy! | |
policy.new(user, pundit_model(record)) | |
rescue ArgumentError | |
raise InvalidConstructorError, "Invalid #<#{policy}> constructor is called" | |
end | |
private | |
def pundit_model(record) | |
record.is_a?(Array) ? record.last : record | |
end | |
end | |
module Helper | |
def policy_scope(scope) | |
pundit_policy_scope(scope) | |
end | |
end | |
included do | |
helper Helper if respond_to?(:helper) | |
if respond_to?(:helper_method) | |
helper_method :policy | |
helper_method :pundit_policy_scope | |
helper_method :pundit_user | |
end | |
end | |
protected | |
def pundit_policy_authorized? | |
!!@_pundit_policy_authorized | |
end | |
def pundit_policy_scoped? | |
!!@_pundit_policy_scoped | |
end | |
def verify_authorized | |
raise AuthorizationNotPerformedError, self.class unless pundit_policy_authorized? | |
end | |
def verify_policy_scoped | |
raise PolicyScopingNotPerformedError, self.class unless pundit_policy_scoped? | |
end | |
def authorize(record, query = nil, policy_class: nil) | |
query ||= "#{action_name}?" | |
@_pundit_policy_authorized = true | |
policy = policy_class ? policy_class.new(pundit_user, record) : policy(record) | |
raise NotAuthorizedError, query: query, record: record, policy: policy unless policy.public_send(query) | |
record | |
end | |
def skip_authorization | |
@_pundit_policy_authorized = true | |
end | |
def skip_policy_scope | |
@_pundit_policy_scoped = true | |
end | |
def policy_scope(scope, policy_scope_class: nil) | |
@_pundit_policy_scoped = true | |
policy_scope_class ? policy_scope_class.new(pundit_user, scope).resolve : pundit_policy_scope(scope) | |
end | |
def policy(record) | |
policies[record] ||= Pundit.policy!(pundit_user, record) | |
end | |
def permitted_attributes(record, action = action_name) | |
policy = policy(record) | |
method_name = if policy.respond_to?("permitted_attributes_for_#{action}") | |
"permitted_attributes_for_#{action}" | |
else | |
"permitted_attributes" | |
end | |
pundit_params_for(record).permit(*policy.public_send(method_name)) | |
end | |
def pundit_params_for(record) | |
params.require(PolicyFinder.new(record).param_key) | |
end | |
def policies | |
@_pundit_policies ||= {} | |
end | |
def policy_scopes | |
@_pundit_policy_scopes ||= {} | |
end | |
def pundit_user | |
current_user | |
end | |
private | |
def pundit_policy_scope(scope) | |
policy_scopes[scope] ||= Pundit.policy_scope!(pundit_user, scope) | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment