From a607f214552e3b309d9d28ecae70f7aa4b5852c5 Mon Sep 17 00:00:00 2001 From: Martin Emde Date: Sat, 21 Sep 2024 13:29:42 -0700 Subject: [PATCH] Refactor Ownership policies to cover updating user's own ownership Make authorization failures more helpful on ownership changes. We were sometimes returning just the text 'Forbidden' when really we need to say that you can't edit your own role or explain the authorization problem, if possible, then redirect back to the rubygem page. --- app/controllers/api/v1/owners_controller.rb | 20 +++--- app/controllers/owners_controller.rb | 34 ++++----- app/models/ownership.rb | 4 +- app/policies/api/application_policy.rb | 9 +++ app/policies/api/ownership_policy.rb | 11 +++ app/policies/api/rubygem_policy.rb | 12 ++-- app/policies/ownership_policy.rb | 2 + .../ownership_confirmation.html.erb | 2 +- config/brakeman.ignore | 52 -------------- .../api/v1/owners_controller_test.rb | 50 +++++++++---- test/functional/owners_controller_test.rb | 71 +++++++++---------- test/models/ownership_test.rb | 10 ++- test/policies/api/ownership_policy_test.rb | 31 ++++++++ test/policies/ownership_policy_test.rb | 19 ++--- 14 files changed, 176 insertions(+), 151 deletions(-) create mode 100644 app/policies/api/ownership_policy.rb delete mode 100644 config/brakeman.ignore create mode 100644 test/policies/api/ownership_policy_test.rb diff --git a/app/controllers/api/v1/owners_controller.rb b/app/controllers/api/v1/owners_controller.rb index 6c85cc60b48..ec4e6ed3f1c 100644 --- a/app/controllers/api/v1/owners_controller.rb +++ b/app/controllers/api/v1/owners_controller.rb @@ -15,9 +15,7 @@ def show def create authorize @rubygem, :add_owner? owner = User.find_by_name!(email_param) - role = params[:role] - ownership = @rubygem.ownerships.new(user: owner, authorizer: @api_key.user) - ownership.role = role if role.present? + ownership = @rubygem.ownerships.new(user: owner, authorizer: @api_key.user, **ownership_params) if ownership.save OwnersMailer.ownership_confirmation(ownership).deliver_later @@ -30,11 +28,16 @@ def create end def update - authorize @rubygem, :update_owner? - owner = User.find_by_name!(email_param) - ownership = @rubygem.ownerships.find_by!(user: owner) + owner = User.find_by_name(email_param) + ownership = @rubygem.ownerships.find_by(user: owner) if owner + if ownership + authorize(ownership) + else + authorize(@rubygem, :update_owner?) # don't leak presence of an email unless authorized + return render_not_found + end - if ownership.update(ownership_update_params) + if ownership.update(ownership_params) render plain: response_with_mfa_warning("Owner updated successfully.") else render plain: response_with_mfa_warning(ownership.errors.full_messages.to_sentence), status: :unprocessable_entity @@ -45,6 +48,7 @@ def destroy authorize @rubygem, :remove_owner? owner = User.find_by_name!(email_param) ownership = @rubygem.ownerships_including_unconfirmed.find_by!(user: owner) + if ownership.safe_destroy OwnersMailer.owner_removed(ownership.user_id, @api_key.user.id, ownership.rubygem_id).deliver_later render plain: response_with_mfa_warning("Owner removed successfully.") @@ -76,7 +80,7 @@ def email_param params.permit(:email).require(:email) end - def ownership_update_params + def ownership_params params.permit(:role) end end diff --git a/app/controllers/owners_controller.rb b/app/controllers/owners_controller.rb index 6c983913aae..1c393af0dfa 100644 --- a/app/controllers/owners_controller.rb +++ b/app/controllers/owners_controller.rb @@ -4,6 +4,11 @@ class OwnersController < ApplicationController before_action :find_rubygem, except: :confirm verify_session_before only: %i[index edit update create destroy] before_action :verify_mfa_requirement, only: %i[create edit update destroy] + before_action :find_ownership, only: %i[edit update destroy] + + rescue_from(Pundit::NotAuthorizedError) do |e| + redirect_to rubygem_path(@rubygem.slug), alert: e.policy.error + end def confirm ownership = Ownership.find_by!(token: token_params) @@ -33,8 +38,6 @@ def index end def edit - authorize @rubygem, :update_owner? - @ownership = @rubygem.ownerships_including_unconfirmed.find_by_owner_handle!(handle_params) end def create @@ -54,25 +57,15 @@ def create # the role is the only thing that can be updated. If more fields are added to the ownership # this action will need to be tweaked a bit def update - authorize @rubygem, :update_owner? - ownership = @rubygem.ownerships_including_unconfirmed.find_by_owner_handle!(handle_params) - - if ownership.user == current_user && update_params[:role] != ownership.role - # Don't allow the owner to change the access level of their own ownership - return redirect_to rubygem_owners_path(@rubygem.slug), alert: t(".update_current_user_role") - end - - if ownership.update(update_params) - OwnersMailer.with(ownership: ownership, authorizer: current_user).owner_updated.deliver_later - redirect_to rubygem_owners_path(ownership.rubygem.slug), notice: t(".success_notice", handle: ownership.user.name) + if @ownership.update(update_params) + OwnersMailer.with(ownership: @ownership, authorizer: current_user).owner_updated.deliver_later + redirect_to rubygem_owners_path(@ownership.rubygem.slug), notice: t(".success_notice", handle: @ownership.user.name) else - index_with_error ownership.errors.full_messages.to_sentence, :unprocessable_entity + index_with_error @ownership.errors.full_messages.to_sentence, :unprocessable_entity end end def destroy - authorize @rubygem, :remove_owner? - @ownership = @rubygem.ownerships_including_unconfirmed.find_by_owner_handle!(handle_params) if @ownership.safe_destroy OwnersMailer.owner_removed(@ownership.user_id, current_user.id, @ownership.rubygem_id).deliver_later redirect_to rubygem_owners_path(@ownership.rubygem.slug), notice: t(".removed_notice", owner_name: @ownership.owner_name) @@ -87,6 +80,15 @@ def verify_session_redirect_path rubygem_owners_url(params[:rubygem_id]) end + def find_ownership + @ownership = @rubygem.ownerships_including_unconfirmed.find_by_owner_handle(handle_params) + return authorize(@ownership) if @ownership + + predicate = params[:action] == "destroy" ? :remove_owner? : :update_owner? + authorize(@rubygem, predicate) + render_not_found + end + def token_params params.permit(:token).require(:token) end diff --git a/app/models/ownership.rb b/app/models/ownership.rb index 0dcb4e0fabf..44941dec8c1 100644 --- a/app/models/ownership.rb +++ b/app/models/ownership.rb @@ -32,8 +32,8 @@ def self.by_indexed_gem_name .order("rubygems.name ASC") end - def self.find_by_owner_handle!(handle) - joins(:user).find_by(users: { handle: handle }) || joins(:user).find_by!(users: { id: handle }) + def self.find_by_owner_handle(handle) + joins(:user).find_by(users: { handle: handle }) || joins(:user).find_by(users: { id: handle }) end def self.create_confirmed(rubygem, user, approver) diff --git a/app/policies/api/application_policy.rb b/app/policies/api/application_policy.rb index 3b69c8e5f12..3d7d9b7f873 100644 --- a/app/policies/api/application_policy.rb +++ b/app/policies/api/application_policy.rb @@ -42,10 +42,19 @@ def deny(error) false end + def api_policy!(record) + Pundit.policy!(api_key, [:api, record]) + end + def user_policy!(record) Pundit.policy!(api_key.user, record) end + def api_authorized?(record, action) + policy = api_policy!(record) + policy.send(action) || deny(policy.error) + end + def user_authorized?(record, action) policy = user_policy!(record) policy.send(action) || deny(policy.error) diff --git a/app/policies/api/ownership_policy.rb b/app/policies/api/ownership_policy.rb new file mode 100644 index 00000000000..464b2376bf0 --- /dev/null +++ b/app/policies/api/ownership_policy.rb @@ -0,0 +1,11 @@ +class Api::OwnershipPolicy < Api::ApplicationPolicy + class Scope < Api::ApplicationPolicy::Scope + end + + delegate :rubygem, to: :record + + def update? + api_authorized?(rubygem, :update_owner?) && + user_authorized?(record, :update?) + end +end diff --git a/app/policies/api/rubygem_policy.rb b/app/policies/api/rubygem_policy.rb index 368b2547d1b..690ede046c3 100644 --- a/app/policies/api/rubygem_policy.rb +++ b/app/policies/api/rubygem_policy.rb @@ -26,18 +26,18 @@ def add_owner? user_authorized?(rubygem, :add_owner?) end - def remove_owner? + def update_owner? user_api_key? && mfa_requirement_satisfied?(rubygem) && - api_key_scope?(:remove_owner, rubygem) && - user_authorized?(rubygem, :remove_owner?) + api_key_scope?(:update_owner, rubygem) && + user_authorized?(rubygem, :update_owner?) end - def update_owner? + def remove_owner? user_api_key? && mfa_requirement_satisfied?(rubygem) && - api_key_scope?(:update_owner, rubygem) && - user_authorized?(rubygem, :update_owner?) + api_key_scope?(:remove_owner, rubygem) && + user_authorized?(rubygem, :remove_owner?) end def configure_trusted_publishers? diff --git a/app/policies/ownership_policy.rb b/app/policies/ownership_policy.rb index d01f8d65385..ad31e15c265 100644 --- a/app/policies/ownership_policy.rb +++ b/app/policies/ownership_policy.rb @@ -9,8 +9,10 @@ def create? end def update? + return deny(t("owners.update.update_current_user_role")) if current_user?(record.user) policy!(user, rubygem).update_owner? end + alias edit? update? def destroy? policy!(user, rubygem).remove_owner? diff --git a/app/views/owners_mailer/ownership_confirmation.html.erb b/app/views/owners_mailer/ownership_confirmation.html.erb index 5e0f8548c4f..1d76dddd526 100644 --- a/app/views/owners_mailer/ownership_confirmation.html.erb +++ b/app/views/owners_mailer/ownership_confirmation.html.erb @@ -29,7 +29,7 @@
- VERIFY + VERIFY
diff --git a/config/brakeman.ignore b/config/brakeman.ignore deleted file mode 100644 index 8fa1ee71005..00000000000 --- a/config/brakeman.ignore +++ /dev/null @@ -1,52 +0,0 @@ -{ - "ignored_warnings": [ - { - "warning_type": "Mass Assignment", - "warning_code": 105, - "fingerprint": "8129b3f5dfead604b3d9f72a38e52ffe0a7273b2fa5cccc5e75c6066fddf3934", - "check_name": "PermitAttributes", - "message": "Potentially dangerous key allowed for mass assignment", - "file": "app/controllers/api/v1/owners_controller.rb", - "line": 80, - "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/", - "code": "params.permit(:role)", - "render_path": null, - "location": { - "type": "method", - "class": "Api::V1::OwnersController", - "method": "ownership_update_params" - }, - "user_input": ":role", - "confidence": "Medium", - "cwe_id": [ - 915 - ], - "note": "" - }, - { - "warning_type": "Mass Assignment", - "warning_code": 105, - "fingerprint": "85e397bacae5462238e8ce59c0fcd1045a414e603588ae313c5156c09a623fed", - "check_name": "PermitAttributes", - "message": "Potentially dangerous key allowed for mass assignment", - "file": "app/controllers/owners_controller.rb", - "line": 98, - "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/", - "code": "params.permit(:role)", - "render_path": null, - "location": { - "type": "method", - "class": "OwnersController", - "method": "update_params" - }, - "user_input": ":role", - "confidence": "Medium", - "cwe_id": [ - 915 - ], - "note": "" - } - ], - "updated": "2024-08-08 12:28:27 +1000", - "brakeman_version": "6.1.2" -} diff --git a/test/functional/api/v1/owners_controller_test.rb b/test/functional/api/v1/owners_controller_test.rb index 751fd0a5138..e1164f85882 100644 --- a/test/functional/api/v1/owners_controller_test.rb +++ b/test/functional/api/v1/owners_controller_test.rb @@ -975,43 +975,65 @@ def self.should_respond_to(format) setup do @owner = create(:user) @maintainer = create(:user) - @rubygem = create(:rubygem) - - @owner_gem_ownership = create(:ownership, user: @owner, rubygem: @rubygem, role: :owner) - @maintainer_gem_ownership = create(:ownership, user: @maintainer, rubygem: @rubygem, role: :maintainer) + @rubygem = create(:rubygem, owners: [@owner]) @api_key = create(:api_key, key: "12223", scopes: %i[update_owner], owner: @owner, rubygem: @rubygem) @request.env["HTTP_AUTHORIZATION"] = "12223" end should "set the maintainer to a lower access level" do + ownership = create(:ownership, user: @maintainer, rubygem: @rubygem, role: :owner) + patch :update, params: { rubygem_id: @rubygem.slug, email: @maintainer.email, role: :maintainer } assert_response :success - assert_predicate @maintainer_gem_ownership.reload, :maintainer? + assert_predicate ownership.reload, :maintainer? + assert_enqueued_email_with OwnersMailer, :owner_updated, params: { ownership: ownership } end - should "schedule an email for the updated user" do - patch :update, params: { rubygem_id: @rubygem.slug, email: @maintainer.email, role: :owner } + context "when the current user is changing their own role" do + should "forbid changing the role" do + patch :update, params: { rubygem_id: @rubygem.slug, email: @owner.email, role: :maintainer } - ownership = Ownership.find_by(rubygem: @rubygem, user: @maintainer) + ownership = @rubygem.ownerships.find_by(user: @owner) - assert_enqueued_email_with OwnersMailer, :owner_updated, params: { ownership: ownership } + assert_response :forbidden + assert_predicate ownership.reload, :owner? + end end context "when the role is invalid" do should "return a bad request response with the error message" do - patch :update, params: { rubygem_id: @rubygem.slug, email: @maintainer.email, role: :invalid } + ownership = create(:ownership, user: @maintainer, rubygem: @rubygem, role: :maintainer) - respond_with :bad_request + patch :update, params: { rubygem_id: @rubygem.slug, email: @maintainer.email, role: :invalid } + assert_response :unprocessable_entity assert_equal "Role is not included in the list", @response.body + assert_predicate ownership.reload, :maintainer? end + end - should "not update the user with the new role" do - patch :update, params: { rubygem_id: @rubygem.slug, email: @maintainer.email, role: :invalid } + context "when the owner is not found" do + context "when the update is authorized" do + should "return a not found response" do + patch :update, params: { rubygem_id: @rubygem.slug, email: "notauser", role: :owner } + + assert_response :not_found + assert_equal "Owner could not be found.", @response.body + end + end - assert_predicate @maintainer_gem_ownership.reload, :maintainer? + context "when the update is not authorized" do + should "return a forbidden response" do + @api_key = create(:api_key, key: "99999", scopes: %i[push_rubygem], owner: @owner) + @request.env["HTTP_AUTHORIZATION"] = "99999" + + patch :update, params: { rubygem_id: @rubygem.slug, email: "notauser", role: :owner } + + assert_response :forbidden + assert_equal "This API key cannot perform the specified action on this gem.", @response.body + end end end end diff --git a/test/functional/owners_controller_test.rb b/test/functional/owners_controller_test.rb index ce8e9d823a2..7cbc4e78dfd 100644 --- a/test/functional/owners_controller_test.rb +++ b/test/functional/owners_controller_test.rb @@ -39,11 +39,8 @@ class OwnersControllerTest < ActionController::TestCase get :index, params: { rubygem_id: @rubygem.name } end - should respond_with :forbidden - - should "render forbidden message" do - assert page.has_content?("Forbidden") - end + should redirect_to("gem info page") { rubygem_path(@rubygem.slug) } + should set_flash[:alert].to "Forbidden" end end @@ -167,7 +164,8 @@ class OwnersControllerTest < ActionController::TestCase post :create, params: { handle: @other_user.display_id, rubygem_id: @rubygem.name } end - should respond_with :forbidden + should redirect_to("gem info page") { rubygem_path(@rubygem.slug) } + should set_flash[:alert].to "Forbidden" should "not add other user as owner" do refute_includes @rubygem.owners_including_unconfirmed, @other_user @@ -192,6 +190,7 @@ class OwnersControllerTest < ActionController::TestCase delete :destroy, params: { rubygem_id: @rubygem.name, handle: @second_user.display_id } end end + should redirect_to("ownership index") { rubygem_owners_path(@rubygem.slug) } should "remove the ownership record" do @@ -231,15 +230,10 @@ class OwnersControllerTest < ActionController::TestCase delete :destroy, params: { rubygem_id: @rubygem.name, handle: @last_owner.display_id } end end - should respond_with :forbidden + should set_flash.now[:alert].to "Can't remove the only owner of the gem" should "not remove the ownership record" do assert_includes @rubygem.owners_including_unconfirmed, @last_owner - end - should "should flash error" do - assert_equal "Can't remove the only owner of the gem", flash[:alert] - end - should "not send email notifications about owner removal" do assert_emails 0 end end @@ -293,7 +287,7 @@ class OwnersControllerTest < ActionController::TestCase delete :destroy, params: { rubygem_id: @rubygem.name, handle: @last_owner.display_id } end - should respond_with :forbidden + should redirect_to("gem info page") { rubygem_path(@rubygem.slug) } should "not remove user as owner" do assert_includes @rubygem.owners, @last_owner @@ -365,12 +359,25 @@ class OwnersControllerTest < ActionController::TestCase @rubygem = create(:rubygem, owners: [@owner, @maintainer]) verified_sign_in_as(@owner) + end - get :edit, params: { rubygem_id: @rubygem.name, handle: @maintainer.display_id } + context "when editing another owner's role" do + setup do + get :edit, params: { rubygem_id: @rubygem.name, handle: @maintainer.display_id } + end + + should respond_with :success + should render_template :edit end - should respond_with :success - should render_template :edit + context "when editing your own role" do + setup do + get :edit, params: { rubygem_id: @rubygem.name, handle: @owner.display_id } + end + + should redirect_to("gem info page") { rubygem_path(@rubygem.slug) } + should set_flash[:alert].to "Can't update your own Role" + end end context "on PATCH to update ownership" do @@ -393,11 +400,6 @@ class OwnersControllerTest < ActionController::TestCase ownership = Ownership.find_by(rubygem: @rubygem, user: @maintainer) assert_predicate ownership, :maintainer? - end - - should "schedule an email for the updated user" do - ownership = Ownership.find_by(rubygem: @rubygem, user: @maintainer) - assert_enqueued_email_with OwnersMailer, :owner_updated, params: { ownership: ownership, authorizer: @owner } end end @@ -503,31 +505,26 @@ class OwnersControllerTest < ActionController::TestCase end end - context "on EDIT to owners" do + context "on GET to edit" do setup do @second_user = create(:user) @ownership = create(:ownership, :unconfirmed, rubygem: @rubygem, user: @second_user) - edit :edit, params: { rubygem_id: @rubygem.name, handle: @second_user.display_id } - - should redirect_to("sessions#verify") { verify_session_path } - should use_before_action(:redirect_to_verify) - - should "show the edit form" do - assert_not_template :edit - end + get :edit, params: { rubygem_id: @rubygem.name, handle: @second_user.display_id } end + + should redirect_to("sessions#verify") { verify_session_path } + should use_before_action(:redirect_to_verify) end - context "on UPDATE to owners" do + context "on PATCH to update" do setup do @second_user = create(:user) - @ownership = create(:ownership, :unconfirmed, rubygem: @rubygem, user: @second_user) - patch :update, params: { rubygem_id: @rubygem.name, handle: @second_user.display_id, role: :owner } - - should "update the ownership record" do - assert_equal Access::MAINTAINER, @ownership.reload.access - end + @ownership = create(:ownership, :unconfirmed, rubygem: @rubygem, user: @second_user, role: :owner) + patch :update, params: { rubygem_id: @rubygem.name, handle: @second_user.display_id, role: :maintainer } end + + should redirect_to("sessions#verify") { verify_session_path } + should use_before_action(:redirect_to_verify) end end diff --git a/test/models/ownership_test.rb b/test/models/ownership_test.rb index 67921ff2306..857e62b717d 100644 --- a/test/models/ownership_test.rb +++ b/test/models/ownership_test.rb @@ -200,14 +200,12 @@ class OwnershipTest < ActiveSupport::TestCase end should "find owner by matching handle/id" do - assert_equal @ownership, @rubygem.ownerships.find_by_owner_handle!(@user.handle) - assert_equal @ownership, @rubygem.ownerships.find_by_owner_handle!(@user) + assert_equal @ownership, @rubygem.ownerships.find_by_owner_handle(@user.handle) + assert_equal @ownership, @rubygem.ownerships.find_by_owner_handle(@user) end - should "raise not found" do - assert_raise ActiveRecord::RecordNotFound do - @rubygem.ownerships.find_by_owner_handle!("wrong user") - end + should "return nil" do + assert_nil @rubygem.ownerships.find_by_owner_handle("wronguser") end end diff --git a/test/policies/api/ownership_policy_test.rb b/test/policies/api/ownership_policy_test.rb new file mode 100644 index 00000000000..d0641f6533e --- /dev/null +++ b/test/policies/api/ownership_policy_test.rb @@ -0,0 +1,31 @@ +require "test_helper" + +class Api::OwnershipPolicyTest < ApiPolicyTestCase + setup do + @owner = create(:user, handle: "owner") + @user = create(:user, handle: "user") + @rubygem = create(:rubygem, owners: [@owner]) + + @record = create(:ownership, rubygem: @rubygem, user: @user, authorizer: @owner) + + OwnershipPolicy.any_instance.stubs( + update?: true, + destroy?: true + ) + end + + def policy!(api_key) + Pundit.policy!(api_key, [:api, @record]) + end + + context "#update?" do + scope = :update_owner + action = :update? + + should_require_user_key scope, action + should_require_mfa scope, action + should_require_scope scope, action + should_require_rubygem_scope scope, action + should_delegate_to_policy scope, action, OwnershipPolicy + end +end diff --git a/test/policies/ownership_policy_test.rb b/test/policies/ownership_policy_test.rb index 601f8ab8fb8..2fa68b75d37 100644 --- a/test/policies/ownership_policy_test.rb +++ b/test/policies/ownership_policy_test.rb @@ -5,8 +5,8 @@ class OwnershipPolicyTest < ActiveSupport::TestCase @authorizer = FactoryBot.create(:user, handle: "owner") @maintainer = FactoryBot.create(:user, handle: "maintainer") @rubygem = FactoryBot.create(:rubygem, owners: [@authorizer], maintainers: [@maintainer]) - @confirmed_ownership = @rubygem.ownerships.first - @confirmed_maintainer_ownership = @maintainer.ownerships.first + @authorizer_ownership = @rubygem.ownerships.first + @maintainer_ownership = @maintainer.ownerships.first @unconfirmed_ownership = FactoryBot.build(:ownership, :unconfirmed, rubygem: @rubygem, authorizer: @authorizer) @unconfirmed_maintainer_ownership = FactoryBot.build(:ownership, :maintainer, rubygem: @rubygem, authorizer: @authorizer) @@ -18,24 +18,25 @@ def test_create assert_predicate Pundit.policy!(@authorizer, @unconfirmed_ownership), :create? refute_predicate Pundit.policy!(@invited, @unconfirmed_ownership), :create? refute_predicate Pundit.policy!(@user, @unconfirmed_ownership), :create? - refute_predicate Pundit.policy!(@maintainer, @confirmed_maintainer_ownership), :create? + refute_predicate Pundit.policy!(@maintainer, @maintainer_ownership), :create? refute_predicate Pundit.policy!(@maintainer, @unconfirmed_maintainer_ownership), :create? end def test_update - assert_predicate Pundit.policy!(@authorizer, @confirmed_ownership), :update? + assert_predicate Pundit.policy!(@authorizer, @maintainer_ownership), :update? + refute_predicate Pundit.policy!(@authorizer, @authorizer_ownership), :update? refute_predicate Pundit.policy!(@invited, @unconfirmed_ownership), :update? refute_predicate Pundit.policy!(@user, @unconfirmed_ownership), :update? refute_predicate Pundit.policy!(@user, @unconfirmed_maintainer_ownership), :update? - refute_predicate Pundit.policy!(@maintainer, @confirmed_maintainer_ownership), :update? + refute_predicate Pundit.policy!(@maintainer, @maintainer_ownership), :update? refute_predicate Pundit.policy!(@maintainer, @unconfirmed_maintainer_ownership), :update? end def test_destroy - assert_predicate Pundit.policy!(@authorizer, @confirmed_ownership), :destroy? - refute_predicate Pundit.policy!(@owner, @confirmed_ownership), :destroy? - refute_predicate Pundit.policy!(@user, @confirmed_ownership), :destroy? - refute_predicate Pundit.policy!(@user, @confirmed_maintainer_ownership), :destroy? + assert_predicate Pundit.policy!(@authorizer, @authorizer_ownership), :destroy? + refute_predicate Pundit.policy!(@maintainer, @authorizer_ownership), :destroy? + refute_predicate Pundit.policy!(@user, @authorizer_ownership), :destroy? + refute_predicate Pundit.policy!(@user, @maintainer_ownership), :destroy? refute_predicate Pundit.policy!(@user, @unconfirmed_maintainer_ownership), :destroy? end end