From 973aceee0de57bbd10aea6e392b885d98235c0b7 Mon Sep 17 00:00:00 2001 From: David Fernandez Date: Fri, 15 Sep 2023 17:52:11 +0000 Subject: [PATCH 1/6] Enforce that the policy is executed by the bot user This MR enforces that only the bot user can trigger a security policy pipeline Changelog: security EE: true --- ...orchestration_policy_rule_schedule_namespace_worker_spec.rb | 1 + .../security/orchestration_policy_rule_schedule_worker_spec.rb | 3 +++ 2 files changed, 4 insertions(+) diff --git a/ee/spec/workers/security/orchestration_policy_rule_schedule_namespace_worker_spec.rb b/ee/spec/workers/security/orchestration_policy_rule_schedule_namespace_worker_spec.rb index 60cb4305054e9f..ba76d2be953ce7 100644 --- a/ee/spec/workers/security/orchestration_policy_rule_schedule_namespace_worker_spec.rb +++ b/ee/spec/workers/security/orchestration_policy_rule_schedule_namespace_worker_spec.rb @@ -37,6 +37,7 @@ end it 'creates async new policy bot user only when it is missing for the project' do + it 'does not invokes the rule schedule worker for all projects in the group' do expect(Security::OrchestrationConfigurationCreateBotWorker).to receive(:perform_async).with(project_1.id, nil) expect(Security::OrchestrationConfigurationCreateBotWorker).to receive(:perform_async).with(project_2.id, nil) expect { worker.perform(schedule_id) }.not_to change { User.count } diff --git a/ee/spec/workers/security/orchestration_policy_rule_schedule_worker_spec.rb b/ee/spec/workers/security/orchestration_policy_rule_schedule_worker_spec.rb index cbbfa7484d8d87..231ba12052e84a 100644 --- a/ee/spec/workers/security/orchestration_policy_rule_schedule_worker_spec.rb +++ b/ee/spec/workers/security/orchestration_policy_rule_schedule_worker_spec.rb @@ -25,6 +25,9 @@ expect { worker.perform }.not_to change { User.count } end + context 'when a project does have a security_policy_bot' do + let_it_be(:security_policy_bot) { create(:user, user_type: :security_policy_bot) } + it 'does not invoke the rule schedule worker when there is no security policy bot' do expect(Security::ScanExecutionPolicies::RuleScheduleWorker).not_to receive(:perform_async) -- GitLab From 1656a24fbded93d4317eb36af97b85f1ef6de870 Mon Sep 17 00:00:00 2001 From: Ethan Urie Date: Wed, 6 Sep 2023 15:25:21 -0400 Subject: [PATCH 2/6] Cherry picking major commits into their own branch Changelog: added --- .../admin/users/components/actions/index.js | 4 ++ .../admin/users/components/actions/trust.vue | 61 +++++++++++++++++++ .../users/components/actions/untrust.vue | 55 +++++++++++++++++ .../javascripts/admin/users/constants.js | 2 + app/controllers/admin/users_controller.rb | 20 ++++++ app/helpers/admin/user_actions_helper.rb | 11 ++++ app/helpers/users_helper.rb | 4 +- app/models/user.rb | 8 +++ app/models/user_custom_attribute.rb | 3 +- app/services/spam/spam_verdict_service.rb | 2 +- .../users/allow_possible_spam_service.rb | 1 + .../users/disallow_possible_spam_service.rb | 1 + config/routes/admin.rb | 2 + 13 files changed, 171 insertions(+), 3 deletions(-) create mode 100644 app/assets/javascripts/admin/users/components/actions/trust.vue create mode 100644 app/assets/javascripts/admin/users/components/actions/untrust.vue diff --git a/app/assets/javascripts/admin/users/components/actions/index.js b/app/assets/javascripts/admin/users/components/actions/index.js index 4e63a85df891f2..373d47d29d9c94 100644 --- a/app/assets/javascripts/admin/users/components/actions/index.js +++ b/app/assets/javascripts/admin/users/components/actions/index.js @@ -9,6 +9,8 @@ import Reject from './reject.vue'; import Unban from './unban.vue'; import Unblock from './unblock.vue'; import Unlock from './unlock.vue'; +import Trust from './trust.vue'; +import Untrust from './untrust.vue'; export default { Activate, @@ -22,4 +24,6 @@ export default { Unblock, Unlock, Reject, + Trust, + Untrust, }; diff --git a/app/assets/javascripts/admin/users/components/actions/trust.vue b/app/assets/javascripts/admin/users/components/actions/trust.vue new file mode 100644 index 00000000000000..2722442ae0d258 --- /dev/null +++ b/app/assets/javascripts/admin/users/components/actions/trust.vue @@ -0,0 +1,61 @@ + + + diff --git a/app/assets/javascripts/admin/users/components/actions/untrust.vue b/app/assets/javascripts/admin/users/components/actions/untrust.vue new file mode 100644 index 00000000000000..919e9dfc4f2066 --- /dev/null +++ b/app/assets/javascripts/admin/users/components/actions/untrust.vue @@ -0,0 +1,55 @@ + + + diff --git a/app/assets/javascripts/admin/users/constants.js b/app/assets/javascripts/admin/users/constants.js index 9cd61d6b1dbcc6..43c9a8749cde76 100644 --- a/app/assets/javascripts/admin/users/constants.js +++ b/app/assets/javascripts/admin/users/constants.js @@ -19,4 +19,6 @@ export const I18N_USER_ACTIONS = { deleteWithContributions: s__('AdminUsers|Delete user and contributions'), ban: s__('AdminUsers|Ban user'), unban: s__('AdminUsers|Unban user'), + trust: s__('AdminUsers|Trust user'), + untrust: s__('AdminUsers|Untrust user'), }; diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 1f05e4e7b21c26..9bb0d902119f26 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -164,6 +164,26 @@ def unlock end end + def trust + result = Users::TrustService.new(current_user).execute(user) + + if result[:status] == :success + redirect_back_or_admin_user(notice: _("Successfully trusted")) + else + redirect_back_or_admin_user(alert: _("Error occurred. User was not updated")) + end + end + + def untrust + result = Users::UntrustService.new(current_user).execute(user) + + if result[:status] == :success + redirect_back_or_admin_user(notice: _("Successfully untrusted")) + else + redirect_back_or_admin_user(alert: _("Error occurred. User was not updated")) + end + end + def confirm if update_user(&:force_confirm) redirect_back_or_admin_user(notice: _("Successfully confirmed")) diff --git a/app/helpers/admin/user_actions_helper.rb b/app/helpers/admin/user_actions_helper.rb index 969c5d5a0b5b14..63c4d3e0db5bca 100644 --- a/app/helpers/admin/user_actions_helper.rb +++ b/app/helpers/admin/user_actions_helper.rb @@ -16,6 +16,7 @@ def admin_actions(user) unlock_actions delete_actions ban_actions + trust_actions @actions end @@ -66,5 +67,15 @@ def ban_actions @actions << 'ban' end end + + def trust_actions + return if @user.internal? + + @actions << if @user.trusted? + 'untrust' + else + 'trust' + end + end end end diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb index a892b6e6ac6f7c..81c41aee142a70 100644 --- a/app/helpers/users_helper.rb +++ b/app/helpers/users_helper.rb @@ -262,7 +262,9 @@ def admin_users_paths delete_with_contributions: admin_user_path(:id, hard_delete: true), admin_user: admin_user_path(:id), ban: ban_admin_user_path(:id), - unban: unban_admin_user_path(:id) + unban: unban_admin_user_path(:id), + trust: trust_admin_user_path(:id), + untrust: untrust_admin_user_path(:id) } end diff --git a/app/models/user.rb b/app/models/user.rb index e17803af1353b0..006082d98d76c5 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -599,6 +599,12 @@ def blocked? scope :by_provider_and_extern_uid, ->(provider, extern_uid) { joins(:identities).merge(Identity.with_extern_uid(provider, extern_uid)) } scope :by_ids_or_usernames, -> (ids, usernames) { where(username: usernames).or(where(id: ids)) } scope :without_forbidden_states, -> { where.not(state: FORBIDDEN_SEARCH_STATES) } + scope :trusted, -> do + where('EXISTS (?)', ::UserCustomAttribute + .select(1) + .where('user_id = users.id') + .trusted_with_spam) + end strip_attributes! :name @@ -767,6 +773,8 @@ def filter_items(filter_name) external when 'deactivated' deactivated + when "trusted" + trusted else active_without_ghosts end diff --git a/app/models/user_custom_attribute.rb b/app/models/user_custom_attribute.rb index 15d50071bf6d8a..cf53ffff292316 100644 --- a/app/models/user_custom_attribute.rb +++ b/app/models/user_custom_attribute.rb @@ -10,13 +10,14 @@ class UserCustomAttribute < ApplicationRecord scope :by_user_id, ->(user_id) { where(user_id: user_id) } scope :by_updated_at, ->(updated_at) { where(updated_at: updated_at) } scope :arkose_sessions, -> { by_key('arkose_session') } + scope :trusted_with_spam, -> { by_key(TRUSTED_BY) } BLOCKED_BY = 'blocked_by' UNBLOCKED_BY = 'unblocked_by' ARKOSE_RISK_BAND = 'arkose_risk_band' AUTO_BANNED_BY_ABUSE_REPORT_ID = 'auto_banned_by_abuse_report_id' AUTO_BANNED_BY_SPAM_LOG_ID = 'auto_banned_by_spam_log_id' - ALLOW_POSSIBLE_SPAM = 'allow_possible_spam' + TRUSTED_BY = 'trusted_by' IDENTITY_VERIFICATION_PHONE_EXEMPT = 'identity_verification_phone_exempt' class << self diff --git a/app/services/spam/spam_verdict_service.rb b/app/services/spam/spam_verdict_service.rb index 9efe51b43b815e..ce5c5201b41921 100644 --- a/app/services/spam/spam_verdict_service.rb +++ b/app/services/spam/spam_verdict_service.rb @@ -90,7 +90,7 @@ def override_via_allow_possible_spam?(verdict:) end def allow_possible_spam? - target.allow_possible_spam?(user) || user.allow_possible_spam? + target.allow_possible_spam? || user.trusted? end def spamcheck_client diff --git a/app/services/users/allow_possible_spam_service.rb b/app/services/users/allow_possible_spam_service.rb index d9273fe0fc1cf4..2caa577056fc93 100644 --- a/app/services/users/allow_possible_spam_service.rb +++ b/app/services/users/allow_possible_spam_service.rb @@ -13,6 +13,7 @@ def execute(user) value: "#{current_user.username}/#{current_user.id}+#{Time.current}" } UserCustomAttribute.upsert_custom_attributes([custom_attribute]) + success end end end diff --git a/app/services/users/disallow_possible_spam_service.rb b/app/services/users/disallow_possible_spam_service.rb index e31ba7ddff02a4..5f4c109aae2b27 100644 --- a/app/services/users/disallow_possible_spam_service.rb +++ b/app/services/users/disallow_possible_spam_service.rb @@ -8,6 +8,7 @@ def initialize(current_user) def execute(user) user.custom_attributes.by_key(UserCustomAttribute::ALLOW_POSSIBLE_SPAM).delete_all + success end end end diff --git a/config/routes/admin.rb b/config/routes/admin.rb index 5513ac1813a0a5..e8ad19624e9823 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -22,6 +22,8 @@ put :unlock put :confirm put :approve + put :trust + put :untrust delete :reject post :impersonate patch :disable_two_factor -- GitLab From 2f49505605ed0b2ab045b2d5657701f037e0d8ad Mon Sep 17 00:00:00 2001 From: Ethan Urie Date: Thu, 3 Aug 2023 15:52:08 -0400 Subject: [PATCH 3/6] Renamed vue component to comply with eslint rules (cherry picked from commit 66b5d4e55c8b12598733bbcb40088a79f90f621f) Pulling in the trust/untrust services Add trust specs for UserActionsHelper (cherry picked from commit a57b001b7e99275bbe33dea1c69643b93b48bfec) Add request specs (cherry picked from commit 87ff28cf5ee4501c347ef5c3656ba04c0e4139e5) Fix trust/untrust spec in model (cherry picked from commit edca4ae268af599e85c2e5249a52930e9a83ade3) Fix trust/untrust spec in model (cherry picked from commit edca4ae268af599e85c2e5249a52930e9a83ade3) (cherry picked from commit 5a680330755696bc2244d0b41897ab984867d1f2) Add more changes from original branch Updated locale file and json files --- .../admin/abuse_report/constants.js | 2 + .../admin/users/components/actions/index.js | 4 +- .../actions/{trust.vue => trust_user.vue} | 0 .../actions/{untrust.vue => untrust_user.vue} | 0 app/controllers/admin/users_controller.rb | 2 +- app/helpers/admin/user_actions_helper.rb | 6 +- .../abuse_report_events_helper.rb | 4 ++ .../resource_events/abuse_report_event.rb | 7 ++- app/models/user.rb | 5 +- app/models/user_custom_attribute.rb | 10 +++ .../abuse_reports/moderate_user_service.rb | 5 ++ app/services/spam/spam_verdict_service.rb | 2 +- .../users/allow_possible_spam_service.rb | 19 ------ ...sible_spam_service.rb => trust_service.rb} | 4 +- app/services/users/untrust_service.rb | 14 +++++ ...icy_rule_schedule_namespace_worker_spec.rb | 1 - ...ration_policy_rule_schedule_worker_spec.rb | 3 - locale/gitlab.pot | 45 ++++++++++++++ spec/factories/users.rb | 9 +++ .../admin_users_data_attributes_paths.json | 62 ++++++++++++++----- .../master/gl-common-scanning-report.json | 8 ++- spec/frontend/admin/users/constants.js | 4 ++ .../helpers/admin/user_actions_helper_spec.rb | 59 +++++++++++++++--- spec/models/user_spec.rb | 20 +++--- spec/requests/admin/users_controller_spec.rb | 20 ++++++ .../moderate_user_service_spec.rb | 37 +++++++++++ .../spam/spam_verdict_service_spec.rb | 2 +- ..._service_spec.rb => trust_service_spec.rb} | 4 +- ...ervice_spec.rb => untrust_service_spec.rb} | 6 +- 29 files changed, 290 insertions(+), 74 deletions(-) rename app/assets/javascripts/admin/users/components/actions/{trust.vue => trust_user.vue} (100%) rename app/assets/javascripts/admin/users/components/actions/{untrust.vue => untrust_user.vue} (100%) delete mode 100644 app/services/users/allow_possible_spam_service.rb rename app/services/users/{disallow_possible_spam_service.rb => trust_service.rb} (55%) create mode 100644 app/services/users/untrust_service.rb rename spec/services/users/{allow_possible_spam_service_spec.rb => trust_service_spec.rb} (80%) rename spec/services/users/{disallow_possible_spam_service_spec.rb => untrust_service_spec.rb} (80%) diff --git a/app/assets/javascripts/admin/abuse_report/constants.js b/app/assets/javascripts/admin/abuse_report/constants.js index 1ecef44ab8f7ba..9937dbc0b76125 100644 --- a/app/assets/javascripts/admin/abuse_report/constants.js +++ b/app/assets/javascripts/admin/abuse_report/constants.js @@ -30,6 +30,7 @@ export const USER_ACTION_OPTIONS = [ NO_ACTION, { value: 'block_user', text: s__('AbuseReport|Block user') }, { value: 'ban_user', text: s__('AbuseReport|Ban user') }, + { value: 'trust_user', text: s__('AbuseReport|Trust user') }, { value: 'delete_user', text: s__('AbuseReport|Delete user') }, ]; @@ -48,6 +49,7 @@ export const REASON_OPTIONS = [ text: s__('AbuseReport|Confirmed violation of a copyright or a trademark'), }, { value: 'malware', text: s__('AbuseReport|Confirmed posting of malware') }, + { value: 'trusted', text: s__(`AbuseReport|Confirmed trusted user`) }, { value: 'other', text: s__('AbuseReport|Something else') }, { value: 'unconfirmed', text: s__('AbuseReport|Abuse unconfirmed') }, ]; diff --git a/app/assets/javascripts/admin/users/components/actions/index.js b/app/assets/javascripts/admin/users/components/actions/index.js index 373d47d29d9c94..633bc4d8b15c32 100644 --- a/app/assets/javascripts/admin/users/components/actions/index.js +++ b/app/assets/javascripts/admin/users/components/actions/index.js @@ -9,8 +9,8 @@ import Reject from './reject.vue'; import Unban from './unban.vue'; import Unblock from './unblock.vue'; import Unlock from './unlock.vue'; -import Trust from './trust.vue'; -import Untrust from './untrust.vue'; +import Trust from './trust_user.vue'; +import Untrust from './untrust_user.vue'; export default { Activate, diff --git a/app/assets/javascripts/admin/users/components/actions/trust.vue b/app/assets/javascripts/admin/users/components/actions/trust_user.vue similarity index 100% rename from app/assets/javascripts/admin/users/components/actions/trust.vue rename to app/assets/javascripts/admin/users/components/actions/trust_user.vue diff --git a/app/assets/javascripts/admin/users/components/actions/untrust.vue b/app/assets/javascripts/admin/users/components/actions/untrust_user.vue similarity index 100% rename from app/assets/javascripts/admin/users/components/actions/untrust.vue rename to app/assets/javascripts/admin/users/components/actions/untrust_user.vue diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 9bb0d902119f26..ae6311c4d09f0d 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -310,7 +310,7 @@ def paginate_without_count? end def users_with_included_associations(users) - users.includes(:authorized_projects) # rubocop: disable CodeReuse/ActiveRecord + users.includes(:authorized_projects, :trusted_with_spam_attribute) # rubocop: disable CodeReuse/ActiveRecord end def admin_making_changes_for_another_user? diff --git a/app/helpers/admin/user_actions_helper.rb b/app/helpers/admin/user_actions_helper.rb index 63c4d3e0db5bca..ba40b3c8a8df5a 100644 --- a/app/helpers/admin/user_actions_helper.rb +++ b/app/helpers/admin/user_actions_helper.rb @@ -69,7 +69,11 @@ def ban_actions end def trust_actions - return if @user.internal? + return if @user.internal? || + @user.blocked_pending_approval? || + @user.banned? || + @user.blocked? || + @user.deactivated? @actions << if @user.trusted? 'untrust' diff --git a/app/helpers/resource_events/abuse_report_events_helper.rb b/app/helpers/resource_events/abuse_report_events_helper.rb index 8adbc891184002..207ec73454b2ad 100644 --- a/app/helpers/resource_events/abuse_report_events_helper.rb +++ b/app/helpers/resource_events/abuse_report_events_helper.rb @@ -10,6 +10,8 @@ def success_message_for_action(action) s_('AbuseReportEvent|Successfully blocked the user') when 'delete_user' s_('AbuseReportEvent|Successfully scheduled the user for deletion') + when 'trust_user' + s_('AbuseReportEvent|Successfully trusted the user') when 'close_report' s_('AbuseReportEvent|Successfully closed the report') when 'ban_user_and_close_report' @@ -18,6 +20,8 @@ def success_message_for_action(action) s_('AbuseReportEvent|Successfully blocked the user and closed the report') when 'delete_user_and_close_report' s_('AbuseReportEvent|Successfully scheduled the user for deletion and closed the report') + when 'trust_user_and_close_report' + s_('AbuseReportEvent|Successfully trusted the user and closed the report') end end end diff --git a/app/models/resource_events/abuse_report_event.rb b/app/models/resource_events/abuse_report_event.rb index 59f88a63998eec..5881f87241d8a8 100644 --- a/app/models/resource_events/abuse_report_event.rb +++ b/app/models/resource_events/abuse_report_event.rb @@ -16,7 +16,9 @@ class AbuseReportEvent < ApplicationRecord close_report: 4, ban_user_and_close_report: 5, block_user_and_close_report: 6, - delete_user_and_close_report: 7 + delete_user_and_close_report: 7, + trust_user: 8, + trust_user_and_close_report: 9 } enum reason: { @@ -28,7 +30,8 @@ class AbuseReportEvent < ApplicationRecord copyright: 6, malware: 7, other: 8, - unconfirmed: 9 + unconfirmed: 9, + trusted: 10 } def success_message diff --git a/app/models/user.rb b/app/models/user.rb index 006082d98d76c5..2c2864901d5207 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -271,6 +271,7 @@ def update_tracked_fields!(request) has_many :bulk_imports has_many :custom_attributes, class_name: 'UserCustomAttribute' + has_one :trusted_with_spam_attribute, -> { UserCustomAttribute.trusted_with_spam }, class_name: 'UserCustomAttribute' has_many :callouts, class_name: 'Users::Callout' has_many :group_callouts, class_name: 'Users::GroupCallout' has_many :project_callouts, class_name: 'Users::ProjectCallout' @@ -2231,8 +2232,8 @@ def abuse_metadata } end - def allow_possible_spam? - custom_attributes.by_key(UserCustomAttribute::ALLOW_POSSIBLE_SPAM).exists? + def trusted? + trusted_with_spam_attribute.present? end def namespace_commit_email_for_namespace(namespace) diff --git a/app/models/user_custom_attribute.rb b/app/models/user_custom_attribute.rb index cf53ffff292316..37c1566427b8bd 100644 --- a/app/models/user_custom_attribute.rb +++ b/app/models/user_custom_attribute.rb @@ -51,6 +51,16 @@ def set_banned_by_spam_log(spam_log) return unless spam_log custom_attribute = { user_id: spam_log.user_id, key: AUTO_BANNED_BY_SPAM_LOG_ID, value: spam_log.id } + end + + def set_trusted_by(user:, trusted_by:) + return unless user && trusted_by + + custom_attribute = { + user_id: user.id, + key: UserCustomAttribute::TRUSTED_BY, + value: "#{trusted_by.username}/#{trusted_by.id}+#{Time.current}" + } upsert_custom_attributes([custom_attribute]) end diff --git a/app/services/admin/abuse_reports/moderate_user_service.rb b/app/services/admin/abuse_reports/moderate_user_service.rb index 823568d9db8710..1e14806c694998 100644 --- a/app/services/admin/abuse_reports/moderate_user_service.rb +++ b/app/services/admin/abuse_reports/moderate_user_service.rb @@ -42,6 +42,7 @@ def perform_action when :block_user then block_user when :delete_user then delete_user when :close_report then close_report + when :trust_user then trust_user end end @@ -66,6 +67,10 @@ def close_report success end + def trust_user + Users::TrustService.new(current_user).execute(abuse_report.user) + end + def close_similar_open_reports # admins see the abuse report and other open reports for the same user in one page # hence, if the request is to close the report, close other open reports for the same user too diff --git a/app/services/spam/spam_verdict_service.rb b/app/services/spam/spam_verdict_service.rb index ce5c5201b41921..2d4bebc8b2bb60 100644 --- a/app/services/spam/spam_verdict_service.rb +++ b/app/services/spam/spam_verdict_service.rb @@ -90,7 +90,7 @@ def override_via_allow_possible_spam?(verdict:) end def allow_possible_spam? - target.allow_possible_spam? || user.trusted? + target.allow_possible_spam?(user) || user.trusted? end def spamcheck_client diff --git a/app/services/users/allow_possible_spam_service.rb b/app/services/users/allow_possible_spam_service.rb deleted file mode 100644 index 2caa577056fc93..00000000000000 --- a/app/services/users/allow_possible_spam_service.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -module Users - class AllowPossibleSpamService < BaseService - def initialize(current_user) - @current_user = current_user - end - - def execute(user) - custom_attribute = { - user_id: user.id, - key: UserCustomAttribute::ALLOW_POSSIBLE_SPAM, - value: "#{current_user.username}/#{current_user.id}+#{Time.current}" - } - UserCustomAttribute.upsert_custom_attributes([custom_attribute]) - success - end - end -end diff --git a/app/services/users/disallow_possible_spam_service.rb b/app/services/users/trust_service.rb similarity index 55% rename from app/services/users/disallow_possible_spam_service.rb rename to app/services/users/trust_service.rb index 5f4c109aae2b27..faf0b9c40eabc0 100644 --- a/app/services/users/disallow_possible_spam_service.rb +++ b/app/services/users/trust_service.rb @@ -1,13 +1,13 @@ # frozen_string_literal: true module Users - class DisallowPossibleSpamService < BaseService + class TrustService < BaseService def initialize(current_user) @current_user = current_user end def execute(user) - user.custom_attributes.by_key(UserCustomAttribute::ALLOW_POSSIBLE_SPAM).delete_all + UserCustomAttribute.set_trusted_by(user: user, trusted_by: @current_user) success end end diff --git a/app/services/users/untrust_service.rb b/app/services/users/untrust_service.rb new file mode 100644 index 00000000000000..6db05d6fa7765c --- /dev/null +++ b/app/services/users/untrust_service.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module Users + class UntrustService < BaseService + def initialize(current_user) + @current_user = current_user + end + + def execute(user) + user.custom_attributes.by_key(UserCustomAttribute::TRUSTED_BY).delete_all + success + end + end +end diff --git a/ee/spec/workers/security/orchestration_policy_rule_schedule_namespace_worker_spec.rb b/ee/spec/workers/security/orchestration_policy_rule_schedule_namespace_worker_spec.rb index ba76d2be953ce7..60cb4305054e9f 100644 --- a/ee/spec/workers/security/orchestration_policy_rule_schedule_namespace_worker_spec.rb +++ b/ee/spec/workers/security/orchestration_policy_rule_schedule_namespace_worker_spec.rb @@ -37,7 +37,6 @@ end it 'creates async new policy bot user only when it is missing for the project' do - it 'does not invokes the rule schedule worker for all projects in the group' do expect(Security::OrchestrationConfigurationCreateBotWorker).to receive(:perform_async).with(project_1.id, nil) expect(Security::OrchestrationConfigurationCreateBotWorker).to receive(:perform_async).with(project_2.id, nil) expect { worker.perform(schedule_id) }.not_to change { User.count } diff --git a/ee/spec/workers/security/orchestration_policy_rule_schedule_worker_spec.rb b/ee/spec/workers/security/orchestration_policy_rule_schedule_worker_spec.rb index 231ba12052e84a..cbbfa7484d8d87 100644 --- a/ee/spec/workers/security/orchestration_policy_rule_schedule_worker_spec.rb +++ b/ee/spec/workers/security/orchestration_policy_rule_schedule_worker_spec.rb @@ -25,9 +25,6 @@ expect { worker.perform }.not_to change { User.count } end - context 'when a project does have a security_policy_bot' do - let_it_be(:security_policy_bot) { create(:user, user_type: :security_policy_bot) } - it 'does not invoke the rule schedule worker when there is no security policy bot' do expect(Security::ScanExecutionPolicies::RuleScheduleWorker).not_to receive(:perform_async) diff --git a/locale/gitlab.pot b/locale/gitlab.pot index d4e5fe8d58a6b4..273f111aaa4d29 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -2239,6 +2239,12 @@ msgstr "" msgid "AbuseReportEvent|Successfully scheduled the user for deletion and closed the report" msgstr "" +msgid "AbuseReportEvent|Successfully trusted the user" +msgstr "" + +msgid "AbuseReportEvent|Successfully trusted the user and closed the report" +msgstr "" + msgid "AbuseReports|%{reportedUser} reported for %{category} by %{count} users" msgstr "" @@ -2308,6 +2314,9 @@ msgstr "" msgid "AbuseReport|Confirmed spam" msgstr "" +msgid "AbuseReport|Confirmed trusted user" +msgstr "" + msgid "AbuseReport|Confirmed violation of a copyright or a trademark" msgstr "" @@ -2428,6 +2437,9 @@ msgstr "" msgid "AbuseReport|Tier" msgstr "" +msgid "AbuseReport|Trust user" +msgstr "" + msgid "AbuseReport|Verification" msgstr "" @@ -3847,6 +3859,9 @@ msgstr "" msgid "AdminUsers|Admins" msgstr "" +msgid "AdminUsers|Allow user %{username} to create possible spam?" +msgstr "" + msgid "AdminUsers|An error occurred while fetching this user's contributions, and the request cannot return the number of issues, merge requests, groups, and projects linked to this user. If you proceed with deleting the user, all their contributions will still be deleted." msgstr "" @@ -3949,6 +3964,9 @@ msgstr "" msgid "AdminUsers|Delete user and contributions" msgstr "" +msgid "AdminUsers|Disallow user %{username} to create possible spam?" +msgstr "" + msgid "AdminUsers|Export permissions as CSV (max 100,000 users)" msgstr "" @@ -4063,6 +4081,9 @@ msgstr "" msgid "AdminUsers|The maximum compute minutes that jobs in this namespace can use on shared runners each month. Set 0 for unlimited. Set empty to inherit the global setting of %{minutes}" msgstr "" +msgid "AdminUsers|The user can create issues, notes, snippets, and merge requests without being blocked." +msgstr "" + msgid "AdminUsers|The user can't access git repositories." msgstr "" @@ -4093,6 +4114,9 @@ msgstr "" msgid "AdminUsers|To confirm, type %{username}." msgstr "" +msgid "AdminUsers|Trust user" +msgstr "" + msgid "AdminUsers|Unban user" msgstr "" @@ -4108,6 +4132,9 @@ msgstr "" msgid "AdminUsers|Unlock user %{username}?" msgstr "" +msgid "AdminUsers|Untrust user" +msgstr "" + msgid "AdminUsers|User administration" msgstr "" @@ -4138,6 +4165,9 @@ msgstr "" msgid "AdminUsers|What does this mean?" msgstr "" +msgid "AdminUsers|When allowed to create possible spam:" +msgstr "" + msgid "AdminUsers|When banned:" msgstr "" @@ -4156,6 +4186,9 @@ msgstr "" msgid "AdminUsers|You are about to permanently delete the user %{username}. This will delete all issues, merge requests, groups, and projects linked to them. To avoid data loss, consider using the %{strongStart}Block user%{strongEnd} feature instead. After you %{strongStart}Delete user%{strongEnd}, you cannot undo this action or recover the data." msgstr "" +msgid "AdminUsers|You can allow possible spam in the future if necessary." +msgstr "" + msgid "AdminUsers|You can always block their account again if needed." msgstr "" @@ -4171,6 +4204,9 @@ msgstr "" msgid "AdminUsers|You can ban their account in the future if necessary." msgstr "" +msgid "AdminUsers|You can disallow creating possible spam in the future." +msgstr "" + msgid "AdminUsers|You can unban their account in the future. Their data remains intact." msgstr "" @@ -18929,6 +18965,9 @@ msgstr "" msgid "Error occurred. User was not unlocked" msgstr "" +msgid "Error occurred. User was not updated" +msgstr "" + msgid "Error parsing CSV file. Please make sure it has" msgstr "" @@ -46104,6 +46143,9 @@ msgstr "" msgid "Successfully synced %{synced_timeago}." msgstr "" +msgid "Successfully trusted" +msgstr "" + msgid "Successfully unbanned" msgstr "" @@ -46116,6 +46158,9 @@ msgstr "" msgid "Successfully unlocked" msgstr "" +msgid "Successfully untrusted" +msgstr "" + msgid "Successfully updated %{last_updated_timeago}." msgstr "" diff --git a/spec/factories/users.rb b/spec/factories/users.rb index d61d5cc2d78f9b..de2b5159fe7107 100644 --- a/spec/factories/users.rb +++ b/spec/factories/users.rb @@ -48,6 +48,15 @@ after(:build) { |user, _| user.ban! } end + trait :trusted do + after(:create) do |user, _| + user.custom_attributes.create!( + key: UserCustomAttribute::TRUSTED_BY, + value: "placeholder" + ) + end + end + trait :ldap_blocked do after(:build) { |user, _| user.ldap_block! } end diff --git a/spec/fixtures/api/schemas/entities/admin_users_data_attributes_paths.json b/spec/fixtures/api/schemas/entities/admin_users_data_attributes_paths.json index 44d8e48a972c48..61472b273e1342 100644 --- a/spec/fixtures/api/schemas/entities/admin_users_data_attributes_paths.json +++ b/spec/fixtures/api/schemas/entities/admin_users_data_attributes_paths.json @@ -1,19 +1,51 @@ { "type": "object", "properties": { - "edit": { "type": "string" }, - "approve": { "type": "string" }, - "reject": { "type": "string" }, - "unblock": { "type": "string" }, - "block": { "type": "string" }, - "deactivate": { "type": "string" }, - "activate": { "type": "string" }, - "unlock": { "type": "string" }, - "delete": { "type": "string" }, - "delete_with_contributions": { "type": "string" }, - "admin_user": { "type": "string" }, - "ban": { "type": "string" }, - "unban": { "type": "string" } + "edit": { + "type": "string" + }, + "approve": { + "type": "string" + }, + "reject": { + "type": "string" + }, + "unblock": { + "type": "string" + }, + "block": { + "type": "string" + }, + "deactivate": { + "type": "string" + }, + "activate": { + "type": "string" + }, + "unlock": { + "type": "string" + }, + "delete": { + "type": "string" + }, + "delete_with_contributions": { + "type": "string" + }, + "admin_user": { + "type": "string" + }, + "ban": { + "type": "string" + }, + "unban": { + "type": "string" + }, + "trust": { + "type": "string" + }, + "untrust": { + "type": "string" + } }, "required": [ "edit", @@ -28,7 +60,9 @@ "delete_with_contributions", "admin_user", "ban", - "unban" + "unban", + "trust", + "untrust" ], "additionalProperties": false } diff --git a/spec/fixtures/security_reports/master/gl-common-scanning-report.json b/spec/fixtures/security_reports/master/gl-common-scanning-report.json index 31a86d3a8aea9e..0f12744c42429e 100644 --- a/spec/fixtures/security_reports/master/gl-common-scanning-report.json +++ b/spec/fixtures/security_reports/master/gl-common-scanning-report.json @@ -414,7 +414,9 @@ "value": "foo" } ], - "links": [] + "links": [ + + ] } ], "remediations": [ @@ -476,7 +478,9 @@ "diff": "dG90YWxseSBsZWdpdGltYXRlIGRpZmYsIDEwLzEwIHdvdWxkIGFwcGx5" } ], - "dependency_files": [], + "dependency_files": [ + + ], "scan": { "analyzer": { "id": "common-analyzer", diff --git a/spec/frontend/admin/users/constants.js b/spec/frontend/admin/users/constants.js index d341eb03b1b33c..39e8e51f43ca0b 100644 --- a/spec/frontend/admin/users/constants.js +++ b/spec/frontend/admin/users/constants.js @@ -9,6 +9,8 @@ const REJECT = 'reject'; const APPROVE = 'approve'; const BAN = 'ban'; const UNBAN = 'unban'; +const TRUST = 'trust'; +const UNTRUST = 'untrust'; export const EDIT = 'edit'; @@ -24,6 +26,8 @@ export const CONFIRMATION_ACTIONS = [ UNBAN, APPROVE, REJECT, + TRUST, + UNTRUST, ]; export const DELETE_ACTIONS = [DELETE, DELETE_WITH_CONTRIBUTIONS]; diff --git a/spec/helpers/admin/user_actions_helper_spec.rb b/spec/helpers/admin/user_actions_helper_spec.rb index 87d2308690c617..abfdabf3413df9 100644 --- a/spec/helpers/admin/user_actions_helper_spec.rb +++ b/spec/helpers/admin/user_actions_helper_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" -RSpec.describe Admin::UserActionsHelper do +RSpec.describe Admin::UserActionsHelper, feature_category: :user_management do describe '#admin_actions' do let_it_be(:current_user) { build(:user) } @@ -29,13 +29,33 @@ context 'the user is a standard user' do let_it_be(:user) { create(:user) } - it { is_expected.to contain_exactly("edit", "block", "ban", "deactivate", "delete", "delete_with_contributions") } + it do + is_expected.to contain_exactly( + "edit", + "block", + "ban", + "deactivate", + "delete", + "delete_with_contributions", + "trust" + ) + end end context 'the user is an admin user' do let_it_be(:user) { create(:user, :admin) } - it { is_expected.to contain_exactly("edit", "block", "ban", "deactivate", "delete", "delete_with_contributions") } + it do + is_expected.to contain_exactly( + "edit", + "block", + "ban", + "deactivate", + "delete", + "delete_with_contributions", + "trust" + ) + end end context 'the user is blocked by LDAP' do @@ -59,7 +79,16 @@ context 'the user is deactivated' do let_it_be(:user) { create(:user, :deactivated) } - it { is_expected.to contain_exactly("edit", "block", "ban", "activate", "delete", "delete_with_contributions") } + it do + is_expected.to contain_exactly( + "edit", + "block", + "ban", + "activate", + "delete", + "delete_with_contributions" + ) + end end context 'the user is locked' do @@ -77,7 +106,8 @@ "deactivate", "unlock", "delete", - "delete_with_contributions" + "delete_with_contributions", + "trust" ) } end @@ -88,6 +118,21 @@ it { is_expected.to contain_exactly("edit", "unban", "delete", "delete_with_contributions") } end + context 'the user is trusted' do + let_it_be(:user) { create(:user, :trusted) } + + it do + is_expected.to contain_exactly("edit", + "block", + "deactivate", + "ban", + "delete", + "delete_with_contributions", + "untrust" + ) + end + end + context 'the current_user does not have permission to delete the user' do let_it_be(:user) { build(:user) } @@ -95,7 +140,7 @@ allow(helper).to receive(:can?).with(current_user, :destroy_user, user).and_return(false) end - it { is_expected.to contain_exactly("edit", "block", "ban", "deactivate") } + it { is_expected.to contain_exactly("edit", "block", "ban", "deactivate", "trust") } end context 'the user is a sole owner of a group' do @@ -106,7 +151,7 @@ group.add_owner(user) end - it { is_expected.to contain_exactly("edit", "block", "ban", "deactivate", "delete_with_contributions") } + it { is_expected.to contain_exactly("edit", "block", "ban", "deactivate", "delete_with_contributions", "trust") } end context 'the user is a bot' do diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index c611c3c26e312f..d35449ca8c8331 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -6120,25 +6120,23 @@ def add_user(access) end end - describe '#allow_possible_spam?' do + describe '#trusted?' do context 'when no custom attribute is set' do - it 'is false' do - expect(user.allow_possible_spam?).to be_falsey + it 'is falsey' do + expect(user.trusted?).to be_falsey end end context 'when the custom attribute is set' do before do - user.custom_attributes.upsert_custom_attributes( - [{ - user_id: user.id, - key: UserCustomAttribute::ALLOW_POSSIBLE_SPAM, - value: "test" - }]) + user.custom_attributes.create!( + key: UserCustomAttribute::TRUSTED_BY, + value: "test" + ) end - it '#allow_possible_spam? is true' do - expect(user.allow_possible_spam?).to be_truthy + it 'is truthy' do + expect(user.trusted?).to be_truthy end end end diff --git a/spec/requests/admin/users_controller_spec.rb b/spec/requests/admin/users_controller_spec.rb index e525d615b50311..ef3a1c1375a535 100644 --- a/spec/requests/admin/users_controller_spec.rb +++ b/spec/requests/admin/users_controller_spec.rb @@ -74,4 +74,24 @@ expect { request }.to change { user.reload.access_locked? }.from(true).to(false) end end + + describe 'PUT #trust' do + subject(:request) { put trust_admin_user_path(user) } + + it 'trusts the user' do + expect { request }.to change { user.reload.trusted? }.from(false).to(true) + end + end + + describe 'PUT #untrust' do + before do + user.custom_attributes.create!(key: UserCustomAttribute::TRUSTED_BY, value: "placeholder") + end + + subject(:request) { put untrust_admin_user_path(user) } + + it 'trusts the user' do + expect { request }.to change { user.reload.trusted? }.from(true).to(false) + end + end end diff --git a/spec/services/admin/abuse_reports/moderate_user_service_spec.rb b/spec/services/admin/abuse_reports/moderate_user_service_spec.rb index 7e08db2b6123ab..3b80d3276a2093 100644 --- a/spec/services/admin/abuse_reports/moderate_user_service_spec.rb +++ b/spec/services/admin/abuse_reports/moderate_user_service_spec.rb @@ -210,6 +210,43 @@ end end + describe 'when trusting the user' do + let(:action) { 'trust_user' } + + it 'calls the Users::TrustService method' do + expect_next_instance_of(Users::TrustService, admin) do |service| + expect(service).to receive(:execute).with(abuse_report.user).and_return(status: :success) + end + + subject + end + + context 'when not closing the report' do + let(:close) { false } + + it_behaves_like 'does not close the report' + it_behaves_like 'records an event', action: 'trust_user' + end + + context 'when closing the report' do + it_behaves_like 'closes the report' + it_behaves_like 'records an event', action: 'trust_user_and_close_report' + end + + context 'when trusting the user fails' do + before do + allow_next_instance_of(Users::TrustService) do |service| + allow(service).to receive(:execute).with(abuse_report.user) + .and_return(status: :error, message: 'Trusting the user failed') + end + end + + it_behaves_like 'returns an error response', 'Trusting the user failed' + it_behaves_like 'does not close the report' + it_behaves_like 'does not record an event' + end + end + describe 'when only closing the report' do let(:action) { '' } diff --git a/spec/services/spam/spam_verdict_service_spec.rb b/spec/services/spam/spam_verdict_service_spec.rb index 70f43d82ead04b..909f0d5e784dd5 100644 --- a/spec/services/spam/spam_verdict_service_spec.rb +++ b/spec/services/spam/spam_verdict_service_spec.rb @@ -141,7 +141,7 @@ UserCustomAttribute.upsert_custom_attributes( [{ user_id: user.id, - key: 'allow_possible_spam', + key: 'trusted_by', value: 'does not matter' }] ) diff --git a/spec/services/users/allow_possible_spam_service_spec.rb b/spec/services/users/trust_service_spec.rb similarity index 80% rename from spec/services/users/allow_possible_spam_service_spec.rb rename to spec/services/users/trust_service_spec.rb index 53618f0c8e9614..1f71992ce9b38e 100644 --- a/spec/services/users/allow_possible_spam_service_spec.rb +++ b/spec/services/users/trust_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Users::AllowPossibleSpamService, feature_category: :user_management do +RSpec.describe Users::TrustService, feature_category: :user_management do let_it_be(:current_user) { create(:admin) } subject(:service) { described_class.new(current_user) } @@ -18,7 +18,7 @@ operation user.reload - expect(user.custom_attributes.by_key(UserCustomAttribute::ALLOW_POSSIBLE_SPAM)).to be_present + expect(user.custom_attributes.by_key(UserCustomAttribute::TRUSTED_BY)).to be_present end end end diff --git a/spec/services/users/disallow_possible_spam_service_spec.rb b/spec/services/users/untrust_service_spec.rb similarity index 80% rename from spec/services/users/disallow_possible_spam_service_spec.rb rename to spec/services/users/untrust_service_spec.rb index 32a47e05525a7b..bfa81bb233a298 100644 --- a/spec/services/users/disallow_possible_spam_service_spec.rb +++ b/spec/services/users/untrust_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Users::DisallowPossibleSpamService, feature_category: :user_management do +RSpec.describe Users::UntrustService, feature_category: :user_management do let_it_be(:current_user) { create(:admin) } subject(:service) { described_class.new(current_user) } @@ -16,14 +16,14 @@ UserCustomAttribute.upsert_custom_attributes( [{ user_id: user.id, - key: :allow_possible_spam, + key: UserCustomAttribute::TRUSTED_BY, value: 'not important' }] ) end it 'updates the custom attributes', :aggregate_failures do - expect(user.custom_attributes.by_key(UserCustomAttribute::ALLOW_POSSIBLE_SPAM)).to be_present + expect(user.custom_attributes.by_key(UserCustomAttribute::TRUSTED_BY)).to be_present operation user.reload -- GitLab From d9c4b4184c14fec6a175b35522722726e0c5bfec Mon Sep 17 00:00:00 2001 From: Ethan Urie Date: Fri, 8 Sep 2023 14:08:45 -0400 Subject: [PATCH 4/6] Refactor to simplify the setting of the custom attribute Use `let_it_be_with_reload` for the `user` to fix issues with setting the custom attribute. Added documentation --- app/models/user_custom_attribute.rb | 1 + app/services/users/untrust_service.rb | 1 + doc/administration/review_abuse_reports.md | 5 ++++- .../master/gl-common-scanning-report.json | 8 ++------ spec/services/spam/spam_verdict_service_spec.rb | 12 +++--------- 5 files changed, 11 insertions(+), 16 deletions(-) diff --git a/app/models/user_custom_attribute.rb b/app/models/user_custom_attribute.rb index 37c1566427b8bd..b2674cb4e88f3c 100644 --- a/app/models/user_custom_attribute.rb +++ b/app/models/user_custom_attribute.rb @@ -51,6 +51,7 @@ def set_banned_by_spam_log(spam_log) return unless spam_log custom_attribute = { user_id: spam_log.user_id, key: AUTO_BANNED_BY_SPAM_LOG_ID, value: spam_log.id } + upsert_custom_attributes([custom_attribute]) end def set_trusted_by(user:, trusted_by:) diff --git a/app/services/users/untrust_service.rb b/app/services/users/untrust_service.rb index 6db05d6fa7765c..eb86e74557c9a2 100644 --- a/app/services/users/untrust_service.rb +++ b/app/services/users/untrust_service.rb @@ -7,6 +7,7 @@ def initialize(current_user) end def execute(user) + # Each user has at most 1 custom attribute with key 'trusted_by' user.custom_attributes.by_key(UserCustomAttribute::TRUSTED_BY).delete_all success end diff --git a/doc/administration/review_abuse_reports.md b/doc/administration/review_abuse_reports.md index 4ff53a4e1b0a2d..ee753f94a4dd00 100644 --- a/doc/administration/review_abuse_reports.md +++ b/doc/administration/review_abuse_reports.md @@ -38,7 +38,7 @@ To access abuse reports: 1. Select **Admin Area**. 1. Select **Abuse Reports**. -There are 3 ways to resolve an abuse report, with a button for each method: +There are 4 ways to resolve an abuse report, with a button for each method: - Remove user & report. This: - [Deletes the reported user](../user/profile/account/delete_account.md) from the @@ -48,6 +48,9 @@ There are 3 ways to resolve an abuse report, with a button for each method: - Remove report. This: - Removes the abuse report from the list. - Removes access restrictions for the reported user. +- Trust user. This: + - Allows the user to create issues, snippets, notes, etc. without being blocked for possible spam. + - Will keep abuse reports from being created for this user. The following is an example of the **Abuse Reports** page: diff --git a/spec/fixtures/security_reports/master/gl-common-scanning-report.json b/spec/fixtures/security_reports/master/gl-common-scanning-report.json index 0f12744c42429e..31a86d3a8aea9e 100644 --- a/spec/fixtures/security_reports/master/gl-common-scanning-report.json +++ b/spec/fixtures/security_reports/master/gl-common-scanning-report.json @@ -414,9 +414,7 @@ "value": "foo" } ], - "links": [ - - ] + "links": [] } ], "remediations": [ @@ -478,9 +476,7 @@ "diff": "dG90YWxseSBsZWdpdGltYXRlIGRpZmYsIDEwLzEwIHdvdWxkIGFwcGx5" } ], - "dependency_files": [ - - ], + "dependency_files": [], "scan": { "analyzer": { "id": "common-analyzer", diff --git a/spec/services/spam/spam_verdict_service_spec.rb b/spec/services/spam/spam_verdict_service_spec.rb index 909f0d5e784dd5..361742699b0cbe 100644 --- a/spec/services/spam/spam_verdict_service_spec.rb +++ b/spec/services/spam/spam_verdict_service_spec.rb @@ -31,7 +31,7 @@ end let(:check_for_spam) { true } - let_it_be(:user) { create(:user) } + let_it_be_with_reload(:user) { create(:user) } let_it_be(:issue) { create(:issue, author: user) } let_it_be(:snippet) { create(:personal_snippet, :public, author: user) } @@ -136,15 +136,9 @@ end end - context 'if allow_possible_spam user custom attribute is set' do + context 'if user is trusted to create possible spam' do before do - UserCustomAttribute.upsert_custom_attributes( - [{ - user_id: user.id, - key: 'trusted_by', - value: 'does not matter' - }] - ) + user.custom_attributes.create!(key: 'trusted_by', value: 'does not matter') end context 'and a service returns a verdict that should be overridden' do -- GitLab From 4e426b7a927cfd4e429b11ee1d6bc02f2e4d26ba Mon Sep 17 00:00:00 2001 From: Phillip Wells Date: Fri, 15 Sep 2023 13:47:38 +0000 Subject: [PATCH 5/6] Code review responses Updated docs with review suggestions. Utilize the `trusted_with_spam_attribute` associate Remove unnecessary files The action components for the users admin panel weren't meant to be part of this MR. Moving them out to the appropriate MR. Remove unneeded changes for controller and helpers --- .../admin/users/components/actions/index.js | 4 -- .../users/components/actions/trust_user.vue | 61 ------------------ .../users/components/actions/untrust_user.vue | 55 ---------------- .../javascripts/admin/users/constants.js | 2 - app/controllers/admin/users_controller.rb | 22 +------ app/helpers/admin/user_actions_helper.rb | 15 ----- app/helpers/users_helper.rb | 4 +- app/models/user.rb | 6 -- app/services/users/untrust_service.rb | 3 +- config/routes/admin.rb | 2 - doc/administration/review_abuse_reports.md | 8 ++- locale/gitlab.pot | 33 ---------- .../admin_users_data_attributes_paths.json | 62 +++++-------------- spec/frontend/admin/users/constants.js | 4 -- .../helpers/admin/user_actions_helper_spec.rb | 59 +++--------------- spec/requests/admin/users_controller_spec.rb | 20 ------ spec/services/users/untrust_service_spec.rb | 4 +- 17 files changed, 31 insertions(+), 333 deletions(-) delete mode 100644 app/assets/javascripts/admin/users/components/actions/trust_user.vue delete mode 100644 app/assets/javascripts/admin/users/components/actions/untrust_user.vue diff --git a/app/assets/javascripts/admin/users/components/actions/index.js b/app/assets/javascripts/admin/users/components/actions/index.js index 633bc4d8b15c32..4e63a85df891f2 100644 --- a/app/assets/javascripts/admin/users/components/actions/index.js +++ b/app/assets/javascripts/admin/users/components/actions/index.js @@ -9,8 +9,6 @@ import Reject from './reject.vue'; import Unban from './unban.vue'; import Unblock from './unblock.vue'; import Unlock from './unlock.vue'; -import Trust from './trust_user.vue'; -import Untrust from './untrust_user.vue'; export default { Activate, @@ -24,6 +22,4 @@ export default { Unblock, Unlock, Reject, - Trust, - Untrust, }; diff --git a/app/assets/javascripts/admin/users/components/actions/trust_user.vue b/app/assets/javascripts/admin/users/components/actions/trust_user.vue deleted file mode 100644 index 2722442ae0d258..00000000000000 --- a/app/assets/javascripts/admin/users/components/actions/trust_user.vue +++ /dev/null @@ -1,61 +0,0 @@ - - - diff --git a/app/assets/javascripts/admin/users/components/actions/untrust_user.vue b/app/assets/javascripts/admin/users/components/actions/untrust_user.vue deleted file mode 100644 index 919e9dfc4f2066..00000000000000 --- a/app/assets/javascripts/admin/users/components/actions/untrust_user.vue +++ /dev/null @@ -1,55 +0,0 @@ - - - diff --git a/app/assets/javascripts/admin/users/constants.js b/app/assets/javascripts/admin/users/constants.js index 43c9a8749cde76..9cd61d6b1dbcc6 100644 --- a/app/assets/javascripts/admin/users/constants.js +++ b/app/assets/javascripts/admin/users/constants.js @@ -19,6 +19,4 @@ export const I18N_USER_ACTIONS = { deleteWithContributions: s__('AdminUsers|Delete user and contributions'), ban: s__('AdminUsers|Ban user'), unban: s__('AdminUsers|Unban user'), - trust: s__('AdminUsers|Trust user'), - untrust: s__('AdminUsers|Untrust user'), }; diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index ae6311c4d09f0d..1f05e4e7b21c26 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -164,26 +164,6 @@ def unlock end end - def trust - result = Users::TrustService.new(current_user).execute(user) - - if result[:status] == :success - redirect_back_or_admin_user(notice: _("Successfully trusted")) - else - redirect_back_or_admin_user(alert: _("Error occurred. User was not updated")) - end - end - - def untrust - result = Users::UntrustService.new(current_user).execute(user) - - if result[:status] == :success - redirect_back_or_admin_user(notice: _("Successfully untrusted")) - else - redirect_back_or_admin_user(alert: _("Error occurred. User was not updated")) - end - end - def confirm if update_user(&:force_confirm) redirect_back_or_admin_user(notice: _("Successfully confirmed")) @@ -310,7 +290,7 @@ def paginate_without_count? end def users_with_included_associations(users) - users.includes(:authorized_projects, :trusted_with_spam_attribute) # rubocop: disable CodeReuse/ActiveRecord + users.includes(:authorized_projects) # rubocop: disable CodeReuse/ActiveRecord end def admin_making_changes_for_another_user? diff --git a/app/helpers/admin/user_actions_helper.rb b/app/helpers/admin/user_actions_helper.rb index ba40b3c8a8df5a..969c5d5a0b5b14 100644 --- a/app/helpers/admin/user_actions_helper.rb +++ b/app/helpers/admin/user_actions_helper.rb @@ -16,7 +16,6 @@ def admin_actions(user) unlock_actions delete_actions ban_actions - trust_actions @actions end @@ -67,19 +66,5 @@ def ban_actions @actions << 'ban' end end - - def trust_actions - return if @user.internal? || - @user.blocked_pending_approval? || - @user.banned? || - @user.blocked? || - @user.deactivated? - - @actions << if @user.trusted? - 'untrust' - else - 'trust' - end - end end end diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb index 81c41aee142a70..a892b6e6ac6f7c 100644 --- a/app/helpers/users_helper.rb +++ b/app/helpers/users_helper.rb @@ -262,9 +262,7 @@ def admin_users_paths delete_with_contributions: admin_user_path(:id, hard_delete: true), admin_user: admin_user_path(:id), ban: ban_admin_user_path(:id), - unban: unban_admin_user_path(:id), - trust: trust_admin_user_path(:id), - untrust: untrust_admin_user_path(:id) + unban: unban_admin_user_path(:id) } end diff --git a/app/models/user.rb b/app/models/user.rb index 2c2864901d5207..f1d08f12e47f45 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -600,12 +600,6 @@ def blocked? scope :by_provider_and_extern_uid, ->(provider, extern_uid) { joins(:identities).merge(Identity.with_extern_uid(provider, extern_uid)) } scope :by_ids_or_usernames, -> (ids, usernames) { where(username: usernames).or(where(id: ids)) } scope :without_forbidden_states, -> { where.not(state: FORBIDDEN_SEARCH_STATES) } - scope :trusted, -> do - where('EXISTS (?)', ::UserCustomAttribute - .select(1) - .where('user_id = users.id') - .trusted_with_spam) - end strip_attributes! :name diff --git a/app/services/users/untrust_service.rb b/app/services/users/untrust_service.rb index eb86e74557c9a2..aa5de71b97f881 100644 --- a/app/services/users/untrust_service.rb +++ b/app/services/users/untrust_service.rb @@ -7,8 +7,7 @@ def initialize(current_user) end def execute(user) - # Each user has at most 1 custom attribute with key 'trusted_by' - user.custom_attributes.by_key(UserCustomAttribute::TRUSTED_BY).delete_all + user.trusted_with_spam_attribute.delete success end end diff --git a/config/routes/admin.rb b/config/routes/admin.rb index e8ad19624e9823..5513ac1813a0a5 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -22,8 +22,6 @@ put :unlock put :confirm put :approve - put :trust - put :untrust delete :reject post :impersonate patch :disable_two_factor diff --git a/doc/administration/review_abuse_reports.md b/doc/administration/review_abuse_reports.md index ee753f94a4dd00..84bb7ab219f4be 100644 --- a/doc/administration/review_abuse_reports.md +++ b/doc/administration/review_abuse_reports.md @@ -32,13 +32,15 @@ To find out more about reporting abuse, see ## Resolving abuse reports +> **Trust user** [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/131102) in GitLab 16.4. + To access abuse reports: 1. On the left sidebar, select **Search or go to**. 1. Select **Admin Area**. 1. Select **Abuse Reports**. -There are 4 ways to resolve an abuse report, with a button for each method: +There are four ways to resolve an abuse report, with a button for each method: - Remove user & report. This: - [Deletes the reported user](../user/profile/account/delete_account.md) from the @@ -49,8 +51,8 @@ There are 4 ways to resolve an abuse report, with a button for each method: - Removes the abuse report from the list. - Removes access restrictions for the reported user. - Trust user. This: - - Allows the user to create issues, snippets, notes, etc. without being blocked for possible spam. - - Will keep abuse reports from being created for this user. + - Allows the user to create issues, notes, snippets, and merge requests without being blocked for spam. + - Prevents abuse reports from being created for this user. The following is an example of the **Abuse Reports** page: diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 273f111aaa4d29..ff23f14ecb398e 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -3859,9 +3859,6 @@ msgstr "" msgid "AdminUsers|Admins" msgstr "" -msgid "AdminUsers|Allow user %{username} to create possible spam?" -msgstr "" - msgid "AdminUsers|An error occurred while fetching this user's contributions, and the request cannot return the number of issues, merge requests, groups, and projects linked to this user. If you proceed with deleting the user, all their contributions will still be deleted." msgstr "" @@ -3964,9 +3961,6 @@ msgstr "" msgid "AdminUsers|Delete user and contributions" msgstr "" -msgid "AdminUsers|Disallow user %{username} to create possible spam?" -msgstr "" - msgid "AdminUsers|Export permissions as CSV (max 100,000 users)" msgstr "" @@ -4081,9 +4075,6 @@ msgstr "" msgid "AdminUsers|The maximum compute minutes that jobs in this namespace can use on shared runners each month. Set 0 for unlimited. Set empty to inherit the global setting of %{minutes}" msgstr "" -msgid "AdminUsers|The user can create issues, notes, snippets, and merge requests without being blocked." -msgstr "" - msgid "AdminUsers|The user can't access git repositories." msgstr "" @@ -4114,9 +4105,6 @@ msgstr "" msgid "AdminUsers|To confirm, type %{username}." msgstr "" -msgid "AdminUsers|Trust user" -msgstr "" - msgid "AdminUsers|Unban user" msgstr "" @@ -4132,9 +4120,6 @@ msgstr "" msgid "AdminUsers|Unlock user %{username}?" msgstr "" -msgid "AdminUsers|Untrust user" -msgstr "" - msgid "AdminUsers|User administration" msgstr "" @@ -4165,9 +4150,6 @@ msgstr "" msgid "AdminUsers|What does this mean?" msgstr "" -msgid "AdminUsers|When allowed to create possible spam:" -msgstr "" - msgid "AdminUsers|When banned:" msgstr "" @@ -4186,9 +4168,6 @@ msgstr "" msgid "AdminUsers|You are about to permanently delete the user %{username}. This will delete all issues, merge requests, groups, and projects linked to them. To avoid data loss, consider using the %{strongStart}Block user%{strongEnd} feature instead. After you %{strongStart}Delete user%{strongEnd}, you cannot undo this action or recover the data." msgstr "" -msgid "AdminUsers|You can allow possible spam in the future if necessary." -msgstr "" - msgid "AdminUsers|You can always block their account again if needed." msgstr "" @@ -4204,9 +4183,6 @@ msgstr "" msgid "AdminUsers|You can ban their account in the future if necessary." msgstr "" -msgid "AdminUsers|You can disallow creating possible spam in the future." -msgstr "" - msgid "AdminUsers|You can unban their account in the future. Their data remains intact." msgstr "" @@ -18965,9 +18941,6 @@ msgstr "" msgid "Error occurred. User was not unlocked" msgstr "" -msgid "Error occurred. User was not updated" -msgstr "" - msgid "Error parsing CSV file. Please make sure it has" msgstr "" @@ -46143,9 +46116,6 @@ msgstr "" msgid "Successfully synced %{synced_timeago}." msgstr "" -msgid "Successfully trusted" -msgstr "" - msgid "Successfully unbanned" msgstr "" @@ -46158,9 +46128,6 @@ msgstr "" msgid "Successfully unlocked" msgstr "" -msgid "Successfully untrusted" -msgstr "" - msgid "Successfully updated %{last_updated_timeago}." msgstr "" diff --git a/spec/fixtures/api/schemas/entities/admin_users_data_attributes_paths.json b/spec/fixtures/api/schemas/entities/admin_users_data_attributes_paths.json index 61472b273e1342..44d8e48a972c48 100644 --- a/spec/fixtures/api/schemas/entities/admin_users_data_attributes_paths.json +++ b/spec/fixtures/api/schemas/entities/admin_users_data_attributes_paths.json @@ -1,51 +1,19 @@ { "type": "object", "properties": { - "edit": { - "type": "string" - }, - "approve": { - "type": "string" - }, - "reject": { - "type": "string" - }, - "unblock": { - "type": "string" - }, - "block": { - "type": "string" - }, - "deactivate": { - "type": "string" - }, - "activate": { - "type": "string" - }, - "unlock": { - "type": "string" - }, - "delete": { - "type": "string" - }, - "delete_with_contributions": { - "type": "string" - }, - "admin_user": { - "type": "string" - }, - "ban": { - "type": "string" - }, - "unban": { - "type": "string" - }, - "trust": { - "type": "string" - }, - "untrust": { - "type": "string" - } + "edit": { "type": "string" }, + "approve": { "type": "string" }, + "reject": { "type": "string" }, + "unblock": { "type": "string" }, + "block": { "type": "string" }, + "deactivate": { "type": "string" }, + "activate": { "type": "string" }, + "unlock": { "type": "string" }, + "delete": { "type": "string" }, + "delete_with_contributions": { "type": "string" }, + "admin_user": { "type": "string" }, + "ban": { "type": "string" }, + "unban": { "type": "string" } }, "required": [ "edit", @@ -60,9 +28,7 @@ "delete_with_contributions", "admin_user", "ban", - "unban", - "trust", - "untrust" + "unban" ], "additionalProperties": false } diff --git a/spec/frontend/admin/users/constants.js b/spec/frontend/admin/users/constants.js index 39e8e51f43ca0b..d341eb03b1b33c 100644 --- a/spec/frontend/admin/users/constants.js +++ b/spec/frontend/admin/users/constants.js @@ -9,8 +9,6 @@ const REJECT = 'reject'; const APPROVE = 'approve'; const BAN = 'ban'; const UNBAN = 'unban'; -const TRUST = 'trust'; -const UNTRUST = 'untrust'; export const EDIT = 'edit'; @@ -26,8 +24,6 @@ export const CONFIRMATION_ACTIONS = [ UNBAN, APPROVE, REJECT, - TRUST, - UNTRUST, ]; export const DELETE_ACTIONS = [DELETE, DELETE_WITH_CONTRIBUTIONS]; diff --git a/spec/helpers/admin/user_actions_helper_spec.rb b/spec/helpers/admin/user_actions_helper_spec.rb index abfdabf3413df9..87d2308690c617 100644 --- a/spec/helpers/admin/user_actions_helper_spec.rb +++ b/spec/helpers/admin/user_actions_helper_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" -RSpec.describe Admin::UserActionsHelper, feature_category: :user_management do +RSpec.describe Admin::UserActionsHelper do describe '#admin_actions' do let_it_be(:current_user) { build(:user) } @@ -29,33 +29,13 @@ context 'the user is a standard user' do let_it_be(:user) { create(:user) } - it do - is_expected.to contain_exactly( - "edit", - "block", - "ban", - "deactivate", - "delete", - "delete_with_contributions", - "trust" - ) - end + it { is_expected.to contain_exactly("edit", "block", "ban", "deactivate", "delete", "delete_with_contributions") } end context 'the user is an admin user' do let_it_be(:user) { create(:user, :admin) } - it do - is_expected.to contain_exactly( - "edit", - "block", - "ban", - "deactivate", - "delete", - "delete_with_contributions", - "trust" - ) - end + it { is_expected.to contain_exactly("edit", "block", "ban", "deactivate", "delete", "delete_with_contributions") } end context 'the user is blocked by LDAP' do @@ -79,16 +59,7 @@ context 'the user is deactivated' do let_it_be(:user) { create(:user, :deactivated) } - it do - is_expected.to contain_exactly( - "edit", - "block", - "ban", - "activate", - "delete", - "delete_with_contributions" - ) - end + it { is_expected.to contain_exactly("edit", "block", "ban", "activate", "delete", "delete_with_contributions") } end context 'the user is locked' do @@ -106,8 +77,7 @@ "deactivate", "unlock", "delete", - "delete_with_contributions", - "trust" + "delete_with_contributions" ) } end @@ -118,21 +88,6 @@ it { is_expected.to contain_exactly("edit", "unban", "delete", "delete_with_contributions") } end - context 'the user is trusted' do - let_it_be(:user) { create(:user, :trusted) } - - it do - is_expected.to contain_exactly("edit", - "block", - "deactivate", - "ban", - "delete", - "delete_with_contributions", - "untrust" - ) - end - end - context 'the current_user does not have permission to delete the user' do let_it_be(:user) { build(:user) } @@ -140,7 +95,7 @@ allow(helper).to receive(:can?).with(current_user, :destroy_user, user).and_return(false) end - it { is_expected.to contain_exactly("edit", "block", "ban", "deactivate", "trust") } + it { is_expected.to contain_exactly("edit", "block", "ban", "deactivate") } end context 'the user is a sole owner of a group' do @@ -151,7 +106,7 @@ group.add_owner(user) end - it { is_expected.to contain_exactly("edit", "block", "ban", "deactivate", "delete_with_contributions", "trust") } + it { is_expected.to contain_exactly("edit", "block", "ban", "deactivate", "delete_with_contributions") } end context 'the user is a bot' do diff --git a/spec/requests/admin/users_controller_spec.rb b/spec/requests/admin/users_controller_spec.rb index ef3a1c1375a535..e525d615b50311 100644 --- a/spec/requests/admin/users_controller_spec.rb +++ b/spec/requests/admin/users_controller_spec.rb @@ -74,24 +74,4 @@ expect { request }.to change { user.reload.access_locked? }.from(true).to(false) end end - - describe 'PUT #trust' do - subject(:request) { put trust_admin_user_path(user) } - - it 'trusts the user' do - expect { request }.to change { user.reload.trusted? }.from(false).to(true) - end - end - - describe 'PUT #untrust' do - before do - user.custom_attributes.create!(key: UserCustomAttribute::TRUSTED_BY, value: "placeholder") - end - - subject(:request) { put untrust_admin_user_path(user) } - - it 'trusts the user' do - expect { request }.to change { user.reload.trusted? }.from(true).to(false) - end - end end diff --git a/spec/services/users/untrust_service_spec.rb b/spec/services/users/untrust_service_spec.rb index bfa81bb233a298..054cb9b82dc9bc 100644 --- a/spec/services/users/untrust_service_spec.rb +++ b/spec/services/users/untrust_service_spec.rb @@ -23,12 +23,12 @@ end it 'updates the custom attributes', :aggregate_failures do - expect(user.custom_attributes.by_key(UserCustomAttribute::TRUSTED_BY)).to be_present + expect(user.trusted_with_spam_attribute).to be_present operation user.reload - expect(user.custom_attributes).to be_empty + expect(user.trusted_with_spam_attribute).to be nil end end end -- GitLab From 89ccb0db9028edcd244907ac1d8374e881619eb1 Mon Sep 17 00:00:00 2001 From: Ethan Urie Date: Tue, 26 Sep 2023 09:24:16 -0400 Subject: [PATCH 6/6] Filter reason options if user selects `Trust user` --- .../components/report_actions.vue | 15 ++++- .../admin/abuse_report/constants.js | 5 +- app/models/user.rb | 2 - .../components/report_actions_spec.js | 55 ++++++++++++++++++- 4 files changed, 68 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/admin/abuse_report/components/report_actions.vue b/app/assets/javascripts/admin/abuse_report/components/report_actions.vue index 560d733c10cebc..e005e183c9fc82 100644 --- a/app/assets/javascripts/admin/abuse_report/components/report_actions.vue +++ b/app/assets/javascripts/admin/abuse_report/components/report_actions.vue @@ -14,8 +14,10 @@ import { DRAWER_Z_INDEX } from '~/lib/utils/constants'; import { ACTIONS_I18N, NO_ACTION, + TRUST_ACTION, USER_ACTION_OPTIONS, REASON_OPTIONS, + TRUST_REASON, STATUS_OPEN, SUCCESS_ALERT, FAILED_ALERT, @@ -77,6 +79,16 @@ export default { userActionOptions() { return this.isNotCurrentUser ? USER_ACTION_OPTIONS : [NO_ACTION]; }, + reasonOptions() { + if (!this.isNotCurrentUser) { + return []; + } + + if (this.form.user_action === TRUST_ACTION.value) { + return [TRUST_REASON]; + } + return REASON_OPTIONS; + }, }, methods: { toggleActionsDrawer() { @@ -120,7 +132,6 @@ export default { }, }, i18n: ACTIONS_I18N, - reasonOptions: REASON_OPTIONS, DRAWER_Z_INDEX, }; @@ -173,7 +184,7 @@ export default { id="reason" v-model="form.reason" data-testid="reason-select" - :options="$options.reasonOptions" + :options="reasonOptions" :state="validationState.reason" @change="validateReason" /> diff --git a/app/assets/javascripts/admin/abuse_report/constants.js b/app/assets/javascripts/admin/abuse_report/constants.js index 9937dbc0b76125..94ef911e8532a1 100644 --- a/app/assets/javascripts/admin/abuse_report/constants.js +++ b/app/assets/javascripts/admin/abuse_report/constants.js @@ -25,12 +25,14 @@ export const ACTIONS_I18N = { }; export const NO_ACTION = { value: '', text: s__('AbuseReport|No action') }; +export const TRUST_REASON = { value: 'trusted', text: s__(`AbuseReport|Confirmed trusted user`) }; +export const TRUST_ACTION = { value: 'trust_user', text: s__('AbuseReport|Trust user') }; export const USER_ACTION_OPTIONS = [ NO_ACTION, { value: 'block_user', text: s__('AbuseReport|Block user') }, { value: 'ban_user', text: s__('AbuseReport|Ban user') }, - { value: 'trust_user', text: s__('AbuseReport|Trust user') }, + TRUST_ACTION, { value: 'delete_user', text: s__('AbuseReport|Delete user') }, ]; @@ -49,7 +51,6 @@ export const REASON_OPTIONS = [ text: s__('AbuseReport|Confirmed violation of a copyright or a trademark'), }, { value: 'malware', text: s__('AbuseReport|Confirmed posting of malware') }, - { value: 'trusted', text: s__(`AbuseReport|Confirmed trusted user`) }, { value: 'other', text: s__('AbuseReport|Something else') }, { value: 'unconfirmed', text: s__('AbuseReport|Abuse unconfirmed') }, ]; diff --git a/app/models/user.rb b/app/models/user.rb index f1d08f12e47f45..b76d19240f8206 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -768,8 +768,6 @@ def filter_items(filter_name) external when 'deactivated' deactivated - when "trusted" - trusted else active_without_ghosts end diff --git a/spec/frontend/admin/abuse_report/components/report_actions_spec.js b/spec/frontend/admin/abuse_report/components/report_actions_spec.js index 0e20630db1435a..3c366980c14ba5 100644 --- a/spec/frontend/admin/abuse_report/components/report_actions_spec.js +++ b/spec/frontend/admin/abuse_report/components/report_actions_spec.js @@ -17,6 +17,9 @@ import { ERROR_MESSAGE, NO_ACTION, USER_ACTION_OPTIONS, + TRUST_ACTION, + TRUST_REASON, + REASON_OPTIONS, } from '~/admin/abuse_report/constants'; import { mockAbuseReport } from '../mock_data'; @@ -40,10 +43,11 @@ describe('ReportActions', () => { const setCloseReport = (close) => wrapper.findByTestId('close').find('input').setChecked(close); const setSelectOption = (id, value) => wrapper.findByTestId(`${id}-select`).find(`option[value=${value}]`).setSelected(); - const selectAction = (action) => setSelectOption('action', action); + const selectAction = (chosenAction) => setSelectOption('action', chosenAction); const selectReason = (reason) => setSelectOption('reason', reason); const setComment = (comment) => wrapper.findByTestId('comment').find('input').setValue(comment); const submitForm = () => wrapper.findByTestId('submit-button').vm.$emit('click'); + const findReasonOptions = () => wrapper.findByTestId('reason-select'); const createComponent = (props = {}) => { wrapper = mountExtended(ReportActions, { @@ -79,8 +83,8 @@ describe('ReportActions', () => { expect(options).toHaveLength(USER_ACTION_OPTIONS.length); - USER_ACTION_OPTIONS.forEach((action, index) => { - expect(options.at(index).text()).toBe(action.text); + USER_ACTION_OPTIONS.forEach((userAction, index) => { + expect(options.at(index).text()).toBe(userAction.text); }); }); }); @@ -100,6 +104,51 @@ describe('ReportActions', () => { }); }); + describe('reasons', () => { + beforeEach(() => { + clickActionsButton(); + }); + + it('shows all non-trust reasons by default', () => { + const reasons = findReasonOptions().findAll('option'); + expect(reasons).toHaveLength(REASON_OPTIONS.length); + + REASON_OPTIONS.forEach((reason, index) => { + expect(reasons.at(index).text()).toBe(reason.text); + }); + }); + + describe('when user selects any non-trust action', () => { + it('shows non-trust reasons', () => { + const reasonLength = REASON_OPTIONS.length; + let reasons; + + USER_ACTION_OPTIONS.forEach((userAction) => { + if (userAction !== TRUST_ACTION && userAction !== NO_ACTION) { + selectAction(userAction.value); + + reasons = findReasonOptions().findAll('option'); + expect(reasons).toHaveLength(reasonLength); + } + }); + }); + }); + + describe('when user selects "Trust user"', () => { + beforeEach(() => { + selectAction(TRUST_ACTION.value); + }); + + it('only shows "Confirmed trusted user" reason', () => { + const reasons = findReasonOptions().findAll('option'); + + expect(reasons).toHaveLength(1); + + expect(reasons.at(0).text()).toBe(TRUST_REASON.text); + }); + }); + }); + describe('when clicking the actions button', () => { beforeEach(() => { clickActionsButton(); -- GitLab