diff --git a/lib/pundit.rb b/lib/pundit.rb index 8a0dc0a..7eedc24 100644 --- a/lib/pundit.rb +++ b/lib/pundit.rb @@ -1,5 +1,56 @@ require "pundit/version" +require "pundit/policy_finder" module Pundit - # Your code goes here... + class NotAuthorizedError < StandardError; end + class NotDefinedError < StandardError; end + + extend ActiveSupport::Concern + + class << self + def policy_scope(user, scope) + policy = PolicyFinder.new(scope).scope + policy.new(user, scope).resolve if policy + end + + def policy_scope!(user, scope) + PolicyFinder.new(scope).scope!.new(user, scope).resolve + end + + def policy(user, record) + scope = PolicyFinder.new(record).policy + scope.new(user, record) if scope + end + + def policy!(user, record) + PolicyFinder.new(record).policy!.new(user, record) + end + end + + included do + if respond_to?(:helper_method) + helper_method :policy_scope + helper_method :policy + end + end + + def verify_authorized + raise NotAuthorizedError unless @_policy_authorized + end + + def authorize(record, query=nil) + query ||= params[:action].to_s + "?" + @_policy_authorized = true + unless policy(record).public_send(query) + raise NotAuthorizedError, "not allowed to #{query} this #{record}" + end + end + + def policy_scope(scope) + Pundit.policy_scope!(current_user, scope) + end + + def policy(record) + Pundit.policy!(current_user, record) + end end diff --git a/lib/pundit/policy_finder.rb b/lib/pundit/policy_finder.rb new file mode 100644 index 0000000..8733f7e --- /dev/null +++ b/lib/pundit/policy_finder.rb @@ -0,0 +1,49 @@ +module Pundit + class PolicyFinder + attr_reader :object + + def initialize(object) + @object = object + end + + def name + if object.respond_to?(:model_name) + object.model_name.to_s + elsif object.class.respond_to?(:model_name) + object.class.model_name.to_s + elsif object.is_a?(Class) + object.to_s + else + object.class.to_s + end + end + + def scope + scope_name.constantize + rescue NameError + nil + end + + def policy + policy_name.constantize + rescue NameError + nil + end + + def scope! + scope or raise NotDefinedError, "unable to find scope #{scope_name} for #{object}" + end + + def policy! + policy or raise NotDefinedError, "unable to find policy #{policy_name} for #{object}" + end + + def scope_name + "#{name}Policy::Scope" + end + + def policy_name + "#{name}Policy" + end + end +end diff --git a/lib/pundit/rspec.rb b/lib/pundit/rspec.rb new file mode 100644 index 0000000..20f5d64 --- /dev/null +++ b/lib/pundit/rspec.rb @@ -0,0 +1,47 @@ +module Pundit + module RSpec + module Matchers + extend ::RSpec::Matchers::DSL + + matcher :permit do |user, record| + match do |policy| + permissions.all? { |permission| policy.new(user, record).public_send(permission) } + end + + failure_message_for_should do |policy| + "Expected #{policy} to grant #{permissions.to_sentence} on #{record} but it didn't" + end + + failure_message_for_should_not do |policy| + "Expected #{policy} not to grant #{permissions.to_sentence} on #{record} but it did" + end + + def permissions + example.metadata[:permissions] + end + end + end + + module DSL + def permissions(*list, &block) + describe(list.to_sentence, :permissions => list, :caller => caller) { instance_eval(&block) } + end + end + + module PolicyExampleGroup + include Pundit::RSpec::Matchers + + def self.included(base) + base.metadata[:type] = :policy + base.extend Pundit::RSpec::DSL + super + end + end + end +end + +RSpec.configure do |config| + config.include Pundit::RSpec::PolicyExampleGroup, :type => :policy, :example_group => { + :file_path => /spec\/policies/ + } +end