Add or remove limited role via API (#20136)
* Refactor API user suspend, make room for more roles * Handle adding limited * Handle remove limited * And swagger docs * Update & refactor roles spec * Remove old route * Remove old specs
This commit is contained in:
parent
e5b4f6a7d0
commit
2c138d86ef
11 changed files with 1205 additions and 686 deletions
99
app/controllers/api/v1/user_roles_controller.rb
Normal file
99
app/controllers/api/v1/user_roles_controller.rb
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
module Api
|
||||
module V1
|
||||
class UserRolesController < ApiController
|
||||
before_action :authenticate_with_api_key!
|
||||
|
||||
# 'suspended' is also known as 'suspend' for historical reasons
|
||||
SUSPEND_MODE = %w[suspend suspended].freeze
|
||||
ROLES = (SUSPEND_MODE + %w[limited]).freeze
|
||||
|
||||
before_action :check_role
|
||||
before_action :set_target_user
|
||||
before_action :authorize_role_management
|
||||
|
||||
rescue_from StandardError, with: :unprocessable_error
|
||||
|
||||
def update
|
||||
if suspend_mode? # suspend user requires more data, such as note
|
||||
suspend_target_user
|
||||
else
|
||||
add_role_to_target_user
|
||||
end
|
||||
|
||||
render json: { success: "okay" }, status: :no_content
|
||||
end
|
||||
|
||||
def destroy
|
||||
# This mechanism for removing roles is pretty specific to limited & suspended,
|
||||
# where they revert 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
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def add_role_to_target_user
|
||||
manager = Moderator::ManageActivityAndRoles.new(admin: @user,
|
||||
user: @target_user,
|
||||
user_params: {})
|
||||
manager.handle_user_status(params[:role].titleize, nil)
|
||||
|
||||
payload = { action: "api_user_#{params[:role]}", target_user_id: @target_user.id }
|
||||
Audit::Logger.log(:admin_api, @user, payload)
|
||||
end
|
||||
|
||||
def authorize_role_management
|
||||
authorize(@target_user, :manage_user_roles?)
|
||||
rescue Pundit::NotAuthorizedError
|
||||
error_unauthorized
|
||||
end
|
||||
|
||||
def check_role
|
||||
return if ROLES.include?(params[:role])
|
||||
|
||||
raise StandardError, "Unable to process #{params[:role]}"
|
||||
end
|
||||
|
||||
def remove_role_from_target_user
|
||||
manager = Moderator::ManageActivityAndRoles.new(admin: @user,
|
||||
user: @target_user,
|
||||
user_params: {})
|
||||
manager.handle_user_status("Good standing", nil)
|
||||
|
||||
payload = { action: "api_user_remove_#{params[:role]}", target_user_id: @target_user.id }
|
||||
Audit::Logger.log(:admin_api, @user, payload)
|
||||
end
|
||||
|
||||
def set_target_user
|
||||
@target_user = User.find(params[:id])
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
error_not_found
|
||||
end
|
||||
|
||||
def suspend_mode?
|
||||
SUSPEND_MODE.include?(params[:role])
|
||||
end
|
||||
|
||||
def suspend_target_user
|
||||
suspend_params = { note_for_current_role: params[:note], user_status: "Suspended" }
|
||||
Moderator::ManageActivityAndRoles.handle_user_roles(admin: @user,
|
||||
user: @target_user,
|
||||
user_params: suspend_params)
|
||||
|
||||
payload = { action: "api_user_suspend", target_user_id: @target_user.id }
|
||||
Audit::Logger.log(:admin_api, @user, payload)
|
||||
end
|
||||
|
||||
def unprocessable_error(exception)
|
||||
message = @target_user&.errors_as_sentence || exception.message
|
||||
|
||||
render json: {
|
||||
success: false,
|
||||
message: message
|
||||
}, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -22,29 +22,6 @@ module Api
|
|||
render :show
|
||||
end
|
||||
|
||||
def suspend
|
||||
authorize(@user, :toggle_suspension_status?)
|
||||
|
||||
target_user = User.find(params[:id])
|
||||
suspend_params = { note_for_current_role: params[:note], user_status: "Suspended" }
|
||||
|
||||
begin
|
||||
Moderator::ManageActivityAndRoles.handle_user_roles(admin: @user,
|
||||
user: target_user,
|
||||
user_params: suspend_params)
|
||||
|
||||
payload = { action: "api_user_suspend", target_user_id: target_user.id }
|
||||
Audit::Logger.log(:admin_api, @user, payload)
|
||||
|
||||
render status: :no_content
|
||||
rescue StandardError
|
||||
render json: {
|
||||
success: false,
|
||||
message: @user.errors_as_sentence
|
||||
}, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def unpublish
|
||||
authorize(@user, :unpublish_all_articles?)
|
||||
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@ class UserPolicy < ApplicationPolicy
|
|||
end
|
||||
|
||||
alias toggle_suspension_status? elevated_user?
|
||||
alias manage_user_roles? elevated_user?
|
||||
alias unpublish_all_articles? elevated_user?
|
||||
|
||||
def moderation_routes?
|
||||
|
|
|
|||
|
|
@ -135,6 +135,7 @@ module Moderator
|
|||
end
|
||||
|
||||
def remove_negative_roles
|
||||
user.remove_role(:limited) if user.limited?
|
||||
user.remove_role(:suspended) if user.suspended?
|
||||
user.remove_role(:warned) if user.warned?
|
||||
user.remove_role(:comment_suspended) if user.comment_suspended?
|
||||
|
|
|
|||
|
|
@ -44,7 +44,6 @@ Rails.application.routes.draw do
|
|||
namespace :api, defaults: { format: "json" } do
|
||||
scope module: :v1, constraints: ApiConstraints.new(version: 1, default: false) do
|
||||
# V1 only endpoints
|
||||
put "/users/:id/suspend", to: "users#suspend", as: :user_suspend
|
||||
put "/articles/:id/unpublish", to: "articles#unpublish", as: :article_unpublish
|
||||
put "/users/:id/unpublish", to: "users#unpublish", as: :user_unpublish
|
||||
|
||||
|
|
@ -69,6 +68,16 @@ Rails.application.routes.draw do
|
|||
|
||||
resources :organizations, only: %i[index create update destroy]
|
||||
|
||||
scope("/users/:id") do
|
||||
constraints(role: /suspend|suspended|limited/) do
|
||||
put "/:role", to: "user_roles#update", as: "user_add_role"
|
||||
end
|
||||
|
||||
constraints(role: /limited/) do
|
||||
delete "/:role", to: "user_roles#destroy", as: "user_remove_role"
|
||||
end
|
||||
end
|
||||
|
||||
draw :api
|
||||
end
|
||||
|
||||
|
|
|
|||
188
spec/requests/api/v1/docs/user_roles_spec.rb
Normal file
188
spec/requests/api/v1/docs/user_roles_spec.rb
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
require "rails_helper"
|
||||
require "swagger_helper"
|
||||
|
||||
# rubocop:disable RSpec/EmptyExampleGroup
|
||||
# rubocop:disable RSpec/VariableName
|
||||
|
||||
RSpec.describe "Api::V1::Docs::Users" do
|
||||
let(:Accept) { "application/vnd.forem.api-v1+json" }
|
||||
let(:api_secret) { create(:api_secret) }
|
||||
let(:user) { api_secret.user }
|
||||
|
||||
let(:banned_user) { create(:user) }
|
||||
let(:article) { create(:article, user: banned_user, published: true) }
|
||||
let(:comment) { create(:comment, user: banned_user, article: article) }
|
||||
|
||||
before do
|
||||
user.add_role(:admin)
|
||||
end
|
||||
|
||||
describe "PUT /users/:id/suspend" do
|
||||
before do
|
||||
user.add_role(:admin)
|
||||
end
|
||||
|
||||
path "/api/users/{id}/suspend" do
|
||||
put "Suspend a User" do
|
||||
tags "users"
|
||||
description "This endpoint allows the client to suspend a user.
|
||||
|
||||
The user associated with the API key must have any 'admin' or 'moderator' role.
|
||||
|
||||
This specified user will be assigned the 'suspended' role. Suspending a user will stop the
|
||||
user from posting new posts and comments. It doesn't delete any of the user's content, just
|
||||
prevents them from creating new content while suspended. Users are not notified of their suspension
|
||||
in the UI, so if you want them to know about this, you must notify them."
|
||||
operationId "suspendUser"
|
||||
produces "application/json"
|
||||
parameter name: :id, in: :path, required: true,
|
||||
description: "The ID of the user to suspend.",
|
||||
schema: {
|
||||
type: :integer,
|
||||
format: :int32,
|
||||
minimum: 1
|
||||
},
|
||||
example: 1
|
||||
|
||||
response "204", "User successfully unpublished" do
|
||||
let(:"api-key") { api_secret.secret }
|
||||
let(:id) { banned_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) { banned_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 "PUT /users/:id/limited" do
|
||||
before do
|
||||
user.add_role(:admin)
|
||||
end
|
||||
|
||||
path "/api/users/{id}/limited" do
|
||||
put "Add limited role for a User" do
|
||||
tags "users"
|
||||
description "This endpoint allows the client to limit a user.
|
||||
|
||||
The user associated with the API key must have any 'admin' or 'moderator' role.
|
||||
|
||||
This specified user will be assigned the 'limited' role. Limiting a user will limit notifications
|
||||
generated from new posts and comments. It doesn't delete any of the user's content or prevent them
|
||||
from generating new content while limited. Users are not notified of their limits
|
||||
in the UI, so if you want them to know about this, you must notify them."
|
||||
operationId "limitUser"
|
||||
produces "application/json"
|
||||
parameter name: :id, in: :path, required: true,
|
||||
description: "The ID of the user to limit.",
|
||||
schema: {
|
||||
type: :integer,
|
||||
format: :int32,
|
||||
minimum: 1
|
||||
},
|
||||
example: 1
|
||||
|
||||
response "204", "User successfully limited" do
|
||||
let(:"api-key") { api_secret.secret }
|
||||
let(:id) { banned_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) { banned_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/limited" do
|
||||
before do
|
||||
user.add_role(:admin)
|
||||
end
|
||||
|
||||
path "/api/users/{id}/limited" do
|
||||
delete "Remove limited for a User" do
|
||||
tags "users"
|
||||
description "This endpoint allows the client to remove limits for a user.
|
||||
|
||||
The user associated with the API key must have any 'admin' or 'moderator' role.
|
||||
|
||||
This specified user will be restored to 'general' status. Users are not notified
|
||||
of limits in the UI, so if you want them to know about this, you must
|
||||
notify them."
|
||||
operationId "unLimitUser"
|
||||
produces "application/json"
|
||||
parameter name: :id, in: :path, required: true,
|
||||
description: "The ID of the user to un-limit.",
|
||||
schema: {
|
||||
type: :integer,
|
||||
format: :int32,
|
||||
minimum: 1
|
||||
},
|
||||
example: 1
|
||||
|
||||
response "204", "User successfully un-limited" do
|
||||
let(:"api-key") { api_secret.secret }
|
||||
let(:id) { banned_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) { banned_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
|
||||
# rubocop:enable RSpec/VariableName
|
||||
# rubocop:enable RSpec/EmptyExampleGroup
|
||||
|
|
@ -127,62 +127,6 @@ request is completed on the server."
|
|||
end
|
||||
end
|
||||
|
||||
describe "PUT /users/:id/suspend" do
|
||||
before do
|
||||
user.add_role(:admin)
|
||||
end
|
||||
|
||||
path "/api/users/{id}/suspend" do
|
||||
put "Suspend a User" do
|
||||
tags "users"
|
||||
description "This endpoint allows the client to suspend a user.
|
||||
|
||||
The user associated with the API key must have any 'admin' or 'moderator' role.
|
||||
|
||||
This specified user will be assigned the 'suspended' role. Suspending a user will stop the
|
||||
user from posting new posts and comments. It doesn't delete any of the user's content, just
|
||||
prevents them from creating new content while suspended. Users are not notified of their suspension
|
||||
in the UI, so if you want them to know about this, you must notify them."
|
||||
operationId "suspendUser"
|
||||
produces "application/json"
|
||||
parameter name: :id, in: :path, required: true,
|
||||
description: "The ID of the user to suspend.",
|
||||
schema: {
|
||||
type: :integer,
|
||||
format: :int32,
|
||||
minimum: 1
|
||||
},
|
||||
example: 1
|
||||
|
||||
response "204", "User successfully unpublished" do
|
||||
let(:"api-key") { api_secret.secret }
|
||||
let(:id) { banned_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) { banned_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 "POST /api/admin/users" do
|
||||
before do
|
||||
user.add_role(:super_admin)
|
||||
|
|
|
|||
181
spec/requests/api/v1/user_roles_spec.rb
Normal file
181
spec/requests/api/v1/user_roles_spec.rb
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
require "rails_helper"
|
||||
|
||||
RSpec.describe "Api::V1::UserRoles" do
|
||||
let(:api_secret) { create(:api_secret) }
|
||||
let(:headers) { { "Accept" => "application/vnd.forem.api-v1+json" } }
|
||||
let(:auth_headers) { headers.merge({ "api-key" => api_secret.secret }) }
|
||||
let(:listener) { :admin_api }
|
||||
|
||||
describe "PUT /api/users/:id/suspend", :aggregate_failures do
|
||||
let(:target_user) { create(:user) }
|
||||
let(:payload) { { note: "Violated CoC despite multiple warnings" } }
|
||||
|
||||
before { Audit::Subscribe.listen listener }
|
||||
after { Audit::Subscribe.forget listener }
|
||||
|
||||
context "when unauthenticated" do
|
||||
it "returns unauthorized" do
|
||||
put api_user_add_role_path(id: target_user.id, role: "suspended"),
|
||||
params: payload,
|
||||
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(id: target_user.id, role: "suspended"),
|
||||
params: payload,
|
||||
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(id: target_user.id, role: "suspended"),
|
||||
params: payload,
|
||||
headers: 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 suspending a user", :aggregate_failures do
|
||||
expect do
|
||||
put api_user_add_role_path(id: target_user.id, role: "suspended"),
|
||||
params: payload,
|
||||
headers: auth_headers
|
||||
|
||||
expect(response).to have_http_status(:no_content)
|
||||
expect(target_user.reload.suspended?).to be true
|
||||
expect(Note.last.content).to eq(payload[:note])
|
||||
end.to change(Note, :count).by(1)
|
||||
end
|
||||
|
||||
it "creates an audit log of the action taken" do
|
||||
put api_user_add_role_path(id: target_user.id, role: "suspended"),
|
||||
params: payload,
|
||||
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_suspend")
|
||||
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 "PUT /api/users/:id/limited", :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, "limited"), 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, "limited"),
|
||||
headers: headers.merge({ "api-key" => "invalid api key" })
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
|
||||
# Setup via let(:api_secret) creates a user with no admin privileges
|
||||
it "returns unauthorized if api key belongs to non-admin user" do
|
||||
put api_user_add_role_path(target_user, "limited"), 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 limiting a user", :aggregate_failures do
|
||||
expect do
|
||||
put api_user_add_role_path(target_user, "limited"), headers: auth_headers
|
||||
|
||||
expect(response).to have_http_status(:no_content)
|
||||
expect(target_user.reload.limited?).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, "limited"), 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_limited")
|
||||
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/limited", :aggregate_failures do
|
||||
let(:target_user) { create(:user, :limited) }
|
||||
|
||||
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, "limited"), 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, "limited"),
|
||||
headers: headers.merge({ "api-key" => "invalid api key" })
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
|
||||
# Setup via let(:api_secret) creates a user with no admin privileges
|
||||
it "returns unauthorized if api key belongs to non-admin user" do
|
||||
delete api_user_remove_role_path(target_user, "limited"), 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 limiting a user", :aggregate_failures do
|
||||
expect do
|
||||
delete api_user_remove_role_path(target_user, "limited"), headers: auth_headers
|
||||
|
||||
expect(response).to have_http_status(:no_content)
|
||||
expect(target_user.reload.limited?).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, "limited"), 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_limited")
|
||||
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
|
||||
|
|
@ -123,71 +123,6 @@ RSpec.describe "Api::V1::Users" do
|
|||
end
|
||||
end
|
||||
|
||||
describe "PUT /api/users/:id/suspend", :aggregate_failures do
|
||||
let(:target_user) { create(:user) }
|
||||
let(:payload) { { note: "Violated CoC despite multiple warnings" } }
|
||||
|
||||
before { Audit::Subscribe.listen listener }
|
||||
|
||||
after { Audit::Subscribe.forget listener }
|
||||
|
||||
context "when unauthenticated" do
|
||||
it "returns unauthorized" do
|
||||
put api_user_suspend_path(id: target_user.id),
|
||||
params: payload,
|
||||
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_suspend_path(id: target_user.id),
|
||||
params: payload,
|
||||
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_suspend_path(id: target_user.id),
|
||||
params: payload,
|
||||
headers: 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 suspending a user", :aggregate_failures do
|
||||
expect do
|
||||
put api_user_suspend_path(id: target_user.id),
|
||||
params: payload,
|
||||
headers: auth_headers
|
||||
|
||||
expect(response).to have_http_status(:no_content)
|
||||
expect(target_user.reload.suspended?).to be true
|
||||
expect(Note.last.content).to eq(payload[:note])
|
||||
end.to change(Note, :count).by(1)
|
||||
end
|
||||
|
||||
it "creates an audit log of the action taken" do
|
||||
put api_user_suspend_path(id: target_user.id),
|
||||
params: payload,
|
||||
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_suspend")
|
||||
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 "PUT /api/users/:id/unpublish", :aggregate_failures do
|
||||
let(:target_user) { create(:user) }
|
||||
let!(:target_articles) { create_list(:article, 3, user: target_user, published: true) }
|
||||
|
|
@ -227,8 +162,8 @@ RSpec.describe "Api::V1::Users" do
|
|||
|
||||
it "is successful in unpublishing a user's comments and articles", :aggregate_failures do
|
||||
# User's articles are published and comments exist
|
||||
expect(target_articles.map(&:published?)).to match_array([true, true, true])
|
||||
expect(target_comments.map(&:deleted)).to match_array([false, false, false])
|
||||
expect(target_articles.map(&:published?)).to contain_exactly(true, true, true)
|
||||
expect(target_comments.map(&:deleted)).to contain_exactly(false, false, false)
|
||||
|
||||
sidekiq_perform_enqueued_jobs(only: Moderator::UnpublishAllArticlesWorker) do
|
||||
put api_user_unpublish_path(id: target_user.id), headers: auth_headers
|
||||
|
|
@ -238,8 +173,8 @@ RSpec.describe "Api::V1::Users" do
|
|||
|
||||
# Ensure article's aren't published and comments deleted
|
||||
# (with boolean attribute so they can be reverted if needed)
|
||||
expect(target_articles.map(&:reload).map(&:published?)).to match_array([false, false, false])
|
||||
expect(target_comments.map(&:reload).map(&:deleted)).to match_array([true, true, true])
|
||||
expect(target_articles.map(&:reload).map(&:published?)).to contain_exactly(false, false, false)
|
||||
expect(target_comments.map(&:reload).map(&:deleted)).to contain_exactly(true, true, true)
|
||||
end
|
||||
|
||||
it "creates an audit log of the action taken" do
|
||||
|
|
|
|||
|
|
@ -4,95 +4,199 @@ RSpec.describe Moderator::ManageActivityAndRoles, type: :service do
|
|||
let(:user) { create(:user) }
|
||||
let(:admin) { create(:user, :super_admin) }
|
||||
|
||||
it "updates user status" do
|
||||
user.add_role(:suspended)
|
||||
user.reload
|
||||
def manage_roles_for(user, user_status:, note: "Test note", acting_as: admin)
|
||||
described_class.handle_user_roles(
|
||||
admin: admin,
|
||||
admin: acting_as,
|
||||
user: user,
|
||||
user_params: { note_for_current_role: "warning user", user_status: "Warned" },
|
||||
user_params: {
|
||||
note_for_current_role: note,
|
||||
user_status: user_status
|
||||
},
|
||||
)
|
||||
expect(user.warned?).to be true
|
||||
expect(user.suspended?).to be false
|
||||
end
|
||||
|
||||
shared_examples_for "elevated role" do |status|
|
||||
context "when user is in limited role" do
|
||||
before { user.add_role(:limited) }
|
||||
|
||||
it "adding #{status} also removes the limited role" do
|
||||
expect(user.roles.pluck(:name)).to include("limited") # confirm assumptions
|
||||
manage_roles_for user, user_status: status
|
||||
expect(user.roles.pluck(:name)).not_to include("limited") # confirm assumptions
|
||||
end
|
||||
end
|
||||
|
||||
context "when user is in suspended role" do
|
||||
before { user.add_role(:suspended) }
|
||||
|
||||
it "adding #{status} also removes the suspended role" do
|
||||
expect(user.roles.pluck(:name)).to include("suspended") # confirm assumptions
|
||||
manage_roles_for user, user_status: status
|
||||
expect(user.roles.pluck(:name)).not_to include("suspended") # confirm assumptions
|
||||
end
|
||||
end
|
||||
|
||||
context "when user is in warned role" do
|
||||
before { user.add_role(:warned) }
|
||||
|
||||
it "adding #{status} also removes the warned role" do
|
||||
expect(user.roles.pluck(:name)).to include("warned") # confirm assumptions
|
||||
manage_roles_for user, user_status: status
|
||||
expect(user.roles.pluck(:name)).not_to include("warned") # confirm assumptions
|
||||
end
|
||||
end
|
||||
|
||||
context "when user is in comment_suspended role" do
|
||||
before { user.add_role(:comment_suspended) }
|
||||
|
||||
it "adding #{status} also removes the comment_suspended role" do
|
||||
expect(user.roles.pluck(:name)).to include("comment_suspended") # confirm assumptions
|
||||
manage_roles_for user, user_status: status
|
||||
expect(user.roles.pluck(:name)).not_to include("comment_suspended") # confirm assumptions
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples_for "negative role" do |status|
|
||||
context "when user is in trusted role" do
|
||||
before { user.add_role(:trusted) }
|
||||
|
||||
it "adding #{status} removes the trusted role" do
|
||||
expect(user.roles.pluck(:name)).to include("trusted") # confirm assumptions
|
||||
manage_roles_for user, user_status: status
|
||||
expect(user.roles.pluck(:name)).not_to include("trusted") # confirm assumptions
|
||||
end
|
||||
end
|
||||
|
||||
context "when user is in tag_moderator role" do
|
||||
before { user.add_role(:tag_moderator) }
|
||||
|
||||
it "adding #{status} removes the tag_moderator role" do
|
||||
expect(user.roles.pluck(:name)).to include("tag_moderator") # confirm assumptions
|
||||
manage_roles_for user, user_status: status
|
||||
expect(user.roles.pluck(:name)).not_to include("tag_moderator") # confirm assumptions
|
||||
end
|
||||
end
|
||||
|
||||
context "when user is in admin role" do
|
||||
before { user.add_role(:admin) }
|
||||
|
||||
it "adding #{status} ignores the admin role" do
|
||||
expect(user.roles.pluck(:name)).to include("admin") # confirm assumptions
|
||||
manage_roles_for user, user_status: status
|
||||
expect(user.roles.pluck(:name)).to include("admin") # confirm assumptions
|
||||
end
|
||||
end
|
||||
|
||||
context "when user is in super_moderator role" do
|
||||
before { user.add_role(:super_moderator) }
|
||||
|
||||
it "adding #{status} ignores the super_moderator role" do
|
||||
expect(user.roles.pluck(:name)).to include("super_moderator") # confirm assumptions
|
||||
manage_roles_for user, user_status: status
|
||||
expect(user.roles.pluck(:name)).to include("super_moderator") # confirm assumptions
|
||||
end
|
||||
end
|
||||
|
||||
context "when user is in tech_admin role" do
|
||||
before { user.add_role(:tech_admin) }
|
||||
|
||||
it "adding #{status} ignores the tech_admin role" do
|
||||
expect(user.roles.pluck(:name)).to include("tech_admin") # confirm assumptions
|
||||
manage_roles_for user, user_status: status
|
||||
expect(user.roles.pluck(:name)).to include("tech_admin") # confirm assumptions
|
||||
end
|
||||
end
|
||||
|
||||
context "when user is in limited role" do
|
||||
before { user.add_role(:limited) }
|
||||
|
||||
it "adding #{status} ignores the limited role" do
|
||||
expect(user.roles.pluck(:name)).to include("limited") # confirm assumptions
|
||||
manage_roles_for user, user_status: status
|
||||
expect(user.roles.pluck(:name)).to include("limited") # confirm assumptions
|
||||
end
|
||||
end
|
||||
|
||||
context "when user is in warned role" do
|
||||
before { user.add_role(:warned) }
|
||||
|
||||
it "adding #{status} ignores the warned role" do
|
||||
expect(user.roles.pluck(:name)).to include("warned") # confirm assumptions
|
||||
manage_roles_for user, user_status: status
|
||||
expect(user.roles.pluck(:name)).to include("warned") # confirm assumptions
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like "elevated role", "Admin"
|
||||
it_behaves_like "elevated role", "Super Moderator"
|
||||
it_behaves_like "elevated role", "Resource Admin: Tag"
|
||||
it_behaves_like "elevated role", "Super Admin"
|
||||
it_behaves_like "elevated role", "Trusted"
|
||||
it_behaves_like "elevated role", "Good standing"
|
||||
it_behaves_like "elevated role", "Tech Admin"
|
||||
|
||||
it_behaves_like "negative role", "Suspended"
|
||||
it_behaves_like "negative role", "Limited"
|
||||
it_behaves_like "negative role", "Warned"
|
||||
|
||||
context "when user is in suspended role" do
|
||||
before { user.add_role(:suspended) }
|
||||
|
||||
it "adding warned removes the suspended role" do
|
||||
expect(user.roles.pluck(:name)).to include("suspended") # confirm assumptions
|
||||
manage_roles_for user, user_status: "Warned"
|
||||
expect(user.roles.pluck(:name)).not_to include("suspended") # confirm assumptions
|
||||
end
|
||||
end
|
||||
|
||||
it "updates user status to limited" do
|
||||
user.add_role(:limited)
|
||||
user.reload
|
||||
described_class.handle_user_roles(
|
||||
admin: admin,
|
||||
user: user,
|
||||
user_params: { note_for_current_role: "limited user", user_status: "Limited" },
|
||||
)
|
||||
expect(user.limited?).to be true
|
||||
expect(user).not_to be_limited
|
||||
manage_roles_for(user, user_status: "Limited")
|
||||
expect(user).to be_limited
|
||||
manage_roles_for(user, user_status: "Good standing")
|
||||
expect(user).not_to be_limited
|
||||
end
|
||||
|
||||
it "updates user to super admin" do
|
||||
described_class.handle_user_roles(
|
||||
admin: admin,
|
||||
user: user,
|
||||
user_params: { note_for_current_role: "Upgrading to super admin", user_status: "Super Admin" },
|
||||
)
|
||||
expect(user.super_admin?).to be true
|
||||
end
|
||||
|
||||
it "assigns trusted role to user that's updated to super admin" do
|
||||
described_class.handle_user_roles(
|
||||
admin: admin,
|
||||
user: user,
|
||||
user_params: { note_for_current_role: "Upgrading to super admin", user_status: "Super Admin" },
|
||||
)
|
||||
expect(user.super_admin?).to be true
|
||||
expect(user).not_to be_super_admin
|
||||
expect(user.has_trusted_role?).to be false
|
||||
manage_roles_for(user, user_status: "Super Admin")
|
||||
expect(user).to be_super_admin
|
||||
expect(user.has_trusted_role?).to be true
|
||||
end
|
||||
|
||||
it "updates user to admin" do
|
||||
described_class.handle_user_roles(
|
||||
admin: admin,
|
||||
user: user,
|
||||
user_params: { note_for_current_role: "Upgrading to admin", user_status: "Admin" },
|
||||
)
|
||||
expect(user.admin?).to be true
|
||||
end
|
||||
|
||||
it "assigns trusted role to user that's updated to admin" do
|
||||
described_class.handle_user_roles(
|
||||
admin: admin,
|
||||
user: user,
|
||||
user_params: { note_for_current_role: "Upgrading to admin", user_status: "Admin" },
|
||||
)
|
||||
expect(user.admin?).to be true
|
||||
expect(user).not_to be_admin
|
||||
expect(user.has_trusted_role?).to be false
|
||||
manage_roles_for(user, user_status: "Admin")
|
||||
expect(user).to be_admin
|
||||
expect(user.has_trusted_role?).to be true
|
||||
end
|
||||
|
||||
it "updates user to tech admin" do
|
||||
described_class.handle_user_roles(
|
||||
admin: admin,
|
||||
user: user,
|
||||
user_params: { note_for_current_role: "Upgrading to tech admin", user_status: "Tech Admin" },
|
||||
)
|
||||
expect(user.tech_admin?).to be true
|
||||
expect(user).not_to be_tech_admin
|
||||
expect(user.single_resource_admin_for?(DataUpdateScript)).to be false
|
||||
manage_roles_for(user, user_status: "Tech Admin")
|
||||
expect(user).to be_tech_admin
|
||||
expect(user.single_resource_admin_for?(DataUpdateScript)).to be true
|
||||
end
|
||||
|
||||
it "updates user to single resource admin" do
|
||||
described_class.handle_user_roles(
|
||||
admin: admin,
|
||||
user: user,
|
||||
user_params: { note_for_current_role: "Upgrading to super admin", user_status: "Resource Admin: Article" },
|
||||
)
|
||||
expect(user.single_resource_admin_for?(Article)).to be false
|
||||
manage_roles_for(user, user_status: "Resource Admin: Article")
|
||||
expect(user.single_resource_admin_for?(Article)).to be true
|
||||
end
|
||||
|
||||
it "updates negative role to positive role" do
|
||||
it "user in 'Good standing' has no negative or elevated roles" do
|
||||
user.add_role(:comment_suspended)
|
||||
described_class.handle_user_roles(
|
||||
admin: admin,
|
||||
user: user,
|
||||
user_params: { note_for_current_role: "user in good standing", user_status: "Good standing" },
|
||||
)
|
||||
user.add_role(:warned)
|
||||
user.add_role(:trusted)
|
||||
manage_roles_for(user, user_status: "Good standing")
|
||||
expect(user.suspended?).to be false
|
||||
expect(user.roles.count).to eq(0)
|
||||
expect(user.has_trusted_role?).to be false
|
||||
end
|
||||
|
||||
describe "Rack::Attack cache invalidation optimization" do
|
||||
|
|
@ -135,43 +239,27 @@ RSpec.describe Moderator::ManageActivityAndRoles, type: :service do
|
|||
admin.add_role(:admin)
|
||||
end
|
||||
|
||||
it "updates user to super admin" do
|
||||
it "raises exception when trying to upgrade user to super admin" do
|
||||
expect do
|
||||
described_class.handle_user_roles(
|
||||
admin: admin,
|
||||
user: user,
|
||||
user_params: { note_for_current_role: "Upgrading to super admin", user_status: "Super Admin" },
|
||||
)
|
||||
manage_roles_for(user, user_status: "Super Admin")
|
||||
end.to raise_error(StandardError)
|
||||
end
|
||||
|
||||
it "updates user to admin" do
|
||||
it "raises exception when trying to upgrade user to admin" do
|
||||
expect do
|
||||
described_class.handle_user_roles(
|
||||
admin: admin,
|
||||
user: user,
|
||||
user_params: { note_for_current_role: "Upgrading to super admin", user_status: "Admin" },
|
||||
)
|
||||
manage_roles_for(user, user_status: "Admin")
|
||||
end.to raise_error(StandardError)
|
||||
end
|
||||
|
||||
it "updates user to single resource admin" do
|
||||
it "raises exception when trying to upgrade user to single resource admin" do
|
||||
expect do
|
||||
described_class.handle_user_roles(
|
||||
admin: admin,
|
||||
user: user,
|
||||
user_params: { note_for_current_role: "Upgrading to super admin", user_status: "Resource Admin: Article" },
|
||||
)
|
||||
manage_roles_for(user, user_status: "Resource Admin: Article")
|
||||
end.to raise_error(StandardError)
|
||||
end
|
||||
|
||||
it "updates user to super moderator" do
|
||||
it "raises exception when trying to upgrade user to super moderator" do
|
||||
expect do
|
||||
described_class.handle_user_roles(
|
||||
admin: admin,
|
||||
user: user,
|
||||
user_params: { note_for_current_role: "Upgrading to super_moderator", user_status: "Super Moderator" },
|
||||
)
|
||||
manage_roles_for(user, user_status: "Super Moderator")
|
||||
end.to raise_error(StandardError)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue