Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add hook for clear pundit context #830

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

## Added

- Add `Pundit::Authorization#pundit_reset!` hook to reset the policy and policy scope cache. (#830)

## 2.4.0 (2024-08-26)

## Changed
Expand Down
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,25 @@ def pundit_user
User.find_by_other_means
end
```
### Handling User Switching in Pundit

When switching users in your application, it's important to reset the Pundit user context to ensure that authorization policies are applied correctly for the new user. Pundit caches the user context, so failing to reset it could result in incorrect permissions being applied.

To handle user switching, you can use the following pattern in your controller:

```ruby
class ApplicationController
include Pundit::Authorization
before_action :switch_user, if: :should_switch_user?

def switch_user
current_user = User.find(params[:user_id])
pundit_reset! # Ensure that the Pundit context is reset for the new user
end
end
```

Make sure to invoke `pundit_reset!` whenever changing the user. This ensures the cached authorization context is reset, preventing any incorrect permissions from being applied.

## Policy Namespacing
In some cases it might be helpful to have multiple policies that serve different contexts for a
Expand Down
22 changes: 22 additions & 0 deletions lib/pundit/authorization.rb
Original file line number Diff line number Diff line change
Expand Up @@ -217,5 +217,27 @@ def pundit_params_for(record)
end

# @!endgroup

# @!group Customize Pundit user

# Clears the cached Pundit authorization data.
#
# This method should be called when the pundit_user is changed,
# such as during user switching, to ensure that stale authorization
# data is not used. Pundit caches authorization policies and scopes
# for the pundit_user, so calling this method will reset those
# caches and ensure that the next authorization checks are performed
# with the correct context for the new pundit_user.
#
# @return [void]
def pundit_reset!
@pundit = nil
@_pundit_policies = nil
@_pundit_policy_scopes = nil
@_pundit_policy_authorized = nil
@_pundit_policy_scoped = nil
end

# @!endgroup
end
end
45 changes: 45 additions & 0 deletions spec/authorization_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -271,4 +271,49 @@ def to_params(*args, **kwargs, &block)
expect(Controller.new(user, action, params).permitted_attributes(post, :revise).to_h).to eq("body" => "blah")
end
end

describe "#pundit_reset!" do
it "allows authorize to react to a user change" do
expect(controller.authorize(post)).to be_truthy
controller.current_user = double
controller.pundit_reset!
expect { controller.authorize(post) }.to raise_error(Pundit::NotAuthorizedError)
end

it "allows policy scope to react to a user change" do
expect(controller.policy_scope(Post)).to eq :published
expect { controller.verify_policy_scoped }.not_to raise_error
controller.current_user = double
controller.pundit_reset!
expect { controller.verify_policy_scoped }.to raise_error(Pundit::PolicyScopingNotPerformedError)
end

it "clears the pundit context user" do
expect(controller.pundit.user).to be(user)

new_user = double
controller.current_user = new_user
expect { controller.pundit_reset! }.to change { controller.pundit.user }.from(user).to(new_user)
end

it "clears pundit_policy_authorized? flag" do
expect(controller.pundit_policy_authorized?).to be false

controller.skip_authorization
expect(controller.pundit_policy_authorized?).to be true
furkanural marked this conversation as resolved.
Show resolved Hide resolved

controller.pundit_reset!
expect(controller.pundit_policy_authorized?).to be false
end

it "clears pundit_policy_scoped? flag" do
expect(controller.pundit_policy_scoped?).to be false

controller.skip_policy_scope
expect(controller.pundit_policy_scoped?).to be true

controller.pundit_reset!
expect(controller.pundit_policy_scoped?).to be false
end
end
end
3 changes: 2 additions & 1 deletion spec/support/lib/controller.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# frozen_string_literal: true

class Controller
attr_reader :current_user, :action_name, :params
attr_accessor :current_user
attr_reader :action_name, :params

class View
def initialize(controller)
Expand Down