Commit b555a3ca authored by Jan Provaznik's avatar Jan Provaznik

Merge branch 'ban-user-api' into 'master'

Ban and unban user via API

See merge request gitlab-org/gitlab!68332
parents 5f1b1e71 4ea4ec48
...@@ -352,6 +352,10 @@ class User < ApplicationRecord ...@@ -352,6 +352,10 @@ class User < ApplicationRecord
transition active: :banned transition active: :banned
end end
event :unban do
transition banned: :active
end
event :deactivate do event :deactivate do
# Any additional changes to this event should be also # Any additional changes to this event should be also
# reflected in app/workers/users/deactivate_dormant_users_worker.rb # reflected in app/workers/users/deactivate_dormant_users_worker.rb
......
...@@ -8,6 +8,10 @@ module Users ...@@ -8,6 +8,10 @@ module Users
user.ban user.ban
end end
def valid_state?(user)
user.active?
end
def action def action
:ban :ban
end end
......
...@@ -8,6 +8,7 @@ module Users ...@@ -8,6 +8,7 @@ module Users
def execute(user) def execute(user)
return permission_error unless allowed? return permission_error unless allowed?
return state_error(user) unless valid_state?(user)
if update_user(user) if update_user(user)
log_event(user) log_event(user)
...@@ -22,6 +23,10 @@ module Users ...@@ -22,6 +23,10 @@ module Users
attr_reader :current_user attr_reader :current_user
def state_error(user)
error(_("You cannot %{action} %{state} users." % { action: action.to_s, state: user.state }), :forbidden)
end
def allowed? def allowed?
can?(current_user, :admin_all_resources) can?(current_user, :admin_all_resources)
end end
......
...@@ -5,7 +5,11 @@ module Users ...@@ -5,7 +5,11 @@ module Users
private private
def update_user(user) def update_user(user)
user.activate user.unban
end
def valid_state?(user)
user.banned?
end end
def action def action
......
...@@ -1467,6 +1467,46 @@ Returns: ...@@ -1467,6 +1467,46 @@ Returns:
- `404 User Not Found` if the user cannot be found. - `404 User Not Found` if the user cannot be found.
- `403 Forbidden` if the user cannot be activated because they are blocked by an administrator or by LDAP synchronization. - `403 Forbidden` if the user cannot be activated because they are blocked by an administrator or by LDAP synchronization.
## Ban user
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/327354) in GitLab 14.3.
Bans the specified user. Available only for admin.
```plaintext
POST /users/:id/ban
```
Parameters:
- `id` (required) - ID of specified user
Returns:
- `201 OK` on success.
- `404 User Not Found` if user cannot be found.
- `403 Forbidden` when trying to ban a user that is not active.
## Unban user
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/327354) in GitLab 14.3.
Unbans the specified user. Available only for admin.
```plaintext
POST /users/:id/unban
```
Parameters:
- `id` (required) - ID of specified user
Returns:
- `201 OK` on success.
- `404 User Not Found` if the user cannot be found.
- `403 Forbidden` when trying to unban a user that is not banned.
### Get user contribution events ### Get user contribution events
Please refer to the [Events API documentation](events.md#get-user-contribution-events) Please refer to the [Events API documentation](events.md#get-user-contribution-events)
......
...@@ -687,6 +687,38 @@ module API ...@@ -687,6 +687,38 @@ module API
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
desc 'Ban a user. Available only for admins.'
params do
requires :id, type: Integer, desc: 'The ID of the user'
end
post ':id/ban', feature_category: :authentication_and_authorization do
authenticated_as_admin!
user = find_user_by_id(params)
result = ::Users::BanService.new(current_user).execute(user)
if result[:status] == :success
true
else
render_api_error!(result[:message], result[:http_status])
end
end
desc 'Unban a user. Available only for admins.'
params do
requires :id, type: Integer, desc: 'The ID of the user'
end
post ':id/unban', feature_category: :authentication_and_authorization do
authenticated_as_admin!
user = find_user_by_id(params)
result = ::Users::UnbanService.new(current_user).execute(user)
if result[:status] == :success
true
else
render_api_error!(result[:message], result[:http_status])
end
end
desc 'Get memberships' do desc 'Get memberships' do
success Entities::Membership success Entities::Membership
end end
......
...@@ -38218,6 +38218,9 @@ msgstr "" ...@@ -38218,6 +38218,9 @@ msgstr ""
msgid "You can view the source or %{linkStart}%{cloneIcon} clone the repository%{linkEnd}" msgid "You can view the source or %{linkStart}%{cloneIcon} clone the repository%{linkEnd}"
msgstr "" msgstr ""
msgid "You cannot %{action} %{state} users."
msgstr ""
msgid "You cannot access the raw file. Please wait a minute." msgid "You cannot access the raw file. Please wait a minute."
msgstr "" msgstr ""
......
...@@ -1919,15 +1919,15 @@ RSpec.describe User do ...@@ -1919,15 +1919,15 @@ RSpec.describe User do
user.ban! user.ban!
end end
it 'activates the user' do it 'unbans the user' do
user.activate user.unban
expect(user.banned?).to eq(false) expect(user.banned?).to eq(false)
expect(user.active?).to eq(true) expect(user.active?).to eq(true)
end end
it 'deletes the BannedUser record' do it 'deletes the BannedUser record' do
expect { user.activate }.to change { Users::BannedUser.count }.by(-1) expect { user.unban }.to change { Users::BannedUser.count }.by(-1)
expect(Users::BannedUser.where(user_id: user.id)).not_to exist expect(Users::BannedUser.where(user_id: user.id)).not_to exist
end end
end end
......
...@@ -12,6 +12,8 @@ RSpec.describe API::Users do ...@@ -12,6 +12,8 @@ RSpec.describe API::Users do
let(:omniauth_user) { create(:omniauth_user) } let(:omniauth_user) { create(:omniauth_user) }
let(:ldap_blocked_user) { create(:omniauth_user, provider: 'ldapmain', state: 'ldap_blocked') } let(:ldap_blocked_user) { create(:omniauth_user, provider: 'ldapmain', state: 'ldap_blocked') }
let(:private_user) { create(:user, private_profile: true) } let(:private_user) { create(:user, private_profile: true) }
let(:deactivated_user) { create(:user, state: 'deactivated') }
let(:banned_user) { create(:user, :banned) }
context 'admin notes' do context 'admin notes' do
let_it_be(:admin) { create(:admin, note: '2019-10-06 | 2FA added | user requested | www.gitlab.com') } let_it_be(:admin) { create(:admin, note: '2019-10-06 | 2FA added | user requested | www.gitlab.com') }
...@@ -2964,6 +2966,169 @@ RSpec.describe API::Users do ...@@ -2964,6 +2966,169 @@ RSpec.describe API::Users do
end end
end end
describe 'POST /users/:id/ban', :aggregate_failures do
context 'when admin' do
subject(:ban_user) { post api("/users/#{user_id}/ban", admin) }
context 'with an active user' do
let(:user_id) { user.id }
it 'bans an active user' do
ban_user
expect(response).to have_gitlab_http_status(:created)
expect(response.body).to eq('true')
expect(user.reload.state).to eq('banned')
end
end
context 'with an ldap blocked user' do
let(:user_id) { ldap_blocked_user.id }
it 'does not ban ldap blocked users' do
ban_user
expect(response).to have_gitlab_http_status(:forbidden)
expect(json_response['message']).to eq('You cannot ban ldap_blocked users.')
expect(ldap_blocked_user.reload.state).to eq('ldap_blocked')
end
end
context 'with a deactivated user' do
let(:user_id) { deactivated_user.id }
it 'does not ban deactivated users' do
ban_user
expect(response).to have_gitlab_http_status(:forbidden)
expect(json_response['message']).to eq('You cannot ban deactivated users.')
expect(deactivated_user.reload.state).to eq('deactivated')
end
end
context 'with a banned user' do
let(:user_id) { banned_user.id }
it 'does not ban banned users' do
ban_user
expect(response).to have_gitlab_http_status(:forbidden)
expect(json_response['message']).to eq('You cannot ban banned users.')
expect(banned_user.reload.state).to eq('banned')
end
end
context 'with a non existent user' do
let(:user_id) { non_existing_record_id }
it 'does not ban non existent users' do
ban_user
expect(response).to have_gitlab_http_status(:not_found)
expect(json_response['message']).to eq('404 User Not Found')
end
end
context 'with an invalid id' do
let(:user_id) { 'ASDF' }
it 'does not ban invalid id users' do
ban_user
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
it 'is not available for non-admin users' do
post api("/users/#{user.id}/ban", user)
expect(response).to have_gitlab_http_status(:forbidden)
expect(user.reload.state).to eq('active')
end
end
describe 'POST /users/:id/unban', :aggregate_failures do
context 'when admin' do
subject(:unban_user) { post api("/users/#{user_id}/unban", admin) }
context 'with a banned user' do
let(:user_id) { banned_user.id }
it 'activates a banned user' do
unban_user
expect(response).to have_gitlab_http_status(:created)
expect(banned_user.reload.state).to eq('active')
end
end
context 'with an ldap_blocked user' do
let(:user_id) { ldap_blocked_user.id }
it 'does not unban ldap_blocked users' do
unban_user
expect(response).to have_gitlab_http_status(:forbidden)
expect(json_response['message']).to eq('You cannot unban ldap_blocked users.')
expect(ldap_blocked_user.reload.state).to eq('ldap_blocked')
end
end
context 'with a deactivated user' do
let(:user_id) { deactivated_user.id }
it 'does not unban deactivated users' do
unban_user
expect(response).to have_gitlab_http_status(:forbidden)
expect(json_response['message']).to eq('You cannot unban deactivated users.')
expect(deactivated_user.reload.state).to eq('deactivated')
end
end
context 'with an active user' do
let(:user_id) { user.id }
it 'does not unban active users' do
unban_user
expect(response).to have_gitlab_http_status(:forbidden)
expect(json_response['message']).to eq('You cannot unban active users.')
expect(user.reload.state).to eq('active')
end
end
context 'with a non existent user' do
let(:user_id) { non_existing_record_id }
it 'does not unban non existent users' do
unban_user
expect(response).to have_gitlab_http_status(:not_found)
expect(json_response['message']).to eq('404 User Not Found')
end
end
context 'with an invalid id user' do
let(:user_id) { 'ASDF' }
it 'does not unban invalid id users' do
unban_user
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
it 'is not available for non admin users' do
post api("/users/#{banned_user.id}/unban", user)
expect(response).to have_gitlab_http_status(:forbidden)
expect(user.reload.state).to eq('active')
end
end
describe "GET /users/:id/memberships" do describe "GET /users/:id/memberships" do
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) } let_it_be(:project) { create(:project) }
......
...@@ -50,7 +50,7 @@ RSpec.describe Users::BanService do ...@@ -50,7 +50,7 @@ RSpec.describe Users::BanService do
response = ban_user response = ban_user
expect(response[:status]).to eq(:error) expect(response[:status]).to eq(:error)
expect(response[:message]).to match(/State cannot transition/) expect(response[:message]).to match('You cannot ban blocked users.')
end end
it_behaves_like 'does not modify the BannedUser record or user state' it_behaves_like 'does not modify the BannedUser record or user state'
......
...@@ -50,7 +50,7 @@ RSpec.describe Users::UnbanService do ...@@ -50,7 +50,7 @@ RSpec.describe Users::UnbanService do
response = unban_user response = unban_user
expect(response[:status]).to eq(:error) expect(response[:status]).to eq(:error)
expect(response[:message]).to match(/State cannot transition/) expect(response[:message]).to match('You cannot unban active users.')
end end
it_behaves_like 'does not modify the BannedUser record or user state' it_behaves_like 'does not modify the BannedUser record or user state'
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment