Add trusted role via API (#20455)

* rubocop

* use has_trusted_role? in tests, not trusted? due to pesky caching issues

* swagger
This commit is contained in:
Philip How 2023-12-18 18:38:05 +00:00 committed by GitHub
parent 199a88a254
commit 78b42c5efd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 2091 additions and 385 deletions

View file

@ -5,7 +5,7 @@ module Api
# 'suspended' is also known as 'suspend' for historical reasons
SUSPEND_MODE = %w[suspend suspended].freeze
ROLES = (SUSPEND_MODE + %w[limited spam]).freeze
ROLES = (SUSPEND_MODE + %w[limited spam trusted]).freeze
before_action :check_role
before_action :set_target_user
@ -20,16 +20,16 @@ module Api
add_role_to_target_user
end
render json: { success: "okay" }, status: :no_content
render json: { success: "okay" }, status: :no_content # rubocop:disable Rails/UnusedRenderContent - adding this as it's been part of the API for a while
end
def destroy
# This mechanism for removing roles is pretty specific to limited, suspended and spam,
# where they revert to "Good standing" — we would need a different approach,
# possibly a whole different service object — to remove *any* roles
# This mechanism for removing roles is specific to limited, suspended, spam and trusted.
# We revert them to "Good standing". We would need a different approach,
# (possibly a whole different service object) to remove *any* roles
remove_role_from_target_user
render json: { success: "okay" }, status: :no_content
render json: { success: "okay" }, status: :no_content # rubocop:disable Rails/UnusedRenderContent - adding this as it's been part of the API for a while
end
private

View file

@ -73,11 +73,11 @@ Rails.application.routes.draw do
resources :organizations, only: %i[index create update destroy]
scope("/users/:id") do
constraints(role: /suspend|suspended|limited|spam/) do
constraints(role: /suspend|suspended|limited|spam|trusted/) do
put "/:role", to: "user_roles#update", as: "user_add_role"
end
constraints(role: /limited|spam/) do
constraints(role: /limited|spam|trusted/) do
delete "/:role", to: "user_roles#destroy", as: "user_remove_role"
end
end

View file

@ -293,6 +293,112 @@ notify them."
end
end
end
describe "PUT /users/:id/trusted" do
before do
user.add_role(:admin)
end
path "/api/users/{id}/trusted" do
put "Add trusted role for a User" do
tags "users"
description "This endpoint allows the client to add the trusted role to a user.
The user associated with the API key must have an 'admin' or 'moderator' role.
The specified user will be assigned the 'trusted' role. Adding the trusted role to a user
allows them to upvote and downvote posts and flag content that needs investigating by admins.
Users are notified of this change in the UI, and by email."
operationId "trustUser"
produces "application/json"
parameter name: :id, in: :path, required: true,
description: "The ID of the user to assign the trusted role.",
schema: {
type: :integer,
format: :int32,
minimum: 1
},
example: 1
response "204", "Trusted role assigned to the user successfully" do
let(:"api-key") { api_secret.secret }
let(:id) { user.id }
add_examples
run_test!
end
response "401", "Unauthorized" do
let(:regular_user) { create(:user) }
let(:low_security_api_secret) { create(:api_secret, user: regular_user) }
let(:"api-key") { low_security_api_secret.secret }
let(:id) { user.id }
add_examples
run_test!
end
response "404", "Unknown User ID" do
let(:"api-key") { api_secret.secret }
let(:id) { 10_000 }
add_examples
run_test!
end
end
end
end
describe "DELETE /users/:id/trusted" do
before do
user.add_role(:admin)
end
path "/api/users/{id}/trusted" do
delete "Remove trusted role from a User" do
tags "users"
description "This endpoint allows the client to remove the trusted role for a user.
The user associated with the API key must have an 'admin' or 'moderator' role.
The specified user will be restored to 'general' status. Users are not notified
of removing their trusted role in the UI, so if you want them to know about this, you must
notify them."
operationId "unTrustUser"
produces "application/json"
parameter name: :id, in: :path, required: true,
description: "The ID of the user to remove the trusted role from.",
schema: {
type: :integer,
format: :int32,
minimum: 1
},
example: 1
response "204", "Successfully removed the trusted role from a user" do
let(:"api-key") { api_secret.secret }
let(:id) { user.id }
add_examples
run_test!
end
response "401", "Unauthorized" do
let(:regular_user) { create(:user) }
let(:low_security_api_secret) { create(:api_secret, user: regular_user) }
let(:"api-key") { low_security_api_secret.secret }
let(:id) { user.id }
add_examples
run_test!
end
response "404", "Unknown User ID" do
let(:"api-key") { api_secret.secret }
let(:id) { 10_000 }
add_examples
run_test!
end
end
end
end
end
end
# rubocop:enable RSpec/VariableName

View file

@ -287,4 +287,110 @@ RSpec.describe "Api::V1::UserRoles" do
end
end
end
describe "PUT /api/users/:id/trusted", :aggregate_failures do
let(:target_user) { create(:user) }
before { Audit::Subscribe.listen listener }
after { Audit::Subscribe.forget listener }
context "when unauthenticated" do
it "returns unauthorized" do
put api_user_add_role_path(target_user, "trusted"), headers: headers
expect(response).to have_http_status(:unauthorized)
end
end
context "when unauthorized" do
it "returns unauthorized if api key is invalid" do
put api_user_add_role_path(target_user, "trusted"),
headers: headers.merge({ "api-key" => "invalid api key" })
expect(response).to have_http_status(:unauthorized)
end
it "returns unauthorized if api key belongs to non-admin user" do
put api_user_add_role_path(target_user, "trusted"), headers: auth_headers
expect(response).to have_http_status(:unauthorized)
end
end
context "when request is authenticated" do
before { api_secret.user.add_role(:super_admin) }
it "is successful in assigning the trusted role to a user", :aggregate_failures do
expect do
put api_user_add_role_path(target_user, "trusted"), headers: auth_headers
expect(response).to have_http_status(:no_content)
expect(target_user.reload.has_trusted_role?).to be true
expect(Note.last.content).to match(/username\d+ updated username\d+/)
end.to change(Note, :count).by(1)
end
it "creates an audit log of the action taken" do
put api_user_add_role_path(target_user, "trusted"), headers: auth_headers
log = AuditLog.last
expect(log.category).to eq(AuditLog::ADMIN_API_AUDIT_LOG_CATEGORY)
expect(log.data["action"]).to eq("api_user_trusted")
expect(log.data["target_user_id"]).to eq(target_user.id)
expect(log.user_id).to eq(api_secret.user.id)
end
end
end
describe "DELETE /api/users/:id/trusted", :aggregate_failures do
let(:target_user) { create(:user, :trusted) }
before { Audit::Subscribe.listen listener }
after { Audit::Subscribe.forget listener }
context "when unauthenticated" do
it "returns unauthorized" do
delete api_user_remove_role_path(target_user, "trusted"), headers: headers
expect(response).to have_http_status(:unauthorized)
end
end
context "when unauthorized" do
it "returns unauthorized if api key is invalid" do
delete api_user_remove_role_path(target_user, "trusted"),
headers: headers.merge({ "api-key" => "invalid api key" })
expect(response).to have_http_status(:unauthorized)
end
it "returns unauthorized if api key belongs to non-admin user" do
delete api_user_remove_role_path(target_user, "trusted"), headers: auth_headers
expect(response).to have_http_status(:unauthorized)
end
end
context "when request is authenticated" do
before { api_secret.user.add_role(:super_admin) }
it "is successful in removing the trusted role from a user", :aggregate_failures do
expect do
delete api_user_remove_role_path(target_user, "trusted"), headers: auth_headers
expect(response).to have_http_status(:no_content)
expect(target_user.reload.has_trusted_role?).to be false
expect(target_user.roles).to eq([])
expect(Note.last.content).to match(/username\d+ updated username\d+/)
end.to change(Note, :count).by(1)
end
it "creates an audit log of the action taken" do
delete api_user_remove_role_path(target_user, "trusted"), headers: auth_headers
log = AuditLog.last
expect(log.category).to eq(AuditLog::ADMIN_API_AUDIT_LOG_CATEGORY)
expect(log.data["action"]).to eq("api_user_remove_trusted")
expect(log.data["target_user_id"]).to eq(target_user.id)
expect(log.user_id).to eq(api_secret.user.id)
end
end
end
end

File diff suppressed because it is too large Load diff