FEATURE: Allow admins to delete reviewables via API (#21174)

This PR adds the ability to destroy reviewables for a passed user via the API. This was not possible before as this action was reserved for reviewables for you created only.

If a user is an admin and calls the `#destroy` action from the API they are able to destroy a reviewable for a passed user. A user can be targeted by passed either their:
- username
- external_id (for SSO) 

to the request.

In the case you attempt to destroy a non-personal reviewable and
- You are not an admin
- You do not access the `#destroy` action via the API

you will raise a `Discourse::InvalidAccess` (403) and will not succeed in destroying the reviewable.
This commit is contained in:
Isaac Janzen 2023-04-20 09:38:41 -05:00 committed by GitHub
parent ba2adc7793
commit dd495a0e19
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 84 additions and 4 deletions

View File

@ -155,7 +155,18 @@ class ReviewablesController < ApplicationController
end
def destroy
reviewable = Reviewable.find_by(id: params[:reviewable_id], created_by: current_user)
user =
if is_api?
if @guardian.is_admin?
fetch_user_from_params
else
raise Discourse::InvalidAccess
end
else
current_user
end
reviewable = Reviewable.find_by(id: params[:reviewable_id], created_by: user)
raise Discourse::NotFound.new if reviewable.blank?
reviewable.perform(current_user, :delete)

View File

@ -232,7 +232,7 @@ class Guardian
end
def can_delete_reviewable_queued_post?(reviewable)
reviewable.present? && authenticated? && reviewable.created_by_id == @user.id
reviewable.present? && authenticated? && (reviewable.created_by_id == @user.id || @user.admin?)
end
def can_see_group?(group)

View File

@ -790,25 +790,94 @@ RSpec.describe ReviewablesController do
describe "#destroy" do
fab!(:user) { Fabricate(:user) }
before { sign_in(user) }
it "returns 404 if the reviewable doesn't exist" do
sign_in(user)
delete "/review/1234.json"
expect(response.code).to eq("404")
end
it "returns 404 if the user can't see the reviewable" do
sign_in(user)
queued_post = Fabricate(:reviewable_queued_post)
delete "/review/#{queued_post.id}.json"
expect(response.code).to eq("404")
end
it "returns 200 if the user can delete the reviewable" do
sign_in(user)
queued_post = Fabricate(:reviewable_queued_post, created_by: user)
delete "/review/#{queued_post.id}.json"
expect(response.code).to eq("200")
expect(queued_post.reload).to be_deleted
end
it "denies attempts to destroy unowned reviewables" do
sign_in(admin)
queued_post = Fabricate(:reviewable_queued_post, created_by: user)
delete "/review/#{queued_post.id}.json"
expect(response.status).to eq(404)
# Reviewable is not deleted because request is not via API
expect(queued_post.reload).to be_present
end
shared_examples "for a passed user" do
it "deletes reviewable" do
api_key = Fabricate(:api_key).key
queued_post = Fabricate(:reviewable_queued_post, created_by: recipient)
delete "/review/#{queued_post.id}.json",
params: {
username: recipient.username,
},
headers: {
HTTP_API_USERNAME: caller.username,
HTTP_API_KEY: api_key,
}
expect(response.status).to eq(response_code)
if reviewable_deleted
expect(queued_post.reload).to be_deleted
else
expect(queued_post.reload).to be_present
end
end
end
describe "api called by admin" do
include_examples "for a passed user" do
let(:caller) { Fabricate(:admin) }
let(:recipient) { user }
let(:response_code) { 200 }
let(:reviewable_deleted) { true }
end
end
describe "api called by tl4 user" do
include_examples "for a passed user" do
let(:caller) { Fabricate(:trust_level_4) }
let(:recipient) { user }
let(:response_code) { 403 }
let(:reviewable_deleted) { false }
end
end
describe "api called by regular user" do
include_examples "for a passed user" do
let(:caller) { user }
let(:recipient) { Fabricate(:user) }
let(:response_code) { 403 }
let(:reviewable_deleted) { false }
end
end
describe "api called by admin for another admin" do
include_examples "for a passed user" do
let(:caller) { Fabricate(:admin) }
let(:recipient) { Fabricate(:admin) }
let(:response_code) { 200 }
let(:reviewable_deleted) { true }
end
end
end
describe "#count" do