From d64ceda60624befb8d791815ce5ca8a2bb056c14 Mon Sep 17 00:00:00 2001 From: sameer shaik Date: Mon, 17 Apr 2023 04:30:20 +0000 Subject: [PATCH 01/13] Add user deactivate service Add user deactivate service and log audit event at the instance level Changelog: added MR: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/117776 EE: true --- app/controllers/admin/users_controller.rb | 11 +++-- app/services/users/deactivate_service.rb | 22 +++++++++ .../services/ee/users/deactivate_service.rb | 31 ++++++++++++ .../audit_events/types/deactivate_user.yml | 8 ++++ .../ee/users/deactivate_service_spec.rb | 41 ++++++++++++++++ .../services/users/deactivate_service_spec.rb | 47 +++++++++++++++++++ 6 files changed, 157 insertions(+), 3 deletions(-) create mode 100644 app/services/users/deactivate_service.rb create mode 100644 ee/app/services/ee/users/deactivate_service.rb create mode 100644 ee/config/audit_events/types/deactivate_user.yml create mode 100644 ee/spec/services/ee/users/deactivate_service_spec.rb create mode 100644 spec/services/users/deactivate_service_spec.rb diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 7fc534be25359d..1d682c04ff7e9c 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -100,15 +100,20 @@ def deactivate return redirect_back_or_admin_user(notice: _("Error occurred. A blocked user cannot be deactivated")) end - return redirect_back_or_admin_user(notice: _("Successfully deactivated")) if user.deactivated? return redirect_back_or_admin_user(notice: _("Internal users cannot be deactivated")) if user.internal? + return redirect_back_or_admin_user(notice: _("Successfully deactivated")) if user.deactivated? unless user.can_be_deactivated? return redirect_back_or_admin_user(notice: format(_("The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"), minimum_inactive_days: Gitlab::CurrentSettings.deactivate_dormant_users_period)) end - user.deactivate - redirect_back_or_admin_user(notice: _("Successfully deactivated")) + result = Users::DeactivateService.new(current_user).execute(user) + + if result[:status] == :success + redirect_back_or_admin_user(notice: _("Successfully deactivated")) + else + redirect_back_or_admin_user(alert: _("Error occurred. User was not deactivated")) + end end def block diff --git a/app/services/users/deactivate_service.rb b/app/services/users/deactivate_service.rb new file mode 100644 index 00000000000000..c4f79b9748d45f --- /dev/null +++ b/app/services/users/deactivate_service.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module Users + class DeactivateService < BaseService + def initialize(current_user) + @current_user = current_user + end + + def execute(user) + if user.blocked? || user.internal? || user.deactivated? || !user.can_be_deactivated? + error("User cannot be deactivated") + elsif user.deactivate + success + else + messages = user.errors.full_messages + error(messages.uniq.join('. ')) + end + end + end +end + +Users::DeactivateService.prepend_mod_with('Users::DeactivateService') diff --git a/ee/app/services/ee/users/deactivate_service.rb b/ee/app/services/ee/users/deactivate_service.rb new file mode 100644 index 00000000000000..5a6740bd007e28 --- /dev/null +++ b/ee/app/services/ee/users/deactivate_service.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module EE + module Users + module DeactivateService + extend ::Gitlab::Utils::Override + + override :execute + def execute(user) + super.tap do |result| + log_audit_event(user) if result[:status] == :success + end + end + + private + + def log_audit_event(user) + audit_context = { + name: 'deactivate_user', + author: current_user, + scope: user, + target: user, + message: "Deactivated user", + target_details: user.username + } + + ::Gitlab::Audit::Auditor.audit(audit_context) + end + end + end +end diff --git a/ee/config/audit_events/types/deactivate_user.yml b/ee/config/audit_events/types/deactivate_user.yml new file mode 100644 index 00000000000000..75db53fffb09fb --- /dev/null +++ b/ee/config/audit_events/types/deactivate_user.yml @@ -0,0 +1,8 @@ +name: deactivate_user +description: Event triggered on user deactivate action +introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/issues/13473 +introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/117776 +feature_category: "user_management" +milestone: "15.11" +saved_to_database: true +streamed: true \ No newline at end of file diff --git a/ee/spec/services/ee/users/deactivate_service_spec.rb b/ee/spec/services/ee/users/deactivate_service_spec.rb new file mode 100644 index 00000000000000..46ffe4f0649ec0 --- /dev/null +++ b/ee/spec/services/ee/users/deactivate_service_spec.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Users::DeactivateService, feature_category: :user_management do + let_it_be(:current_user) { create(:admin) } + + subject(:service) { described_class.new(current_user) } + + describe '#execute' do + let!(:user) { create(:user) } + + subject(:operation) { service.execute(user) } + + context 'for audit events', :enable_admin_mode do + include_examples 'audit event logging' do + let(:operation) { service.execute(user) } + + let(:fail_condition!) do + allow(user).to receive(:deactivate).and_return(false) + end + + let(:attributes) do + { + author_id: current_user.id, + entity_id: user.id, + entity_type: 'User', + details: { + author_class: 'User', + author_name: current_user.name, + custom_message: 'Deactivated user', + target_details: user.username, + target_id: user.id, + target_type: 'User' + } + } + end + end + end + end +end diff --git a/spec/services/users/deactivate_service_spec.rb b/spec/services/users/deactivate_service_spec.rb new file mode 100644 index 00000000000000..32d1f458a53acf --- /dev/null +++ b/spec/services/users/deactivate_service_spec.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Users::DeactivateService, feature_category: :user_management do + let_it_be(:current_user) { create(:admin) } + + subject(:service) { described_class.new(current_user) } + + describe '#execute' do + subject(:operation) { service.execute(user) } + + context 'when successful' do + let(:user) { create(:user) } + + it { is_expected.to eq(status: :success) } + + it "change the user's state" do + expect { operation }.to change { user.state }.to('deactivated') + end + end + + context 'when failed' do + let(:user) { create(:user, :deactivated) } + + it 'returns error result' do + aggregate_failures 'error result' do + expect(operation[:status]).to eq(:error) + expect(operation[:message]).to eq('User cannot be deactivated') + end + end + + it "does not change the user's state" do + expect { operation }.not_to change { user.state } + end + end + + context 'when internal user' do + let(:user) { create(:user, :bot) } + + it 'returns error result' do + expect(operation[:status]).to eq(:error) + expect(operation[:message]).to eq('User cannot be deactivated') + end + end + end +end -- GitLab From 1ac80adce7c86f518e98494837628a88ef248fbc Mon Sep 17 00:00:00 2001 From: sameer shaik Date: Mon, 17 Apr 2023 05:21:05 +0000 Subject: [PATCH 02/13] Add user deactivate response --- locale/gitlab.pot | 3 +++ 1 file changed, 3 insertions(+) diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 4887fed6ca1771..40f07823cb4f7b 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -17011,6 +17011,9 @@ msgstr "" msgid "Error occurred. User was not confirmed" msgstr "" +msgid "Error occurred. User was not deactivated" +msgstr "" + msgid "Error occurred. User was not unbanned" msgstr "" -- GitLab From 84a139c45497904f559bde3127ac8ba5a63ac0af Mon Sep 17 00:00:00 2001 From: sameer shaik Date: Tue, 18 Apr 2023 17:06:23 +0000 Subject: [PATCH 03/13] Update API to use deactivate service --- app/services/users/deactivate_service.rb | 2 +- lib/api/users.rb | 5 +++-- spec/services/users/deactivate_service_spec.rb | 6 +++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/app/services/users/deactivate_service.rb b/app/services/users/deactivate_service.rb index c4f79b9748d45f..19dfa7cfc2f025 100644 --- a/app/services/users/deactivate_service.rb +++ b/app/services/users/deactivate_service.rb @@ -8,7 +8,7 @@ def initialize(current_user) def execute(user) if user.blocked? || user.internal? || user.deactivated? || !user.can_be_deactivated? - error("User cannot be deactivated") + error(_('User cannot be deactivated')) elsif user.deactivate success else diff --git a/lib/api/users.rb b/lib/api/users.rb index 505fcb2b38e8ec..11e868cf2dd41b 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -764,10 +764,11 @@ def reorder_users(users) forbidden!("The user you are trying to deactivate has been active in the past #{Gitlab::CurrentSettings.deactivate_dormant_users_period} days and cannot be deactivated") end - if user.deactivate + result = ::Users::DeactivateService.new(current_user).execute(user) + if result[:status] == :success true else - render_api_error!(user.errors.full_messages, 400) + render_api_error!(result[:message], result[:http_status]) end end # rubocop: enable CodeReuse/ActiveRecord diff --git a/spec/services/users/deactivate_service_spec.rb b/spec/services/users/deactivate_service_spec.rb index 32d1f458a53acf..227bdcf8001f63 100644 --- a/spec/services/users/deactivate_service_spec.rb +++ b/spec/services/users/deactivate_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe Users::DeactivateService, feature_category: :user_management do - let_it_be(:current_user) { create(:admin) } + let_it_be(:current_user) { build(:admin) } subject(:service) { described_class.new(current_user) } @@ -15,12 +15,12 @@ it { is_expected.to eq(status: :success) } - it "change the user's state" do + it "changes the user's state" do expect { operation }.to change { user.state }.to('deactivated') end end - context 'when failed' do + context 'when unsuccessful' do let(:user) { create(:user, :deactivated) } it 'returns error result' do -- GitLab From 02c9ff07e7efd3256399c25d347b1a3c8ec55c81 Mon Sep 17 00:00:00 2001 From: sameer shaik Date: Wed, 19 Apr 2023 04:16:14 +0000 Subject: [PATCH 04/13] Use deactivate service in API --- locale/gitlab.pot | 3 +++ 1 file changed, 3 insertions(+) diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 40f07823cb4f7b..f2a87bbfb87d8e 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -47987,6 +47987,9 @@ msgstr "" msgid "User and IP rate limits" msgstr "" +msgid "User cannot be deactivated" +msgstr "" + msgid "User cap" msgstr "" -- GitLab From 3f05510edc2896d949b71963872d7816b28ba627 Mon Sep 17 00:00:00 2001 From: sameer shaik Date: Thu, 27 Apr 2023 05:52:39 +0000 Subject: [PATCH 05/13] Refactore deactivate service --- app/controllers/admin/users_controller.rb | 15 +------ app/services/users/deactivate_service.rb | 38 ++++++++++++++---- .../services/ee/users/deactivate_service.rb | 2 +- ...eactivate_user.yml => user_deactivate.yml} | 4 +- lib/api/users.rb | 2 +- .../admin/users_controller_spec.rb | 13 ++++-- .../services/users/deactivate_service_spec.rb | 40 +++++++++++++++---- 7 files changed, 79 insertions(+), 35 deletions(-) rename ee/config/audit_events/types/{deactivate_user.yml => user_deactivate.yml} (87%) diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 1d682c04ff7e9c..c2bb69465d0505 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -96,23 +96,12 @@ def activate end def deactivate - if user.blocked? - return redirect_back_or_admin_user(notice: _("Error occurred. A blocked user cannot be deactivated")) - end - - return redirect_back_or_admin_user(notice: _("Internal users cannot be deactivated")) if user.internal? - return redirect_back_or_admin_user(notice: _("Successfully deactivated")) if user.deactivated? - - unless user.can_be_deactivated? - return redirect_back_or_admin_user(notice: format(_("The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"), minimum_inactive_days: Gitlab::CurrentSettings.deactivate_dormant_users_period)) - end - result = Users::DeactivateService.new(current_user).execute(user) - if result[:status] == :success + if result.success? redirect_back_or_admin_user(notice: _("Successfully deactivated")) else - redirect_back_or_admin_user(alert: _("Error occurred. User was not deactivated")) + redirect_back_or_admin_user(alert: result.message) end end diff --git a/app/services/users/deactivate_service.rb b/app/services/users/deactivate_service.rb index 19dfa7cfc2f025..cd17523b61e605 100644 --- a/app/services/users/deactivate_service.rb +++ b/app/services/users/deactivate_service.rb @@ -7,14 +7,38 @@ def initialize(current_user) end def execute(user) - if user.blocked? || user.internal? || user.deactivated? || !user.can_be_deactivated? - error(_('User cannot be deactivated')) - elsif user.deactivate - success - else - messages = user.errors.full_messages - error(messages.uniq.join('. ')) + return ::ServiceResponse.error(message: _('You are not authorized to perform this action')) unless allowed? + return ::ServiceResponse.error(message: 'Error occurred. A blocked user cannot be deactivated') if user.blocked? + return ::ServiceResponse.error(message: 'Internal users cannot be deactivated') if user.internal? + return ::ServiceResponse.error(message: _('User has already been deactivated')) if user.deactivated? + + unless user.can_be_deactivated? + + return ::ServiceResponse.error(message: format(_('The user you are trying to deactivate has been active in the + past %{minimum_inactive_days} days + and cannot be deactivated'), + minimum_inactive_days: Gitlab::CurrentSettings.deactivate_dormant_users_period).squish) + end + + return ::ServiceResponse.error(message: user.errors.full_messages.to_sentence) unless user.deactivate + + log_event(user) + + ::ServiceResponse.success + end + + private + + attr_reader :current_user + + def allowed? + can?(current_user, :admin_all_resources) + end + + def log_event(user) + Gitlab::AppLogger.info(message: 'User deactivated', user: user.username.to_s, email: user.email.to_s, + deactivated_by: @current_user.username.to_s, ip_address: @current_user.current_sign_in_ip.to_s) end end end diff --git a/ee/app/services/ee/users/deactivate_service.rb b/ee/app/services/ee/users/deactivate_service.rb index 5a6740bd007e28..fe0695d942b199 100644 --- a/ee/app/services/ee/users/deactivate_service.rb +++ b/ee/app/services/ee/users/deactivate_service.rb @@ -16,7 +16,7 @@ def execute(user) def log_audit_event(user) audit_context = { - name: 'deactivate_user', + name: 'user_deactivate', author: current_user, scope: user, target: user, diff --git a/ee/config/audit_events/types/deactivate_user.yml b/ee/config/audit_events/types/user_deactivate.yml similarity index 87% rename from ee/config/audit_events/types/deactivate_user.yml rename to ee/config/audit_events/types/user_deactivate.yml index 75db53fffb09fb..475dad66431c8f 100644 --- a/ee/config/audit_events/types/deactivate_user.yml +++ b/ee/config/audit_events/types/user_deactivate.yml @@ -1,8 +1,8 @@ -name: deactivate_user +name: user_deactivate description: Event triggered on user deactivate action introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/issues/13473 introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/117776 feature_category: "user_management" -milestone: "15.11" +milestone: "16.0" saved_to_database: true streamed: true \ No newline at end of file diff --git a/lib/api/users.rb b/lib/api/users.rb index 11e868cf2dd41b..a87ec6bb52ac21 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -768,7 +768,7 @@ def reorder_users(users) if result[:status] == :success true else - render_api_error!(result[:message], result[:http_status]) + render_api_error!(result[:message], 400) end end # rubocop: enable CodeReuse/ActiveRecord diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb index ec2559550c36d4..3492987fbc083f 100644 --- a/spec/controllers/admin/users_controller_spec.rb +++ b/spec/controllers/admin/users_controller_spec.rb @@ -388,7 +388,7 @@ put :deactivate, params: { id: user.username } user.reload expect(user.deactivated?).to be_falsey - expect(flash[:notice]).to eq("The user you are trying to deactivate has been active in the past #{Gitlab::CurrentSettings.deactivate_dormant_users_period} days and cannot be deactivated") + expect(flash[:alert]).to eq("The user you are trying to deactivate has been active in the past #{Gitlab::CurrentSettings.deactivate_dormant_users_period} days and cannot be deactivated") end end end @@ -398,7 +398,12 @@ user.deactivate end - it_behaves_like 'a request that deactivates the user' + it 'does not deactivate the user' do + put :deactivate, params: { id: user.username } + user.reload + expect(user.deactivated?).to be_truthy + expect(flash[:alert]).to eq('User has already been deactivated') + end end context 'for a blocked user' do @@ -410,7 +415,7 @@ put :deactivate, params: { id: user.username } user.reload expect(user.deactivated?).to be_falsey - expect(flash[:notice]).to eq('Error occurred. A blocked user cannot be deactivated') + expect(flash[:alert]).to eq('Error occurred. A blocked user cannot be deactivated') end end @@ -421,7 +426,7 @@ put :deactivate, params: { id: internal_user.username } expect(internal_user.reload.deactivated?).to be_falsey - expect(flash[:notice]).to eq('Internal users cannot be deactivated') + expect(flash[:alert]).to eq('Internal users cannot be deactivated') end end end diff --git a/spec/services/users/deactivate_service_spec.rb b/spec/services/users/deactivate_service_spec.rb index 227bdcf8001f63..106bbcb60bd28c 100644 --- a/spec/services/users/deactivate_service_spec.rb +++ b/spec/services/users/deactivate_service_spec.rb @@ -4,29 +4,39 @@ RSpec.describe Users::DeactivateService, feature_category: :user_management do let_it_be(:current_user) { build(:admin) } + let_it_be(:user) { build(:user) } subject(:service) { described_class.new(current_user) } describe '#execute' do subject(:operation) { service.execute(user) } - context 'when successful' do + context 'when successful', :enable_admin_mode do let(:user) { create(:user) } - it { is_expected.to eq(status: :success) } + it 'returns success status' do + expect(operation[:status]).to eq(:success) + end it "changes the user's state" do expect { operation }.to change { user.state }.to('deactivated') end + + it 'creates a log entry' do + expect(Gitlab::AppLogger).to receive(:info).with(message: "User deactivated", user: user.username, + email: user.email, deactivated_by: current_user.username, ip_address: current_user.current_sign_in_ip.to_s) + + operation + end end - context 'when unsuccessful' do + context 'when the user is already deactivated', :enable_admin_mode do let(:user) { create(:user, :deactivated) } it 'returns error result' do aggregate_failures 'error result' do expect(operation[:status]).to eq(:error) - expect(operation[:message]).to eq('User cannot be deactivated') + expect(operation[:message]).to eq('User has already been deactivated') end end @@ -35,12 +45,28 @@ end end - context 'when internal user' do + context 'when internal user', :enable_admin_mode do let(:user) { create(:user, :bot) } - it 'returns error result' do + it 'returns an error message' do + expect(operation[:status]).to eq(:error) + expect(operation[:message]).to eq('Internal users cannot be deactivated') + end + end + + context 'when user is blocked', :enable_admin_mode do + let(:user) { create(:user, :blocked) } + + it 'returns an error message' do + expect(operation[:status]).to eq(:error) + expect(operation[:message]).to eq('Error occurred. A blocked user cannot be deactivated') + end + end + + context 'when user is not an admin' do + it 'returns permissions error message' do expect(operation[:status]).to eq(:error) - expect(operation[:message]).to eq('User cannot be deactivated') + expect(operation[:message]).to eq("You are not authorized to perform this action") end end end -- GitLab From 9cef81b801d1b8f5c4ca908f19ba417ea9143af0 Mon Sep 17 00:00:00 2001 From: sameer shaik Date: Thu, 27 Apr 2023 11:19:23 +0000 Subject: [PATCH 06/13] Refactor user deactivate service --- locale/gitlab.pot | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/locale/gitlab.pot b/locale/gitlab.pot index f2a87bbfb87d8e..a42e65f82b099a 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -16996,9 +16996,6 @@ msgstr "" msgid "Error occurred while updating the issue status" msgstr "" -msgid "Error occurred. A blocked user cannot be deactivated" -msgstr "" - msgid "Error occurred. A blocked user must be unblocked to be activated" msgstr "" @@ -17011,9 +17008,6 @@ msgstr "" msgid "Error occurred. User was not confirmed" msgstr "" -msgid "Error occurred. User was not deactivated" -msgstr "" - msgid "Error occurred. User was not unbanned" msgstr "" @@ -23803,9 +23797,6 @@ msgstr "" msgid "Internal users" msgstr "" -msgid "Internal users cannot be deactivated" -msgstr "" - msgid "Interval" msgstr "" @@ -44729,7 +44720,10 @@ msgstr "" msgid "The user you are trying to approve is not pending approval" msgstr "" -msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated" +msgid "" +"The user you are trying to deactivate has been active in the\n" +" past %{minimum_inactive_days} days\n" +" and cannot be deactivated" msgstr "" msgid "The username for the Jenkins server." @@ -47987,9 +47981,6 @@ msgstr "" msgid "User and IP rate limits" msgstr "" -msgid "User cannot be deactivated" -msgstr "" - msgid "User cap" msgstr "" @@ -48005,6 +47996,9 @@ msgstr "" msgid "User does not have permission to create a Security Policy project." msgstr "" +msgid "User has already been deactivated" +msgstr "" + msgid "User identity was successfully created." msgstr "" -- GitLab From 4e9bb0805eacfca9d92b257c1c957d813d664e86 Mon Sep 17 00:00:00 2001 From: sameer shaik Date: Thu, 27 Apr 2023 12:15:25 +0000 Subject: [PATCH 07/13] Update deactivate service --- locale/gitlab.pot | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/locale/gitlab.pot b/locale/gitlab.pot index a42e65f82b099a..6568c20595ff92 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -44720,10 +44720,7 @@ msgstr "" msgid "The user you are trying to approve is not pending approval" msgstr "" -msgid "" -"The user you are trying to deactivate has been active in the\n" -" past %{minimum_inactive_days} days\n" -" and cannot be deactivated" +msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated" msgstr "" msgid "The username for the Jenkins server." -- GitLab From b9584b13be7d0d2509a1ffc3895b08bf1d2c27b5 Mon Sep 17 00:00:00 2001 From: sameer shaik Date: Thu, 27 Apr 2023 15:08:34 +0000 Subject: [PATCH 08/13] Update deactive response --- locale/gitlab.pot | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 6568c20595ff92..a39686f8de7588 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -44720,7 +44720,10 @@ msgstr "" msgid "The user you are trying to approve is not pending approval" msgstr "" -msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated" +msgid "" +"The user you are trying to deactivate has been active in the\n" +" past %{minimum_inactive_days} days\n" +" and cannot be deactivated" msgstr "" msgid "The username for the Jenkins server." @@ -53926,4 +53929,4 @@ msgid "{project}" msgstr "" msgid "✔" -msgstr "" +msgstr "" \ No newline at end of file -- GitLab From ff5f87d27576d4413176409fb3b4543447aef694 Mon Sep 17 00:00:00 2001 From: sameer shaik Date: Thu, 27 Apr 2023 18:08:35 +0000 Subject: [PATCH 09/13] Skip deactivate response length --- app/services/users/deactivate_service.rb | 8 +++----- locale/gitlab.pot | 7 ++----- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/app/services/users/deactivate_service.rb b/app/services/users/deactivate_service.rb index cd17523b61e605..ad4ad737d0ee28 100644 --- a/app/services/users/deactivate_service.rb +++ b/app/services/users/deactivate_service.rb @@ -13,12 +13,10 @@ def execute(user) return ::ServiceResponse.error(message: _('User has already been deactivated')) if user.deactivated? unless user.can_be_deactivated? - - return ::ServiceResponse.error(message: format(_('The user you are trying to deactivate has been active in the - past %{minimum_inactive_days} days - and cannot be deactivated'), + # rubocop:disable Layout/LineLength + return ::ServiceResponse.error(message: format(_('The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated'), minimum_inactive_days: Gitlab::CurrentSettings.deactivate_dormant_users_period).squish) - + # rubocop:enable Layout/LineLength end return ::ServiceResponse.error(message: user.errors.full_messages.to_sentence) unless user.deactivate diff --git a/locale/gitlab.pot b/locale/gitlab.pot index a39686f8de7588..6568c20595ff92 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -44720,10 +44720,7 @@ msgstr "" msgid "The user you are trying to approve is not pending approval" msgstr "" -msgid "" -"The user you are trying to deactivate has been active in the\n" -" past %{minimum_inactive_days} days\n" -" and cannot be deactivated" +msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated" msgstr "" msgid "The username for the Jenkins server." @@ -53929,4 +53926,4 @@ msgid "{project}" msgstr "" msgid "✔" -msgstr "" \ No newline at end of file +msgstr "" -- GitLab From bfd1b9222db8a3b0690c329559f4113944a06258 Mon Sep 17 00:00:00 2001 From: sameer shaik Date: Fri, 28 Apr 2023 12:16:42 +0000 Subject: [PATCH 10/13] Update deactivate event coverage --- app/services/users/deactivate_service.rb | 30 +++++++++++++++---- lib/api/users.rb | 2 +- locale/gitlab.pot | 6 ++++ spec/requests/api/users_spec.rb | 17 +++++++++++ .../services/users/deactivate_service_spec.rb | 4 +++ 5 files changed, 52 insertions(+), 7 deletions(-) diff --git a/app/services/users/deactivate_service.rb b/app/services/users/deactivate_service.rb index ad4ad737d0ee28..2261fedcfce4f5 100644 --- a/app/services/users/deactivate_service.rb +++ b/app/services/users/deactivate_service.rb @@ -7,19 +7,37 @@ def initialize(current_user) end def execute(user) - return ::ServiceResponse.error(message: _('You are not authorized to perform this action')) unless allowed? - return ::ServiceResponse.error(message: 'Error occurred. A blocked user cannot be deactivated') if user.blocked? - return ::ServiceResponse.error(message: 'Internal users cannot be deactivated') if user.internal? - return ::ServiceResponse.error(message: _('User has already been deactivated')) if user.deactivated? + unless allowed? + return ::ServiceResponse.error(message: _('You are not authorized to perform this action'), + reason: :forbidden) + end + + if user.blocked? + return ::ServiceResponse.error(message: _('Error occurred. A blocked user cannot be deactivated'), + reason: :forbidden) + end + + if user.internal? + return ::ServiceResponse.error(message: _('Internal users cannot be deactivated'), + reason: :forbidden) + end + + if user.deactivated? + return ::ServiceResponse.error(message: _('User has already been deactivated'), + reason: :forbidden) + end unless user.can_be_deactivated? # rubocop:disable Layout/LineLength return ::ServiceResponse.error(message: format(_('The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated'), - minimum_inactive_days: Gitlab::CurrentSettings.deactivate_dormant_users_period).squish) + minimum_inactive_days: Gitlab::CurrentSettings.deactivate_dormant_users_period).squish, reason: :forbidden) # rubocop:enable Layout/LineLength end - return ::ServiceResponse.error(message: user.errors.full_messages.to_sentence) unless user.deactivate + unless user.deactivate + return ::ServiceResponse.error(message: user.errors.full_messages.to_sentence, + reason: :bad_request) + end log_event(user) diff --git a/lib/api/users.rb b/lib/api/users.rb index a87ec6bb52ac21..d0ab0e8294b111 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -768,7 +768,7 @@ def reorder_users(users) if result[:status] == :success true else - render_api_error!(result[:message], 400) + render_api_error!(result[:message], result[:http_status] || :bad_request) end end # rubocop: enable CodeReuse/ActiveRecord diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 6568c20595ff92..ba3d349baf0aba 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -16996,6 +16996,9 @@ msgstr "" msgid "Error occurred while updating the issue status" msgstr "" +msgid "Error occurred. A blocked user cannot be deactivated" +msgstr "" + msgid "Error occurred. A blocked user must be unblocked to be activated" msgstr "" @@ -23797,6 +23800,9 @@ msgstr "" msgid "Internal users" msgstr "" +msgid "Internal users cannot be deactivated" +msgstr "" + msgid "Interval" msgstr "" diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 3ecd012be129fc..b31ef33722d7bc 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -3621,6 +3621,23 @@ def update_password(user, admin, password = User.random_password) it_behaves_like '404' end + + context 'fails to deactivate an user' do + let(:activity) { { last_activity_on: Gitlab::CurrentSettings.deactivate_dormant_users_period.next.days.ago } } + + before do + allow_next_instance_of(::Users::DeactivateService) do |service| + allow(service).to receive(:execute).and_return(::ServiceResponse.error(message: 'Failed to deactivate the user', reason: :bad_request)) + end + end + + it 'returns the error from DeactivateService' do + deactivate + + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['message']).to eq('Failed to deactivate the user') + end + end end end end diff --git a/spec/services/users/deactivate_service_spec.rb b/spec/services/users/deactivate_service_spec.rb index 106bbcb60bd28c..66c3e5be7f095b 100644 --- a/spec/services/users/deactivate_service_spec.rb +++ b/spec/services/users/deactivate_service_spec.rb @@ -37,6 +37,7 @@ aggregate_failures 'error result' do expect(operation[:status]).to eq(:error) expect(operation[:message]).to eq('User has already been deactivated') + expect(operation.reason).to eq :forbidden end end @@ -51,6 +52,7 @@ it 'returns an error message' do expect(operation[:status]).to eq(:error) expect(operation[:message]).to eq('Internal users cannot be deactivated') + expect(operation.reason).to eq :forbidden end end @@ -60,6 +62,7 @@ it 'returns an error message' do expect(operation[:status]).to eq(:error) expect(operation[:message]).to eq('Error occurred. A blocked user cannot be deactivated') + expect(operation.reason).to eq :forbidden end end @@ -67,6 +70,7 @@ it 'returns permissions error message' do expect(operation[:status]).to eq(:error) expect(operation[:message]).to eq("You are not authorized to perform this action") + expect(operation.reason).to eq :forbidden end end end -- GitLab From 92ae31f083e1dcfcdfa59703bbbbc52b4139ef08 Mon Sep 17 00:00:00 2001 From: sameer shaik Date: Wed, 10 May 2023 17:09:37 +0000 Subject: [PATCH 11/13] Refine deactivate service --- app/controllers/admin/users_controller.rb | 3 ++- app/services/users/deactivate_service.rb | 22 ++++++++++--------- .../audit_events/types/user_deactivate.yml | 2 +- lib/api/users.rb | 4 ++-- locale/gitlab.pot | 5 ++++- .../admin/users_controller_spec.rb | 7 +----- spec/requests/api/users_spec.rb | 17 -------------- .../services/users/deactivate_service_spec.rb | 13 +++++++++-- 8 files changed, 33 insertions(+), 40 deletions(-) diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index c2bb69465d0505..45a7901b2c4039 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -96,7 +96,8 @@ def activate end def deactivate - result = Users::DeactivateService.new(current_user).execute(user) + deactivate_service = Users::DeactivateService.new(current_user, skip_authorization: true) + result = deactivate_service.execute(user) if result.success? redirect_back_or_admin_user(notice: _("Successfully deactivated")) diff --git a/app/services/users/deactivate_service.rb b/app/services/users/deactivate_service.rb index 2261fedcfce4f5..c969dc8a5bbf48 100644 --- a/app/services/users/deactivate_service.rb +++ b/app/services/users/deactivate_service.rb @@ -2,8 +2,9 @@ module Users class DeactivateService < BaseService - def initialize(current_user) + def initialize(current_user, skip_authorization: false) @current_user = current_user + @skip_authorization = skip_authorization end def execute(user) @@ -22,16 +23,15 @@ def execute(user) reason: :forbidden) end - if user.deactivated? - return ::ServiceResponse.error(message: _('User has already been deactivated'), - reason: :forbidden) - end + return ::ServiceResponse.success(message: _('User has already been deactivated')) if user.deactivated? unless user.can_be_deactivated? - # rubocop:disable Layout/LineLength - return ::ServiceResponse.error(message: format(_('The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated'), - minimum_inactive_days: Gitlab::CurrentSettings.deactivate_dormant_users_period).squish, reason: :forbidden) - # rubocop:enable Layout/LineLength + deactivation_error_message = format( + _('The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days') + + _(' and cannot be deactivated'), + minimum_inactive_days: Gitlab::CurrentSettings.deactivate_dormant_users_period + ).squish + return ::ServiceResponse.error(message: deactivation_error_message, reason: :forbidden) end unless user.deactivate @@ -49,12 +49,14 @@ def execute(user) attr_reader :current_user def allowed? + return true if @skip_authorization + can?(current_user, :admin_all_resources) end def log_event(user) Gitlab::AppLogger.info(message: 'User deactivated', user: user.username.to_s, email: user.email.to_s, - deactivated_by: @current_user.username.to_s, ip_address: @current_user.current_sign_in_ip.to_s) + deactivated_by: current_user.username.to_s, ip_address: current_user.current_sign_in_ip.to_s) end end end diff --git a/ee/config/audit_events/types/user_deactivate.yml b/ee/config/audit_events/types/user_deactivate.yml index 475dad66431c8f..beeaf31c4c388d 100644 --- a/ee/config/audit_events/types/user_deactivate.yml +++ b/ee/config/audit_events/types/user_deactivate.yml @@ -5,4 +5,4 @@ introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/117776 feature_category: "user_management" milestone: "16.0" saved_to_database: true -streamed: true \ No newline at end of file +streamed: true diff --git a/lib/api/users.rb b/lib/api/users.rb index d0ab0e8294b111..1d923660b8feb7 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -764,11 +764,11 @@ def reorder_users(users) forbidden!("The user you are trying to deactivate has been active in the past #{Gitlab::CurrentSettings.deactivate_dormant_users_period} days and cannot be deactivated") end - result = ::Users::DeactivateService.new(current_user).execute(user) + result = ::Users::DeactivateService.new(current_user, skip_authorization: true).execute(user) if result[:status] == :success true else - render_api_error!(result[:message], result[:http_status] || :bad_request) + render_api_error!(result[:message], :bad_request) end end # rubocop: enable CodeReuse/ActiveRecord diff --git a/locale/gitlab.pot b/locale/gitlab.pot index ba3d349baf0aba..26c8aed07cf01e 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -45,6 +45,9 @@ msgstr "" msgid " and %{sliced}" msgstr "" +msgid " and cannot be deactivated" +msgstr "" + msgid " or " msgstr "" @@ -44726,7 +44729,7 @@ msgstr "" msgid "The user you are trying to approve is not pending approval" msgstr "" -msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated" +msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days" msgstr "" msgid "The username for the Jenkins server." diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb index 3492987fbc083f..9b00451de304b3 100644 --- a/spec/controllers/admin/users_controller_spec.rb +++ b/spec/controllers/admin/users_controller_spec.rb @@ -398,12 +398,7 @@ user.deactivate end - it 'does not deactivate the user' do - put :deactivate, params: { id: user.username } - user.reload - expect(user.deactivated?).to be_truthy - expect(flash[:alert]).to eq('User has already been deactivated') - end + it_behaves_like 'a request that deactivates the user' end context 'for a blocked user' do diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index b31ef33722d7bc..3ecd012be129fc 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -3621,23 +3621,6 @@ def update_password(user, admin, password = User.random_password) it_behaves_like '404' end - - context 'fails to deactivate an user' do - let(:activity) { { last_activity_on: Gitlab::CurrentSettings.deactivate_dormant_users_period.next.days.ago } } - - before do - allow_next_instance_of(::Users::DeactivateService) do |service| - allow(service).to receive(:execute).and_return(::ServiceResponse.error(message: 'Failed to deactivate the user', reason: :bad_request)) - end - end - - it 'returns the error from DeactivateService' do - deactivate - - expect(response).to have_gitlab_http_status(:bad_request) - expect(json_response['message']).to eq('Failed to deactivate the user') - end - end end end end diff --git a/spec/services/users/deactivate_service_spec.rb b/spec/services/users/deactivate_service_spec.rb index 66c3e5be7f095b..0bb6e51a3b13f5 100644 --- a/spec/services/users/deactivate_service_spec.rb +++ b/spec/services/users/deactivate_service_spec.rb @@ -35,9 +35,8 @@ it 'returns error result' do aggregate_failures 'error result' do - expect(operation[:status]).to eq(:error) + expect(operation[:status]).to eq(:success) expect(operation[:message]).to eq('User has already been deactivated') - expect(operation.reason).to eq :forbidden end end @@ -73,5 +72,15 @@ expect(operation.reason).to eq :forbidden end end + + context 'when skip_authorization is true' do + let(:non_admin_user) { create(:user) } + let(:user_to_deactivate) { create(:user) } + let(:skip_authorization_service) { described_class.new(non_admin_user, skip_authorization: true) } + + it 'deactivates the user even if the current user is not an admin' do + expect(skip_authorization_service.execute(user_to_deactivate)[:status]).to eq(:success) + end + end end end -- GitLab From 349d1b18c4d68f77918e2428de5e8a167c768ba9 Mon Sep 17 00:00:00 2001 From: sameer shaik Date: Thu, 11 May 2023 11:12:31 +0000 Subject: [PATCH 12/13] Refine deactivation error message --- app/services/users/deactivate_service.rb | 11 ++++++----- locale/gitlab.pot | 5 +---- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/app/services/users/deactivate_service.rb b/app/services/users/deactivate_service.rb index c969dc8a5bbf48..e69ce13d3cc824 100644 --- a/app/services/users/deactivate_service.rb +++ b/app/services/users/deactivate_service.rb @@ -26,11 +26,12 @@ def execute(user) return ::ServiceResponse.success(message: _('User has already been deactivated')) if user.deactivated? unless user.can_be_deactivated? - deactivation_error_message = format( - _('The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days') + - _(' and cannot be deactivated'), - minimum_inactive_days: Gitlab::CurrentSettings.deactivate_dormant_users_period - ).squish + message = _( + 'The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days ' \ + 'and cannot be deactivated') + + deactivation_error_message = format(message, + minimum_inactive_days: Gitlab::CurrentSettings.deactivate_dormant_users_period) return ::ServiceResponse.error(message: deactivation_error_message, reason: :forbidden) end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 26c8aed07cf01e..ba3d349baf0aba 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -45,9 +45,6 @@ msgstr "" msgid " and %{sliced}" msgstr "" -msgid " and cannot be deactivated" -msgstr "" - msgid " or " msgstr "" @@ -44729,7 +44726,7 @@ msgstr "" msgid "The user you are trying to approve is not pending approval" msgstr "" -msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days" +msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated" msgstr "" msgid "The username for the Jenkins server." -- GitLab From 611772993ab7a16fdc26cc381fb7f49d4f62b8d3 Mon Sep 17 00:00:00 2001 From: sameer shaik Date: Mon, 15 May 2023 11:05:35 +0000 Subject: [PATCH 13/13] Refactor deactivate method --- lib/api/users.rb | 8 +------- spec/requests/api/users_spec.rb | 8 ++++---- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/lib/api/users.rb b/lib/api/users.rb index 1d923660b8feb7..3d9af536c3c03c 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -758,17 +758,11 @@ def reorder_users(users) break if user.deactivated? - unless user.can_be_deactivated? - forbidden!('A blocked user cannot be deactivated by the API') if user.blocked? - forbidden!('An internal user cannot be deactivated by the API') if user.internal? - forbidden!("The user you are trying to deactivate has been active in the past #{Gitlab::CurrentSettings.deactivate_dormant_users_period} days and cannot be deactivated") - end - result = ::Users::DeactivateService.new(current_user, skip_authorization: true).execute(user) if result[:status] == :success true else - render_api_error!(result[:message], :bad_request) + render_api_error!(result[:message], result[:reason] || :bad_request) end end # rubocop: enable CodeReuse/ActiveRecord diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 3ecd012be129fc..cc8be312c71a43 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -3558,7 +3558,7 @@ def update_password(user, admin, password = User.random_password) deactivate expect(response).to have_gitlab_http_status(:forbidden) - expect(json_response['message']).to eq("403 Forbidden - The user you are trying to deactivate has been active in the past #{Gitlab::CurrentSettings.deactivate_dormant_users_period} days and cannot be deactivated") + expect(json_response['message']).to eq("The user you are trying to deactivate has been active in the past #{Gitlab::CurrentSettings.deactivate_dormant_users_period} days and cannot be deactivated") expect(user.reload.state).to eq('active') end end @@ -3582,7 +3582,7 @@ def update_password(user, admin, password = User.random_password) deactivate expect(response).to have_gitlab_http_status(:forbidden) - expect(json_response['message']).to eq('403 Forbidden - A blocked user cannot be deactivated by the API') + expect(json_response['message']).to eq('Error occurred. A blocked user cannot be deactivated') expect(blocked_user.reload.state).to eq('blocked') end end @@ -3596,7 +3596,7 @@ def update_password(user, admin, password = User.random_password) deactivate expect(response).to have_gitlab_http_status(:forbidden) - expect(json_response['message']).to eq('403 Forbidden - A blocked user cannot be deactivated by the API') + expect(json_response['message']).to eq('Error occurred. A blocked user cannot be deactivated') expect(user.reload.state).to eq('ldap_blocked') end end @@ -3608,7 +3608,7 @@ def update_password(user, admin, password = User.random_password) deactivate expect(response).to have_gitlab_http_status(:forbidden) - expect(json_response['message']).to eq('403 Forbidden - An internal user cannot be deactivated by the API') + expect(json_response['message']).to eq('Internal users cannot be deactivated') end end -- GitLab