diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 9f0d911f5ef34fe35ff81ff5a25b7217874dfa40..2e9229db56cc311ca0f133993013281a19387e87 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -7,6 +7,7 @@ class Admin::UsersController < Admin::ApplicationController before_action :user, except: [:index, :cohorts, :new, :create] before_action :check_impersonation_availability, only: :impersonate before_action :ensure_destroy_prerequisites_met, only: [:destroy] + before_action :check_ban_user_feature_flag, only: [:ban] feature_category :users @@ -130,6 +131,24 @@ def unblock end end + def ban + result = Users::BanService.new(current_user).execute(user) + + if result[:status] == :success + redirect_back_or_admin_user(notice: _("Successfully banned")) + else + redirect_back_or_admin_user(alert: _("Error occurred. User was not banned")) + end + end + + def unban + if update_user { |user| user.activate } + redirect_back_or_admin_user(notice: _("Successfully unbanned")) + else + redirect_back_or_admin_user(alert: _("Error occurred. User was not unbanned")) + end + end + def unlock if update_user { |user| user.unlock_access! } redirect_back_or_admin_user(alert: _("Successfully unlocked")) @@ -325,6 +344,10 @@ def check_impersonation_availability access_denied! unless Gitlab.config.gitlab.impersonation_enabled end + def check_ban_user_feature_flag + access_denied! unless Feature.enabled?(:ban_user_feature_flag) + end + def log_impersonation_event Gitlab::AppLogger.info(_("User %{current_user_username} has started impersonating %{username}") % { current_user_username: current_user.username, username: user.username }) end diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb index 998c697e6176e0f1d1acd6b585bef1f92e9ecd5a..c1d05c2d3cfb8f4c0d62019d06850b595a57a574 100644 --- a/app/helpers/users_helper.rb +++ b/app/helpers/users_helper.rb @@ -162,6 +162,49 @@ def user_block_effects header + list end + def user_ban_data(user) + { + path: ban_admin_user_path(user), + method: 'put', + modal_attributes: { + title: s_('AdminUsers|Ban user %{username}?') % { username: sanitize_name(user.name) }, + message: s_('AdminUsers|You can unban their account in the future. Their data remains intact.'), + okVariant: 'warning', + okTitle: s_('AdminUsers|Ban') + }.to_json + } + end + + def user_unban_data(user) + { + path: unban_admin_user_path(user), + method: 'put', + modal_attributes: { + title: s_('AdminUsers|Unban %{username}?') % { username: sanitize_name(user.name) }, + message: s_('AdminUsers|You ban their account in the future if necessary.'), + okVariant: 'info', + okTitle: s_('AdminUsers|Unban') + }.to_json + } + end + + def user_ban_effects + header = tag.p s_('AdminUsers|Banning the user has the following effects:') + + list = tag.ul do + concat tag.li s_('AdminUsers|User will be blocked') + end + + link_start = ''.html_safe % { url: help_page_path("user/admin_area/moderate_users", anchor: "ban-a-user") } + info = tag.p s_('AdminUsers|Learn more about %{link_start}banned users.%{link_end}').html_safe % { link_start: link_start, link_end: ''.html_safe } + + header + list + info + end + + def ban_feature_available? + Feature.enabled?(:ban_user_feature_flag) + end + def user_deactivation_data(user, message) { path: deactivate_admin_user_path(user), @@ -235,6 +278,9 @@ def blocked_user_badge(user) pending_approval_badge = { text: s_('AdminUsers|Pending approval'), variant: 'info' } return pending_approval_badge if user.blocked_pending_approval? + banned_badge = { text: s_('AdminUsers|Banned'), variant: 'danger' } + return banned_badge if user.banned? + { text: s_('AdminUsers|Blocked'), variant: 'danger' } end diff --git a/app/models/user.rb b/app/models/user.rb index 4b38ef771a125ed19b5cd1cfb84a4280fa9de834..4ff6221bcab34ec0e2b5053f4ca2d71069520d64 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -326,6 +326,7 @@ def max_access_for_group transition deactivated: :blocked transition ldap_blocked: :blocked transition blocked_pending_approval: :blocked + transition banned: :blocked end event :ldap_block do @@ -338,19 +339,24 @@ def max_access_for_group transition blocked: :active transition ldap_blocked: :active transition blocked_pending_approval: :active + transition banned: :active end event :block_pending_approval do transition active: :blocked_pending_approval end + event :ban do + transition active: :banned + end + event :deactivate do # Any additional changes to this event should be also # reflected in app/workers/users/deactivate_dormant_users_worker.rb transition active: :deactivated end - state :blocked, :ldap_blocked, :blocked_pending_approval do + state :blocked, :ldap_blocked, :blocked_pending_approval, :banned do def blocked? true end @@ -377,6 +383,7 @@ def blocked? scope :instance_access_request_approvers_to_be_notified, -> { admins.active.order_recent_sign_in.limit(INSTANCE_ACCESS_REQUEST_APPROVERS_TO_BE_NOTIFIED_LIMIT) } scope :blocked, -> { with_states(:blocked, :ldap_blocked) } scope :blocked_pending_approval, -> { with_states(:blocked_pending_approval) } + scope :banned, -> { with_states(:banned) } scope :external, -> { where(external: true) } scope :non_external, -> { where(external: false) } scope :confirmed, -> { where.not(confirmed_at: nil) } @@ -598,6 +605,8 @@ def filter_items(filter_name) blocked when 'blocked_pending_approval' blocked_pending_approval + when 'banned' + banned when 'two_factor_disabled' without_two_factor when 'two_factor_enabled' diff --git a/app/services/users/ban_service.rb b/app/services/users/ban_service.rb new file mode 100644 index 0000000000000000000000000000000000000000..247ed14966bedb4583b58f90fbd34bdb06fc79b9 --- /dev/null +++ b/app/services/users/ban_service.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Users + class BanService < BaseService + def initialize(current_user) + @current_user = current_user + end + + def execute(user) + if user.ban + log_event(user) + success + else + messages = user.errors.full_messages + error(messages.uniq.join('. ')) + end + end + + private + + def log_event(user) + Gitlab::AppLogger.info(message: "User banned", user: "#{user.username}", email: "#{user.email}", banned_by: "#{current_user.username}", ip_address: "#{current_user.current_sign_in_ip}") + end + end +end diff --git a/app/views/admin/users/_ban_user.html.haml b/app/views/admin/users/_ban_user.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..229c88adb7f7a4b7c44541b57a8c6f18ad119ad9 --- /dev/null +++ b/app/views/admin/users/_ban_user.html.haml @@ -0,0 +1,9 @@ +- if ban_feature_available? + .card.border-warning + .card-header.bg-warning.gl-text-white + = s_('AdminUsers|Ban user') + .card-body + = user_ban_effects + %br + %button.btn.gl-button.btn-warning.js-confirm-modal-button{ data: user_ban_data(user) } + = s_('AdminUsers|Ban user') diff --git a/app/views/admin/users/_head.html.haml b/app/views/admin/users/_head.html.haml index ade3581e5b90c9069d1075e55ad01f7204b44459..be04e87f8b9cbe29fed52b23fafc8903047c6519 100644 --- a/app/views/admin/users/_head.html.haml +++ b/app/views/admin/users/_head.html.haml @@ -3,6 +3,9 @@ - if @user.blocked_pending_approval? %span.cred = s_('AdminUsers|(Pending approval)') + - elsif @user.banned? + %span.cred + = s_('AdminUsers|(Banned)') - elsif @user.blocked? %span.cred = s_('AdminUsers|(Blocked)') diff --git a/app/views/admin/users/_users.html.haml b/app/views/admin/users/_users.html.haml index efa50ec47f5b678f10dff987447ae206818850a8..e4438f38a47577c566f2608cecd58c4831e2e06c 100644 --- a/app/views/admin/users/_users.html.haml +++ b/app/views/admin/users/_users.html.haml @@ -28,6 +28,11 @@ = link_to admin_users_path(filter: "blocked") do = s_('AdminUsers|Blocked') %small.badge.gl-tab-counter-badge.badge-muted.badge-pill.gl-badge.sm= limited_counter_with_delimiter(User.blocked) + - if ban_feature_available? + = nav_link(html_options: { class: active_when(params[:filter] == 'banned') }) do + = link_to admin_users_path(filter: "banned") do + = s_('AdminUsers|Banned') + %small.badge.gl-tab-counter-badge.badge-muted.badge-pill.gl-badge.sm= limited_counter_with_delimiter(User.banned) = nav_link(html_options: { class: "#{active_when(params[:filter] == 'blocked_pending_approval')} filter-blocked-pending-approval" }) do = link_to admin_users_path(filter: "blocked_pending_approval"), data: { qa_selector: 'pending_approval_tab' } do = s_('AdminUsers|Pending approval') diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml index 03ddf5c76a027559ca977d5050b5bed1fbc4526f..19cc29668f59e0ffae7d059b3bfe36466e7efc23 100644 --- a/app/views/admin/users/show.html.haml +++ b/app/views/admin/users/show.html.haml @@ -176,6 +176,20 @@ - if @user.blocked_pending_approval? = render 'admin/users/approve_user', user: @user = render 'admin/users/reject_pending_user', user: @user + - elsif @user.banned? + .gl-card.border-info.gl-mb-5 + .gl-card-header.gl-bg-blue-500.gl-text-white + = _('This user is banned') + .gl-card-body + %p= _('A banned user cannot:') + %ul + %li= _('Log in') + %li= _('Access Git repositories') + - link_start = ''.html_safe % { url: help_page_path("user/admin_area/moderate_users", anchor: "ban-a-user") } + = s_('AdminUsers|Learn more about %{link_start}banned users.%{link_end}').html_safe % { link_start: link_start, link_end: ''.html_safe } + %p + %button.btn.gl-button.btn-info.js-confirm-modal-button{ data: user_unban_data(@user) } + = s_('AdminUsers|Unban user') - else .gl-card.border-info.gl-mb-5 .gl-card-header.gl-bg-blue-500.gl-text-white @@ -190,6 +204,7 @@ = s_('AdminUsers|Unblock user') - elsif !@user.internal? = render 'admin/users/block_user', user: @user + = render 'admin/users/ban_user', user: @user - if @user.access_locked? .card.border-info.gl-mb-5 diff --git a/changelogs/unreleased/ban-user-state-ui.yml b/changelogs/unreleased/ban-user-state-ui.yml new file mode 100644 index 0000000000000000000000000000000000000000..6da3d575dc9a20dd1072b3923c442163dbfe9673 --- /dev/null +++ b/changelogs/unreleased/ban-user-state-ui.yml @@ -0,0 +1,5 @@ +--- +title: Ban user state and UI +merge_request: 61292 +author: +type: added diff --git a/config/feature_flags/development/ban_user_feature_flag.yml b/config/feature_flags/development/ban_user_feature_flag.yml new file mode 100644 index 0000000000000000000000000000000000000000..6765e82e25205bb6f78e3a5ebe9afe9c466e9477 --- /dev/null +++ b/config/feature_flags/development/ban_user_feature_flag.yml @@ -0,0 +1,8 @@ +--- +name: ban_user_feature_flag +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/61292 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/330667 +milestone: '13.12' +type: development +group: group::access +default_enabled: false diff --git a/config/routes/admin.rb b/config/routes/admin.rb index e7f851f7de4d76cbc9599946dbf26e647aba382d..2ba00e3bf665373fdc9178fff49df5a6006e3aeb 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -21,6 +21,8 @@ get :keys put :block put :unblock + put :ban + put :unban put :deactivate put :activate put :unlock diff --git a/doc/subscriptions/self_managed/index.md b/doc/subscriptions/self_managed/index.md index db0d54ff14c3357e1ba3ffd5135177fb486bbca4..648ad0c70a56d2f97f8582d45ba932eaacaa0b15 100644 --- a/doc/subscriptions/self_managed/index.md +++ b/doc/subscriptions/self_managed/index.md @@ -48,8 +48,8 @@ using [Seat Link](#seat-link). A _billable user_ counts against the number of subscription seats. Every user is considered a billable user, with the following exceptions: -- [Deactivated users](../../user/admin_area/activating_deactivating_users.md#deactivating-a-user) and - [blocked users](../../user/admin_area/blocking_unblocking_users.md) don't count as billable users in the current subscription. When they are either deactivated or blocked they release a _billable user_ seat. However, they may +- [Deactivated users](../../user/admin_area/moderate_users.md#deactivating-a-user) and + [blocked users](../../user/admin_area/moderate_users.md#blocking-a-user) don't count as billable users in the current subscription. When they are either deactivated or blocked they release a _billable user_ seat. However, they may count toward overages in the subscribed seat count. - Users who are [pending approval](../../user/admin_area/approving_users.md). - Members with Guest permissions on an Ultimate subscription. @@ -183,7 +183,7 @@ Starting 30 days before a subscription expires, GitLab notifies administrators o We recommend following these steps during renewal: -1. Prune any inactive or unwanted users by [blocking them](../../user/admin_area/blocking_unblocking_users.md#blocking-a-user). +1. Prune any inactive or unwanted users by [blocking them](../../user/admin_area/moderate_users.md#blocking-a-user). 1. Determine if you have a need for user growth in the upcoming subscription. 1. Log in to the [Customers Portal](https://customers.gitlab.com/customers/sign_in) and select the **Renew** button beneath your existing subscription. diff --git a/doc/user/admin_area/activating_deactivating_users.md b/doc/user/admin_area/activating_deactivating_users.md index 144ee2dbf9873249e0124da2a120b18e40463ccc..cafc7caf981ea00597d28d2dac5e10c5b0b5010d 100644 --- a/doc/user/admin_area/activating_deactivating_users.md +++ b/doc/user/admin_area/activating_deactivating_users.md @@ -1,69 +1,8 @@ --- -stage: Manage -group: Access -info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments -type: howto +redirect_to: 'moderate_users.md' --- -# Activating and deactivating users +This document was moved to [another location](moderate_users.md). -GitLab administrators can deactivate and activate users. - -## Deactivating a user - -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/22257) in GitLab 12.4. - -In order to temporarily prevent access by a GitLab user that has no recent activity, administrators -can choose to deactivate the user. - -Deactivating a user is functionally identical to [blocking a user](blocking_unblocking_users.md), -with the following differences: - -- It does not prohibit the user from logging back in via the UI. -- Once a deactivated user logs back into the GitLab UI, their account is set to active. - -A deactivated user: - -- Cannot access Git repositories or the API. -- Will not receive any notifications from GitLab. -- Will not be able to use [slash commands](../../integration/slash_commands.md). - -Personal projects, and group and user history of the deactivated user will be left intact. - -A user can be deactivated from the Admin Area. To do this: - -1. Navigate to **Admin Area > Overview > Users**. -1. Select a user. -1. Under the **Account** tab, click **Deactivate user**. - -Please note that for the deactivation option to be visible to an admin, the user: - -- Must be currently active. -- Must not have signed in, or have any activity, in the last 90 days. - -Users can also be deactivated using the [GitLab API](../../api/users.md#deactivate-user). - -NOTE: -A deactivated user does not consume a [seat](../../subscriptions/self_managed/index.md#billable-users). - -## Activating a user - -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/22257) in GitLab 12.4. - -A deactivated user can be activated from the Admin Area. - -To do this: - -1. Navigate to **Admin Area > Overview > Users**. -1. Click on the **Deactivated** tab. -1. Select a user. -1. Under the **Account** tab, click **Activate user**. - -Users can also be activated using the [GitLab API](../../api/users.md#activate-user). - -NOTE: -Activating a user changes the user's state to active and consumes a -[seat](../../subscriptions/self_managed/index.md#billable-users). - -NOTE: -A deactivated user can also activate their account themselves by logging back in via the UI. + + diff --git a/doc/user/admin_area/approving_users.md b/doc/user/admin_area/approving_users.md index 9141d7f488de2df5fbda71005a5c777838842c4b..2b3b90cb1a49ccace60249ab4c02aa499781624b 100644 --- a/doc/user/admin_area/approving_users.md +++ b/doc/user/admin_area/approving_users.md @@ -21,7 +21,7 @@ When a user registers for an account while this setting is enabled: A user pending approval: -- Is functionally identical to a [blocked](blocking_unblocking_users.md) user. +- Is functionally identical to a [blocked](moderate_users.md#blocking-a-user) user. - Cannot sign in. - Cannot access Git repositories or the GitLab API. - Does not receive any notifications from GitLab. diff --git a/doc/user/admin_area/blocking_unblocking_users.md b/doc/user/admin_area/blocking_unblocking_users.md index 14a5b5085cb069943cba832c3fb5d87983445dc8..cafc7caf981ea00597d28d2dac5e10c5b0b5010d 100644 --- a/doc/user/admin_area/blocking_unblocking_users.md +++ b/doc/user/admin_area/blocking_unblocking_users.md @@ -1,51 +1,8 @@ --- -stage: Manage -group: Access -info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments -type: howto +redirect_to: 'moderate_users.md' --- -# Blocking and unblocking users +This document was moved to [another location](moderate_users.md). -GitLab administrators block and unblock users. - -## Blocking a user - -In order to completely prevent access of a user to the GitLab instance, administrators can choose to -block the user. - -Users can be blocked [via an abuse report](review_abuse_reports.md#blocking-users), -or directly from the Admin Area. To do this: - -1. Navigate to **Admin Area > Overview > Users**. -1. Select a user. -1. Under the **Account** tab, click **Block user**. - -A blocked user: - -- Cannot log in. -- Cannot access Git repositories or the API. -- Does not receive any notifications from GitLab. -- Cannot use [slash commands](../../integration/slash_commands.md). - -Personal projects, and group and user history of the blocked user are left intact. - -Users can also be blocked using the [GitLab API](../../api/users.md#block-user). - -NOTE: -A blocked user does not consume a [seat](../../subscriptions/self_managed/index.md#billable-users). - -## Unblocking a user - -A blocked user can be unblocked from the Admin Area. To do this: - -1. Navigate to **Admin Area > Overview > Users**. -1. Click on the **Blocked** tab. -1. Select a user. -1. Under the **Account** tab, click **Unblock user**. - -Users can also be unblocked using the [GitLab API](../../api/users.md#unblock-user). - -NOTE: -Unblocking a user changes the user's state to active and consumes a -[seat](../../subscriptions/self_managed/index.md#billable-users). + + diff --git a/doc/user/admin_area/index.md b/doc/user/admin_area/index.md index 01926f289375871e591f6e1a7837f8654684a1f8..60c1ecbd10f479b1db4d44eb564e34dcfe179d26 100644 --- a/doc/user/admin_area/index.md +++ b/doc/user/admin_area/index.md @@ -117,8 +117,8 @@ To list users matching a specific criteria, click on one of the following tabs o - **2FA Enabled** - **2FA Disabled** - **External** -- **[Blocked](blocking_unblocking_users.md)** -- **[Deactivated](activating_deactivating_users.md)** +- **[Blocked](moderate_users.md#blocking-a-user)** +- **[Deactivated](moderate_users.md#deactivating-a-user)** - **Without projects** For each user, the following are listed: diff --git a/doc/user/admin_area/moderate_users.md b/doc/user/admin_area/moderate_users.md new file mode 100644 index 0000000000000000000000000000000000000000..c04003dd75f4b5befc2bc66d65a99a5ce2aec224 --- /dev/null +++ b/doc/user/admin_area/moderate_users.md @@ -0,0 +1,157 @@ +--- +stage: Manage +group: Access +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments +type: howto +--- + +# Moderate users + +GitLab administrators can moderate user access by blocking, banning, or deactivating users. + +## Blocking and unblocking users + +GitLab administrators can block and unblock users. + +### Blocking a user + +In order to completely prevent access of a user to the GitLab instance, +administrators can choose to block the user. + +Users can be blocked [via an abuse report](review_abuse_reports.md#blocking-users), +or directly from the Admin Area. To do this: + +1. Navigate to **Admin Area > Overview > Users**. +1. Select a user. +1. Under the **Account** tab, click **Block user**. + +A blocked user: + +- Cannot log in. +- Cannot access Git repositories or the API. +- Does not receive any notifications from GitLab. +- Cannot use [slash commands](../../integration/slash_commands.md). + +Personal projects, and group and user history of the blocked user are left intact. + +Users can also be blocked using the [GitLab API](../../api/users.md#block-user). + +NOTE: +A blocked user does not consume a [seat](../../subscriptions/self_managed/index.md#billable-users). + +### Unblocking a user + +A blocked user can be unblocked from the Admin Area. To do this: + +1. Navigate to **Admin Area > Overview > Users**. +1. Click on the **Blocked** tab. +1. Select a user. +1. Under the **Account** tab, click **Unblock user**. + +Users can also be unblocked using the [GitLab API](../../api/users.md#unblock-user). + +NOTE: +Unblocking a user changes the user's state to active and consumes a +[seat](../../subscriptions/self_managed/index.md#billable-users). + +## Activating and deactivating users + +GitLab administrators can deactivate and activate users. + +### Deactivating a user + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/22257) in GitLab 12.4. + +In order to temporarily prevent access by a GitLab user that has no recent activity, +administrators can choose to deactivate the user. + +Deactivating a user is functionally identical to [blocking a user](#blocking-and-unblocking-users), +with the following differences: + +- It does not prohibit the user from logging back in via the UI. +- Once a deactivated user logs back into the GitLab UI, their account is set to active. + +A deactivated user: + +- Cannot access Git repositories or the API. +- Will not receive any notifications from GitLab. +- Will not be able to use [slash commands](../../integration/slash_commands.md). + +Personal projects, and group and user history of the deactivated user are left intact. + +A user can be deactivated from the Admin Area. To do this: + +1. Navigate to **Admin Area > Overview > Users**. +1. Select a user. +1. Under the **Account** tab, click **Deactivate user**. + +Please note that for the deactivation option to be visible to an admin, the user: + +- Must be currently active. +- Must not have signed in, or have any activity, in the last 90 days. + +Users can also be deactivated using the [GitLab API](../../api/users.md#deactivate-user). + +NOTE: +A deactivated user does not consume a [seat](../../subscriptions/self_managed/index.md#billable-users). + +### Activating a user + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/22257) in GitLab 12.4. + +A deactivated user can be activated from the Admin Area. + +To do this: + +1. Navigate to **Admin Area > Overview > Users**. +1. Click on the **Deactivated** tab. +1. Select a user. +1. Under the **Account** tab, click **Activate user**. + +Users can also be activated using the [GitLab API](../../api/users.md#activate-user). + +NOTE: +Activating a user changes the user's state to active and consumes a +[seat](../../subscriptions/self_managed/index.md#billable-users). + +NOTE: +A deactivated user can also activate their account themselves by logging back in via the UI. + +## Ban and unban users + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/327353) in GitLab 13.12. + +GitLab administrators can ban users. + +NOTE: +This feature is behind a feature flag that is disabled by default. GitLab administrators +with access to the GitLab Rails console can [enable](../../administration/feature_flags.md) +this feature for your GitLab instance. + +### Ban a user + +To completely block a user, administrators can choose to ban the user. + +Users can be banned using the Admin Area. To do this: + +1. Navigate to **Admin Area > Overview > Users**. +1. Select a user. +1. Under the **Account** tab, click **Ban user**. + +NOTE: +This feature is a work in progress. Currently, banning a user +only blocks them and does not hide their comments or issues. +This functionality will be implemented in follow up issues. + +### Unban a user + +A banned user can be unbanned using the Admin Area. To do this: + +1. Navigate to **Admin Area > Overview > Users**. +1. Click on the **Banned** tab. +1. Select a user. +1. Under the **Account** tab, click **Unban user**. + +NOTE: +Unbanning a user changes the user's state to active and consumes a +[seat](../../subscriptions/self_managed/index.md#billable-users). diff --git a/doc/user/profile/account/delete_account.md b/doc/user/profile/account/delete_account.md index c0560269f00b0bdacb084f35c12135b8801fc0c9..361353a0f8c092dfaeced326fce8730bb88202e0 100644 --- a/doc/user/profile/account/delete_account.md +++ b/doc/user/profile/account/delete_account.md @@ -69,7 +69,7 @@ username of the original user. When using the **Delete user and contributions** option, **all** associated records are removed. This includes all of the items mentioned above including issues, merge requests, notes/comments, and more. Consider -[blocking a user](../../admin_area/blocking_unblocking_users.md) +[blocking a user](../../admin_area/moderate_users.md#blocking-a-user) or using the **Delete user** option instead. When a user is deleted from an [abuse report](../../admin_area/review_abuse_reports.md) diff --git a/doc/user/upgrade_email_bypass.md b/doc/user/upgrade_email_bypass.md index 199c1a47e04d9b0ae90c1cb51925aabcd9cd1a70..7d2f2395815cb1e7a95d6635628c42c5465c1040 100644 --- a/doc/user/upgrade_email_bypass.md +++ b/doc/user/upgrade_email_bypass.md @@ -73,7 +73,7 @@ Your account has been blocked. Fatal: Could not read from remote repository Your primary email address is not confirmed. ``` -You can assure your users that they have not been [Blocked](admin_area/blocking_unblocking_users.md) by an administrator. +You can assure your users that they have not been [Blocked](admin_area/moderate_users.md#blocking-and-unblocking-users) by an administrator. When affected users see this message, they must confirm their email address before they can commit code. ## What do I need to know as an administrator of a GitLab self-managed Instance? diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 2afaf90e74046c34c8b344a6de74603218ff47d4..b2c11642456e55354b4d5203ea7e3f4106f1fb80 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -1373,6 +1373,9 @@ msgstr "" msgid "A Let's Encrypt account will be configured for this GitLab installation using your email address. You will receive emails to warn of expiring certificates." msgstr "" +msgid "A banned user cannot:" +msgstr "" + msgid "A basic page and serverless function that uses AWS Lambda, AWS API Gateway, and GitLab Pages" msgstr "" @@ -2435,6 +2438,9 @@ msgstr "" msgid "AdminUsers|(Admin)" msgstr "" +msgid "AdminUsers|(Banned)" +msgstr "" + msgid "AdminUsers|(Blocked)" msgstr "" @@ -2501,6 +2507,21 @@ msgstr "" msgid "AdminUsers|Automatically marked as default internal user" msgstr "" +msgid "AdminUsers|Ban" +msgstr "" + +msgid "AdminUsers|Ban user" +msgstr "" + +msgid "AdminUsers|Ban user %{username}?" +msgstr "" + +msgid "AdminUsers|Banned" +msgstr "" + +msgid "AdminUsers|Banning the user has the following effects:" +msgstr "" + msgid "AdminUsers|Be added to groups and projects" msgstr "" @@ -2588,6 +2609,9 @@ msgstr "" msgid "AdminUsers|It's you!" msgstr "" +msgid "AdminUsers|Learn more about %{link_start}banned users.%{link_end}" +msgstr "" + msgid "AdminUsers|Log in" msgstr "" @@ -2669,6 +2693,15 @@ msgstr "" msgid "AdminUsers|To confirm, type %{username}" msgstr "" +msgid "AdminUsers|Unban" +msgstr "" + +msgid "AdminUsers|Unban %{username}?" +msgstr "" + +msgid "AdminUsers|Unban user" +msgstr "" + msgid "AdminUsers|Unblock" msgstr "" @@ -2684,6 +2717,9 @@ msgstr "" msgid "AdminUsers|Unlock user %{username}?" msgstr "" +msgid "AdminUsers|User will be blocked" +msgstr "" + msgid "AdminUsers|User will not be able to access git repositories" msgstr "" @@ -2720,6 +2756,9 @@ msgstr "" msgid "AdminUsers|You are about to permanently delete the user %{username}. This will delete all of the issues, merge requests, and groups linked to them. To avoid data loss, consider using the %{strongStart}block user%{strongEnd} feature instead. Once you %{strongStart}Delete user%{strongEnd}, it cannot be undone or recovered." msgstr "" +msgid "AdminUsers|You ban their account in the future if necessary." +msgstr "" + msgid "AdminUsers|You can always block their account again if needed." msgstr "" @@ -2732,6 +2771,9 @@ msgstr "" msgid "AdminUsers|You can always unblock their account, their data will remain intact." msgstr "" +msgid "AdminUsers|You can unban their account in the future. Their data remains intact." +msgstr "" + msgid "AdminUsers|You cannot remove your own admin rights." msgstr "" @@ -12905,12 +12947,18 @@ msgstr "" msgid "Error occurred. A blocked user must be unblocked to be activated" msgstr "" +msgid "Error occurred. User was not banned" +msgstr "" + msgid "Error occurred. User was not blocked" msgstr "" msgid "Error occurred. User was not confirmed" msgstr "" +msgid "Error occurred. User was not unbanned" +msgstr "" + msgid "Error occurred. User was not unblocked" msgstr "" @@ -31247,6 +31295,9 @@ msgstr "" msgid "Successfully approved" msgstr "" +msgid "Successfully banned" +msgstr "" + msgid "Successfully blocked" msgstr "" @@ -31271,6 +31322,9 @@ msgstr "" msgid "Successfully synced %{synced_timeago}." msgstr "" +msgid "Successfully unbanned" +msgstr "" + msgid "Successfully unblocked" msgstr "" @@ -33377,6 +33431,9 @@ msgstr "" msgid "This user has the %{access} role in the %{name} project." msgstr "" +msgid "This user is banned" +msgstr "" + msgid "This user is blocked" msgstr "" diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb index 1afd20f5021e9de3c1572c255c10bfbae75ebd70..722c9c322cca93d105900a16fecc8b24936621e8 100644 --- a/spec/controllers/admin/users_controller_spec.rb +++ b/spec/controllers/admin/users_controller_spec.rb @@ -365,6 +365,56 @@ end end + describe 'PUT ban/:id' do + context 'when ban_user_feature_flag is enabled' do + it 'bans user' do + put :ban, params: { id: user.username } + + user.reload + expect(user.banned?).to be_truthy + expect(flash[:notice]).to eq _('Successfully banned') + end + + context 'when unsuccessful' do + let(:user) { create(:user, :blocked) } + + it 'does not ban user' do + put :ban, params: { id: user.username } + + user.reload + expect(user.banned?).to be_falsey + expect(flash[:alert]).to eq _('Error occurred. User was not banned') + end + end + end + + context 'when ban_user_feature_flag is not enabled' do + before do + stub_feature_flags(ban_user_feature_flag: false) + end + + it 'does not ban user, renders 404' do + put :ban, params: { id: user.username } + + user.reload + expect(user.banned?).to be_falsey + expect(response).to have_gitlab_http_status(:not_found) + end + end + end + + describe 'PUT unban/:id' do + let(:banned_user) { create(:user, :banned) } + + it 'unbans user' do + put :unban, params: { id: banned_user.username } + + banned_user.reload + expect(banned_user.banned?).to be_falsey + expect(flash[:notice]).to eq _('Successfully unbanned') + end + end + describe 'PUT unlock/:id' do before do request.env["HTTP_REFERER"] = "/" diff --git a/spec/factories/users.rb b/spec/factories/users.rb index 7ac44d55687ecfc86bb9aa5a4236c36ab2dc7ea5..476c57f2d80d0aef5b9d3f9bdb23cf1d63944cd8 100644 --- a/spec/factories/users.rb +++ b/spec/factories/users.rb @@ -27,6 +27,10 @@ after(:build) { |user, _| user.block_pending_approval! } end + trait :banned do + after(:build) { |user, _| user.ban! } + end + trait :ldap_blocked do after(:build) { |user, _| user.ldap_block! } end diff --git a/spec/features/admin/users/users_spec.rb b/spec/features/admin/users/users_spec.rb index 36907d4aa603d71220bb28882ca67f4b80441844..d3931373ee3ba00f91f641a454ffeda8185bfe28 100644 --- a/spec/features/admin/users/users_spec.rb +++ b/spec/features/admin/users/users_spec.rb @@ -85,6 +85,7 @@ expect(page).to have_link('2FA Disabled', href: admin_users_path(filter: 'two_factor_disabled')) expect(page).to have_link('External', href: admin_users_path(filter: 'external')) expect(page).to have_link('Blocked', href: admin_users_path(filter: 'blocked')) + expect(page).to have_link('Banned', href: admin_users_path(filter: 'banned')) expect(page).to have_link('Deactivated', href: admin_users_path(filter: 'deactivated')) expect(page).to have_link('Without projects', href: admin_users_path(filter: 'wop')) end diff --git a/spec/helpers/users_helper_spec.rb b/spec/helpers/users_helper_spec.rb index 46073b37a3b023125fec497de447be1220fb6979..862fd58df04e733739cf12f4accb53d0b4a4e626 100644 --- a/spec/helpers/users_helper_spec.rb +++ b/spec/helpers/users_helper_spec.rb @@ -136,6 +136,16 @@ def filter_ee_badges(badges) end end + context 'with a banned user' do + it 'returns the banned badge' do + banned_user = create(:user, :banned) + + badges = helper.user_badges_in_admin_section(banned_user) + + expect(filter_ee_badges(badges)).to eq([text: 'Banned', variant: 'danger']) + end + end + context 'with an admin user' do it "returns the admin badge" do admin_user = create(:admin) diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 0c35b1265ba4f212daa1cd5bcc0125abe55ca764..cb34917f07352d1c8c881be56f4946aad5a7b925 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -728,6 +728,7 @@ let_it_be(:blocked_user) { create(:user, :blocked) } let_it_be(:ldap_blocked_user) { create(:omniauth_user, :ldap_blocked) } let_it_be(:blocked_pending_approval_user) { create(:user, :blocked_pending_approval) } + let_it_be(:banned_user) { create(:user, :banned) } describe '.blocked' do subject { described_class.blocked } @@ -738,7 +739,7 @@ ldap_blocked_user ) - expect(subject).not_to include(active_user, blocked_pending_approval_user) + expect(subject).not_to include(active_user, blocked_pending_approval_user, banned_user) end end @@ -749,6 +750,14 @@ expect(subject).to contain_exactly(blocked_pending_approval_user) end end + + describe '.banned' do + subject { described_class.banned } + + it 'returns only banned users' do + expect(subject).to contain_exactly(banned_user) + end + end end describe ".with_two_factor" do @@ -1934,6 +1943,12 @@ expect(described_class.filter_items('blocked')).to include user end + it 'filters by banned' do + expect(described_class).to receive(:banned).and_return([user]) + + expect(described_class.filter_items('banned')).to include user + end + it 'filters by blocked pending approval' do expect(described_class).to receive(:blocked_pending_approval).and_return([user]) diff --git a/spec/services/users/ban_service_spec.rb b/spec/services/users/ban_service_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..0e6ac615da5f4046487774376a2c4dc6d2a71cab --- /dev/null +++ b/spec/services/users/ban_service_spec.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Users::BanService do + let(: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 "bans the user" do + expect { operation }.to change { user.state }.to('banned') + end + + it "blocks the user" do + expect { operation }.to change { user.blocked? }.from(false).to(true) + end + + it 'logs ban in application logs' do + allow(Gitlab::AppLogger).to receive(:info) + + operation + + expect(Gitlab::AppLogger).to have_received(:info).with(message: "User banned", user: "#{user.username}", email: "#{user.email}", banned_by: "#{current_user.username}", ip_address: "#{current_user.current_sign_in_ip}") + end + end + + context 'when failed' do + let(:user) { create(:user, :blocked) } + + it 'returns error result' do + aggregate_failures 'error result' do + expect(operation[:status]).to eq(:error) + expect(operation[:message]).to match(/State cannot transition/) + end + end + + it "does not change the user's state" do + expect { operation }.not_to change { user.state } + end + end + end +end