Skip to content

Commit

Permalink
Extract Pundit controller authorization module
Browse files Browse the repository at this point in the history
  • Loading branch information
Jan Jedrychowski authored and Burgestrand committed Jan 4, 2022
1 parent 46af662 commit 4d9b584
Show file tree
Hide file tree
Showing 7 changed files with 453 additions and 413 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Expand Up @@ -30,6 +30,10 @@ changes.
- Dropped support for Ruby end-of-life versions: 2.4, 2.5 and JRuby 9.1 (#676)
- Dropped support for RSpec 2 (#615)

### Deprecated

- Deprecate `include Pundit` in favor of `include Pundit::Authorization` (#621)

## 2.1.0 (2019-08-14)

### Fixed
Expand Down
12 changes: 6 additions & 6 deletions README.md
Expand Up @@ -26,11 +26,11 @@ Sponsored by:
gem "pundit"
```

Include Pundit in your application controller:
Include `Pundit::Authorization` in your application controller:

``` ruby
class ApplicationController < ActionController::Base
include Pundit
include Pundit::Authorization
end
```

Expand Down Expand Up @@ -334,7 +334,7 @@ that you haven't forgotten to authorize the action. For example:

``` ruby
class ApplicationController < ActionController::Base
include Pundit
include Pundit::Authorization
after_action :verify_authorized
end
```
Expand All @@ -347,7 +347,7 @@ authorize individual instances.

``` ruby
class ApplicationController < ActionController::Base
include Pundit
include Pundit::Authorization
after_action :verify_authorized, except: :index
after_action :verify_policy_scoped, only: :index
end
Expand Down Expand Up @@ -490,7 +490,7 @@ method in every controller.

```ruby
class ApplicationController < ActionController::Base
include Pundit
include Pundit::Authorization

rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized

Expand Down Expand Up @@ -643,7 +643,7 @@ class UserContext
end

class ApplicationController
include Pundit
include Pundit::Authorization

def pundit_user
UserContext.new(current_user, request.ip)
Expand Down
167 changes: 5 additions & 162 deletions lib/pundit.rb
Expand Up @@ -7,6 +7,7 @@
require "active_support/core_ext/object/blank"
require "active_support/core_ext/module/introspection"
require "active_support/dependencies/autoload"
require "pundit/authorization"

# @api private
# To avoid name clashes with common Error naming when mixing in Pundit,
Expand Down Expand Up @@ -53,7 +54,10 @@ class PolicyScopingNotPerformedError < AuthorizationNotPerformedError; end
# Error that will be raised if a policy or policy scope is not defined.
class NotDefinedError < Error; end

extend ActiveSupport::Concern
def self.included(base)
warn "[DEPRECATION] 'include Pundit' is deprecated. Please use 'include Pundit::Authorization' instead."
base.include Authorization
end

class << self
# Retrieves the policy for the given record, initializing it with the
Expand Down Expand Up @@ -163,165 +167,4 @@ 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

# @return [Boolean] whether authorization has been performed, i.e. whether
# one {#authorize} or {#skip_authorization} has been called
def pundit_policy_authorized?
!!@_pundit_policy_authorized
end

# @return [Boolean] whether policy scoping has been performed, i.e. whether
# one {#policy_scope} or {#skip_policy_scope} has been called
def pundit_policy_scoped?
!!@_pundit_policy_scoped
end

# Raises an error if authorization has not been performed, usually used as an
# `after_action` filter to prevent programmer error in forgetting to call
# {#authorize} or {#skip_authorization}.
#
# @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
# @raise [AuthorizationNotPerformedError] if authorization has not been performed
# @return [void]
def verify_authorized
raise AuthorizationNotPerformedError, self.class unless pundit_policy_authorized?
end

# Raises an error if policy scoping has not been performed, usually used as an
# `after_action` filter to prevent programmer error in forgetting to call
# {#policy_scope} or {#skip_policy_scope} in index actions.
#
# @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
# @raise [AuthorizationNotPerformedError] if policy scoping has not been performed
# @return [void]
def verify_policy_scoped
raise PolicyScopingNotPerformedError, self.class unless pundit_policy_scoped?
end

# Retrieves the policy for the given record, initializing it with the record
# and current user and finally throwing an error if the user is not
# authorized to perform the given action.
#
# @param record [Object, Array] the object we're checking permissions of
# @param query [Symbol, String] the predicate method to check on the policy (e.g. `:show?`).
# If omitted then this defaults to the Rails controller action name.
# @param policy_class [Class] the policy class we want to force use of
# @raise [NotAuthorizedError] if the given query method returned false
# @return [Object] Always returns the passed object record
def authorize(record, query = nil, policy_class: nil)
query ||= "#{action_name}?"

@_pundit_policy_authorized = true

Pundit.authorize(pundit_user, record, query, policy_class: policy_class, cache: policies)
end

# Allow this action not to perform authorization.
#
# @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
# @return [void]
def skip_authorization
@_pundit_policy_authorized = :skipped
end

# Allow this action not to perform policy scoping.
#
# @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
# @return [void]
def skip_policy_scope
@_pundit_policy_scoped = :skipped
end

# Retrieves the policy scope for the given record.
#
# @see https://github.com/varvet/pundit#scopes
# @param scope [Object] the object we're retrieving the policy scope for
# @param policy_scope_class [Class] the policy scope class we want to force use of
# @return [Scope{#resolve}, nil] instance of scope class which can resolve to a scope
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

# Retrieves the policy for the given record.
#
# @see https://github.com/varvet/pundit#policies
# @param record [Object] the object we're retrieving the policy for
# @return [Object, nil] instance of policy class with query methods
def policy(record)
policies[record] ||= Pundit.policy!(pundit_user, record)
end

# Retrieves a set of permitted attributes from the policy by instantiating
# the policy class for the given record and calling `permitted_attributes` on
# it, or `permitted_attributes_for_{action}` if `action` is defined. It then infers
# what key the record should have in the params hash and retrieves the
# permitted attributes from the params hash under that key.
#
# @see https://github.com/varvet/pundit#strong-parameters
# @param record [Object] the object we're retrieving permitted attributes for
# @param action [Symbol, String] the name of the action being performed on the record (e.g. `:update`).
# If omitted then this defaults to the Rails controller action name.
# @return [Hash{String => Object}] the permitted attributes
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

# Retrieves the params for the given record.
#
# @param record [Object] the object we're retrieving params for
# @return [ActionController::Parameters] the params
def pundit_params_for(record)
params.require(PolicyFinder.new(record).param_key)
end

# Cache of policies. You should not rely on this method.
#
# @api private
# rubocop:disable Naming/MemoizedInstanceVariableName
def policies
@_pundit_policies ||= {}
end
# rubocop:enable Naming/MemoizedInstanceVariableName

# Cache of policy scope. You should not rely on this method.
#
# @api private
# rubocop:disable Naming/MemoizedInstanceVariableName
def policy_scopes
@_pundit_policy_scopes ||= {}
end
# rubocop:enable Naming/MemoizedInstanceVariableName

# Hook method which allows customizing which user is passed to policies and
# scopes initialized by {#authorize}, {#policy} and {#policy_scope}.
#
# @see https://github.com/varvet/pundit#customize-pundit-user
# @return [Object] the user object to be used with pundit
def pundit_user
current_user
end

private

def pundit_policy_scope(scope)
policy_scopes[scope] ||= Pundit.policy_scope!(pundit_user, scope)
end
end

0 comments on commit 4d9b584

Please sign in to comment.