Commit 80bf781a authored by Imre Farkas's avatar Imre Farkas

Merge branch '257886-user-admin-approval-approve-user-via-admin-ui' into 'master'

User admin approval - Approve users pending approval via admin UI

See merge request gitlab-org/gitlab!44877
parents 1ba856f5 f13736a4
...@@ -6,6 +6,7 @@ class Admin::UsersController < Admin::ApplicationController ...@@ -6,6 +6,7 @@ class Admin::UsersController < Admin::ApplicationController
before_action :user, except: [:index, :new, :create] before_action :user, except: [:index, :new, :create]
before_action :check_impersonation_availability, only: :impersonate before_action :check_impersonation_availability, only: :impersonate
before_action :ensure_destroy_prerequisites_met, only: [:destroy] before_action :ensure_destroy_prerequisites_met, only: [:destroy]
before_action :check_admin_approval_feature_available!, only: [:approve]
feature_category :users feature_category :users
...@@ -62,6 +63,16 @@ class Admin::UsersController < Admin::ApplicationController ...@@ -62,6 +63,16 @@ class Admin::UsersController < Admin::ApplicationController
end end
end end
def approve
result = Users::ApproveService.new(current_user).execute(user)
if result[:status] == :success
redirect_back_or_admin_user(notice: _("Successfully approved"))
else
redirect_back_or_admin_user(alert: result[:message])
end
end
def activate def activate
return redirect_back_or_admin_user(notice: _("Error occurred. A blocked user must be unblocked to be activated")) if user.blocked? return redirect_back_or_admin_user(notice: _("Error occurred. A blocked user must be unblocked to be activated")) if user.blocked?
...@@ -82,7 +93,7 @@ class Admin::UsersController < Admin::ApplicationController ...@@ -82,7 +93,7 @@ class Admin::UsersController < Admin::ApplicationController
def block def block
result = Users::BlockService.new(current_user).execute(user) result = Users::BlockService.new(current_user).execute(user)
if result[:status] = :success if result[:status] == :success
redirect_back_or_admin_user(notice: _("Successfully blocked")) redirect_back_or_admin_user(notice: _("Successfully blocked"))
else else
redirect_back_or_admin_user(alert: _("Error occurred. User was not blocked")) redirect_back_or_admin_user(alert: _("Error occurred. User was not blocked"))
...@@ -287,6 +298,10 @@ class Admin::UsersController < Admin::ApplicationController ...@@ -287,6 +298,10 @@ class Admin::UsersController < Admin::ApplicationController
def log_impersonation_event def log_impersonation_event
Gitlab::AppLogger.info(_("User %{current_user_username} has started impersonating %{username}") % { current_user_username: current_user.username, username: user.username }) Gitlab::AppLogger.info(_("User %{current_user_username} has started impersonating %{username}") % { current_user_username: current_user.username, username: user.username })
end end
def check_admin_approval_feature_available!
access_denied! unless Feature.enabled?(:admin_approval_for_new_user_signups)
end
end end
Admin::UsersController.prepend_if_ee('EE::Admin::UsersController') Admin::UsersController.prepend_if_ee('EE::Admin::UsersController')
...@@ -84,7 +84,7 @@ module UsersHelper ...@@ -84,7 +84,7 @@ module UsersHelper
def user_badges_in_admin_section(user) def user_badges_in_admin_section(user)
[].tap do |badges| [].tap do |badges|
badges << { text: s_('AdminUsers|Blocked'), variant: 'danger' } if user.blocked? badges << blocked_user_badge(user) if user.blocked?
badges << { text: s_('AdminUsers|Admin'), variant: 'success' } if user.admin? badges << { text: s_('AdminUsers|Admin'), variant: 'success' } if user.admin?
badges << { text: s_('AdminUsers|External'), variant: 'secondary' } if user.external? badges << { text: s_('AdminUsers|External'), variant: 'secondary' } if user.external?
badges << { text: s_("AdminUsers|It's you!"), variant: nil } if current_user == user badges << { text: s_("AdminUsers|It's you!"), variant: nil } if current_user == user
...@@ -106,8 +106,19 @@ module UsersHelper ...@@ -106,8 +106,19 @@ module UsersHelper
end end
end end
def can_force_email_confirmation?(user)
!user.confirmed?
end
private private
def blocked_user_badge(user)
pending_approval_badge = { text: s_('AdminUsers|Pending approval'), variant: 'info' }
return pending_approval_badge if user.blocked_pending_approval?
{ text: s_('AdminUsers|Blocked'), variant: 'danger' }
end
def get_profile_tabs def get_profile_tabs
tabs = [] tabs = []
......
...@@ -120,6 +120,7 @@ module ApplicationSettingImplementation ...@@ -120,6 +120,7 @@ module ApplicationSettingImplementation
repository_checks_enabled: true, repository_checks_enabled: true,
repository_storages_weighted: { default: 100 }, repository_storages_weighted: { default: 100 },
repository_storages: ['default'], repository_storages: ['default'],
require_admin_approval_after_user_signup: false,
require_two_factor_authentication: false, require_two_factor_authentication: false,
restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'], restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'],
rsa_key_restriction: 0, rsa_key_restriction: 0,
......
...@@ -293,6 +293,7 @@ class User < ApplicationRecord ...@@ -293,6 +293,7 @@ class User < ApplicationRecord
transition active: :blocked transition active: :blocked
transition deactivated: :blocked transition deactivated: :blocked
transition ldap_blocked: :blocked transition ldap_blocked: :blocked
transition blocked_pending_approval: :blocked
end end
event :ldap_block do event :ldap_block do
...@@ -338,7 +339,8 @@ class User < ApplicationRecord ...@@ -338,7 +339,8 @@ class User < ApplicationRecord
# Scopes # Scopes
scope :admins, -> { where(admin: true) } scope :admins, -> { where(admin: true) }
scope :blocked, -> { with_states(:blocked, :ldap_blocked, :blocked_pending_approval) } scope :blocked, -> { with_states(:blocked, :ldap_blocked) }
scope :blocked_pending_approval, -> { with_states(:blocked_pending_approval) }
scope :external, -> { where(external: true) } scope :external, -> { where(external: true) }
scope :confirmed, -> { where.not(confirmed_at: nil) } scope :confirmed, -> { where.not(confirmed_at: nil) }
scope :active, -> { with_state(:active).non_internal } scope :active, -> { with_state(:active).non_internal }
...@@ -538,6 +540,8 @@ class User < ApplicationRecord ...@@ -538,6 +540,8 @@ class User < ApplicationRecord
admins admins
when 'blocked' when 'blocked'
blocked blocked
when 'blocked_pending_approval'
blocked_pending_approval
when 'two_factor_disabled' when 'two_factor_disabled'
without_two_factor without_two_factor
when 'two_factor_enabled' when 'two_factor_enabled'
......
...@@ -98,6 +98,7 @@ class GlobalPolicy < BasePolicy ...@@ -98,6 +98,7 @@ class GlobalPolicy < BasePolicy
rule { admin }.policy do rule { admin }.policy do
enable :read_custom_attribute enable :read_custom_attribute
enable :update_custom_attribute enable :update_custom_attribute
enable :approve_user
end end
# We can't use `read_statistics` because the user may have different permissions for different projects # We can't use `read_statistics` because the user may have different permissions for different projects
......
# frozen_string_literal: true
module Users
class ApproveService < BaseService
def initialize(current_user)
@current_user = current_user
end
def execute(user)
return error(_('You are not allowed to approve a user')) unless allowed?
return error(_('The user you are trying to approve is not pending an approval')) unless approval_required?(user)
if user.activate
# Resends confirmation email if the user isn't confirmed yet.
# Please see Devise's implementation of `resend_confirmation_instructions` for detail.
user.resend_confirmation_instructions
user.accept_pending_invitations! if user.active_for_authentication?
success
else
error(user.errors.full_messages.uniq.join('. '))
end
end
private
attr_reader :current_user
def allowed?
can?(current_user, :approve_user)
end
def approval_required?(user)
user.blocked_pending_approval?
end
end
end
.card.border-info
.card-header.gl-bg-blue-500.gl-text-white
= s_('AdminUsers|This user has requested access')
.card-body
= render partial: 'admin/users/user_approve_effects'
%br
= link_to s_('AdminUsers|Approve user'), approve_admin_user_path(user), method: :put, class: "btn gl-button btn-info", data: { confirm: s_('AdminUsers|Are you sure?') }
.card.border-warning
.card-header.bg-warning.text-white
= s_('AdminUsers|Block this user')
.card-body
= render partial: 'admin/users/user_block_effects'
%br
%button.btn.gl-button.btn-warning{ data: { 'gl-modal-action': 'block',
content: s_('AdminUsers|You can always unblock their account, their data will remain intact.'),
url: block_admin_user_path(user),
username: sanitize_name(user.name) } }
= s_('AdminUsers|Block user')
%h3.page-title %h3.page-title
= @user.name = @user.name
- if @user.blocked? - if @user.blocked_pending_approval?
%span.cred (Blocked) %span.cred
= s_('AdminUsers|(Pending approval)')
- elsif @user.blocked?
%span.cred
= s_('AdminUsers|(Blocked)')
- if @user.internal? - if @user.internal?
%span.cred (Internal) %span.cred
= s_('AdminUsers|(Internal)')
- if @user.admin - if @user.admin
%span.cred (Admin) %span.cred
= s_('AdminUsers|(Admin)')
- if @user.deactivated? - if @user.deactivated?
%span.cred (Deactivated) %span.cred
= s_('AdminUsers|(Deactivated)')
= render_if_exists 'admin/users/audtior_user_badge' = render_if_exists 'admin/users/audtior_user_badge'
.float-right .float-right
......
...@@ -35,15 +35,22 @@ ...@@ -35,15 +35,22 @@
%span.small %span.small
= s_('AdminUsers|Cannot unblock LDAP blocked users') = s_('AdminUsers|Cannot unblock LDAP blocked users')
- elsif user.blocked? - elsif user.blocked?
= link_to _('Unblock'), unblock_admin_user_path(user), method: :put - if user.blocked_pending_approval?
= link_to s_('AdminUsers|Approve'), approve_admin_user_path(user), method: :put
%button.btn.btn-default-tertiary{ data: { 'gl-modal-action': 'block',
url: block_admin_user_path(user),
username: sanitize_name(user.name) } }
= s_('AdminUsers|Block')
- else
= link_to _('Unblock'), unblock_admin_user_path(user), method: :put
- else - else
%button.btn.gl-button.btn-default-tertiary{ data: { 'gl-modal-action': 'block', %button.btn.btn-default-tertiary{ data: { 'gl-modal-action': 'block',
url: block_admin_user_path(user), url: block_admin_user_path(user),
username: sanitize_name(user.name) } } username: sanitize_name(user.name) } }
= s_('AdminUsers|Block') = s_('AdminUsers|Block')
- if user.can_be_deactivated? - if user.can_be_deactivated?
%li %li
%button.btn.gl-button.btn-default-tertiary{ data: { 'gl-modal-action': 'deactivate', %button.btn.btn-default-tertiary{ data: { 'gl-modal-action': 'deactivate',
url: deactivate_admin_user_path(user), url: deactivate_admin_user_path(user),
username: sanitize_name(user.name) } } username: sanitize_name(user.name) } }
= s_('AdminUsers|Deactivate') = s_('AdminUsers|Deactivate')
...@@ -57,13 +64,13 @@ ...@@ -57,13 +64,13 @@
%li.divider %li.divider
- if user.can_be_removed? - if user.can_be_removed?
%li %li
%button.delete-user-button.btn.gl-button.btn-default-tertiary.text-danger{ data: { 'gl-modal-action': 'delete', %button.delete-user-button.btn.btn-default-tertiary.text-danger{ data: { 'gl-modal-action': 'delete',
delete_user_url: admin_user_path(user), delete_user_url: admin_user_path(user),
block_user_url: block_admin_user_path(user), block_user_url: block_admin_user_path(user),
username: sanitize_name(user.name) } } username: sanitize_name(user.name) } }
= s_('AdminUsers|Delete user') = s_('AdminUsers|Delete user')
%li %li
%button.delete-user-button.btn.gl-button.btn-default-tertiary.text-danger{ data: { 'gl-modal-action': 'delete-with-contributions', %button.delete-user-button.btn.btn-default-tertiary.text-danger{ data: { 'gl-modal-action': 'delete-with-contributions',
delete_user_url: admin_user_path(user, hard_delete: true), delete_user_url: admin_user_path(user, hard_delete: true),
block_user_url: block_admin_user_path(user), block_user_url: block_admin_user_path(user),
username: sanitize_name(user.name) } } username: sanitize_name(user.name) } }
......
%p
= s_('AdminUsers|Approved users can:')
%ul
%li
= s_('AdminUsers|Log in')
%li
= s_('AdminUsers|Access Git repositories')
%li
= s_('AdminUsers|Access the API')
%li
= s_('AdminUsers|Be added to groups and projects')
...@@ -30,6 +30,11 @@ ...@@ -30,6 +30,11 @@
= link_to admin_users_path(filter: "blocked") do = link_to admin_users_path(filter: "blocked") do
= s_('AdminUsers|Blocked') = s_('AdminUsers|Blocked')
%small.badge.badge-pill= limited_counter_with_delimiter(User.blocked) %small.badge.badge-pill= limited_counter_with_delimiter(User.blocked)
- if Feature.enabled?(:admin_approval_for_new_user_signups)
= 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") do
= s_('AdminUsers|Pending approval')
%small.badge.badge-pill= limited_counter_with_delimiter(User.blocked_pending_approval)
= nav_link(html_options: { class: active_when(params[:filter] == 'deactivated') }) do = nav_link(html_options: { class: active_when(params[:filter] == 'deactivated') }) do
= link_to admin_users_path(filter: "deactivated") do = link_to admin_users_path(filter: "deactivated") do
= s_('AdminUsers|Deactivated') = s_('AdminUsers|Deactivated')
......
...@@ -137,7 +137,7 @@ ...@@ -137,7 +137,7 @@
.col-md-6 .col-md-6
- unless @user == current_user - unless @user == current_user
- unless @user.confirmed? - if can_force_email_confirmation?(@user)
.card.border-info .card.border-info
.card-header.bg-info.text-white .card-header.bg-info.text-white
Confirm user Confirm user
...@@ -173,28 +173,23 @@ ...@@ -173,28 +173,23 @@
= s_('AdminUsers|Deactivate user') = s_('AdminUsers|Deactivate user')
- if @user.blocked? - if @user.blocked?
.card.border-info - if @user.blocked_pending_approval?
.card-header.bg-info.text-white = render 'admin/users/approve_user', user: @user
This user is blocked = render 'admin/users/block_user', user: @user
.card-body - else
%p A blocked user cannot: .card.border-info
%ul .card-header.gl-bg-blue-500.gl-text-white
%li Log in This user is blocked
%li Access Git repositories .card-body
%br %p A blocked user cannot:
= link_to 'Unblock user', unblock_admin_user_path(@user), method: :put, class: "btn gl-button btn-info", data: { confirm: 'Are you sure?' } %ul
%li Log in
%li Access Git repositories
%br
= link_to 'Unblock user', unblock_admin_user_path(@user), method: :put, class: "btn gl-button btn-info", data: { confirm: s_('AdminUsers|Are you sure?') }
- elsif !@user.internal? - elsif !@user.internal?
.card.border-warning = render 'admin/users/block_user', user: @user
.card-header.bg-warning.text-white
Block this user
.card-body
= render partial: 'admin/users/user_block_effects'
%br
%button.btn.gl-button.btn-warning{ data: { 'gl-modal-action': 'block',
content: 'You can always unblock their account, their data will remain intact.',
url: block_admin_user_path(@user),
username: sanitize_name(@user.name) } }
= s_('AdminUsers|Block user')
- if @user.access_locked? - if @user.access_locked?
.card.border-info .card.border-info
.card-header.bg-info.text-white .card-header.bg-info.text-white
......
...@@ -17,6 +17,7 @@ namespace :admin do ...@@ -17,6 +17,7 @@ namespace :admin do
put :activate put :activate
put :unlock put :unlock
put :confirm put :confirm
put :approve
post :impersonate post :impersonate
patch :disable_two_factor patch :disable_two_factor
delete 'remove/:email_id', action: 'remove_email', as: 'remove_email' delete 'remove/:email_id', action: 'remove_email', as: 'remove_email'
......
...@@ -2042,6 +2042,21 @@ msgstr "" ...@@ -2042,6 +2042,21 @@ msgstr ""
msgid "AdminStatistics|Snippets" msgid "AdminStatistics|Snippets"
msgstr "" msgstr ""
msgid "AdminUsers|(Admin)"
msgstr ""
msgid "AdminUsers|(Blocked)"
msgstr ""
msgid "AdminUsers|(Deactivated)"
msgstr ""
msgid "AdminUsers|(Internal)"
msgstr ""
msgid "AdminUsers|(Pending approval)"
msgstr ""
msgid "AdminUsers|2FA Disabled" msgid "AdminUsers|2FA Disabled"
msgstr "" msgstr ""
...@@ -2051,6 +2066,12 @@ msgstr "" ...@@ -2051,6 +2066,12 @@ msgstr ""
msgid "AdminUsers|Access" msgid "AdminUsers|Access"
msgstr "" msgstr ""
msgid "AdminUsers|Access Git repositories"
msgstr ""
msgid "AdminUsers|Access the API"
msgstr ""
msgid "AdminUsers|Active" msgid "AdminUsers|Active"
msgstr "" msgstr ""
...@@ -2063,12 +2084,30 @@ msgstr "" ...@@ -2063,12 +2084,30 @@ msgstr ""
msgid "AdminUsers|Admins" msgid "AdminUsers|Admins"
msgstr "" msgstr ""
msgid "AdminUsers|Approve"
msgstr ""
msgid "AdminUsers|Approve user"
msgstr ""
msgid "AdminUsers|Approved users can:"
msgstr ""
msgid "AdminUsers|Are you sure?"
msgstr ""
msgid "AdminUsers|Automatically marked as default internal user" msgid "AdminUsers|Automatically marked as default internal user"
msgstr "" msgstr ""
msgid "AdminUsers|Be added to groups and projects"
msgstr ""
msgid "AdminUsers|Block" msgid "AdminUsers|Block"
msgstr "" msgstr ""
msgid "AdminUsers|Block this user"
msgstr ""
msgid "AdminUsers|Block user" msgid "AdminUsers|Block user"
msgstr "" msgstr ""
...@@ -2123,6 +2162,9 @@ msgstr "" ...@@ -2123,6 +2162,9 @@ msgstr ""
msgid "AdminUsers|It's you!" msgid "AdminUsers|It's you!"
msgstr "" msgstr ""
msgid "AdminUsers|Log in"
msgstr ""
msgid "AdminUsers|New user" msgid "AdminUsers|New user"
msgstr "" msgstr ""
...@@ -2132,6 +2174,9 @@ msgstr "" ...@@ -2132,6 +2174,9 @@ msgstr ""
msgid "AdminUsers|Owned groups will be left" msgid "AdminUsers|Owned groups will be left"
msgstr "" msgstr ""
msgid "AdminUsers|Pending approval"
msgstr ""
msgid "AdminUsers|Personal projects will be left" msgid "AdminUsers|Personal projects will be left"
msgstr "" msgstr ""
...@@ -2177,6 +2222,9 @@ msgstr "" ...@@ -2177,6 +2222,9 @@ msgstr ""
msgid "AdminUsers|The user will not receive any notifications" msgid "AdminUsers|The user will not receive any notifications"
msgstr "" msgstr ""
msgid "AdminUsers|This user has requested access"
msgstr ""
msgid "AdminUsers|To confirm, type %{projectName}" msgid "AdminUsers|To confirm, type %{projectName}"
msgstr "" msgstr ""
...@@ -2201,6 +2249,9 @@ msgstr "" ...@@ -2201,6 +2249,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." 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 "" msgstr ""
msgid "AdminUsers|You can always unblock their account, their data will remain intact."
msgstr ""
msgid "AdminUsers|You cannot remove your own admin rights." msgid "AdminUsers|You cannot remove your own admin rights."
msgstr "" msgstr ""
...@@ -25262,6 +25313,9 @@ msgstr "" ...@@ -25262,6 +25313,9 @@ msgstr ""
msgid "Successfully activated" msgid "Successfully activated"
msgstr "" msgstr ""
msgid "Successfully approved"
msgstr ""
msgid "Successfully blocked" msgid "Successfully blocked"
msgstr "" msgstr ""
...@@ -26163,6 +26217,9 @@ msgstr "" ...@@ -26163,6 +26217,9 @@ msgstr ""
msgid "The user map is a mapping of the FogBugz users that participated on your projects to the way their email address and usernames will be imported into GitLab. You can change this by populating the table below." msgid "The user map is a mapping of the FogBugz users that participated on your projects to the way their email address and usernames will be imported into GitLab. You can change this by populating the table below."
msgstr "" msgstr ""
msgid "The user you are trying to approve is not pending an approval"
msgstr ""
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated" msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr "" msgstr ""
...@@ -29713,6 +29770,9 @@ msgstr "" ...@@ -29713,6 +29770,9 @@ msgstr ""
msgid "You are going to turn on the confidentiality. This means that only team members with %{strongStart}at least Reporter access%{strongEnd} are able to see and leave comments on the %{issuableType}." msgid "You are going to turn on the confidentiality. This means that only team members with %{strongStart}at least Reporter access%{strongEnd} are able to see and leave comments on the %{issuableType}."
msgstr "" msgstr ""
msgid "You are not allowed to approve a user"
msgstr ""
msgid "You are not allowed to push into this branch. Create another branch or open a merge request." msgid "You are not allowed to push into this branch. Create another branch or open a merge request."
msgstr "" msgstr ""
......
...@@ -102,6 +102,58 @@ RSpec.describe Admin::UsersController do ...@@ -102,6 +102,58 @@ RSpec.describe Admin::UsersController do
end end
end end
describe 'PUT #approve' do
let(:user) { create(:user, :blocked_pending_approval) }
subject { put :approve, params: { id: user.username } }
context 'when feature is disabled' do
before do
stub_feature_flags(admin_approval_for_new_user_signups: false)
end
it 'responds with access denied' do
subject
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'when feature is enabled' do
before do
stub_feature_flags(admin_approval_for_new_user_signups: true)
end
context 'when successful' do
it 'activates the user' do
subject
user.reload
expect(user).to be_active
expect(flash[:notice]).to eq('Successfully approved')
end
end
context 'when unsuccessful' do
let(:user) { create(:user, :blocked) }
it 'displays the error' do
subject
expect(flash[:alert]).to eq('The user you are trying to approve is not pending an approval')
end
it 'does not activate the user' do
subject
user.reload
expect(user).not_to be_active
end
end
end
end
describe 'PUT #activate' do describe 'PUT #activate' do
shared_examples 'a request that activates the user' do shared_examples 'a request that activates the user' do
it 'activates the user' do it 'activates the user' do
......
...@@ -62,6 +62,43 @@ RSpec.describe "Admin::Users" do ...@@ -62,6 +62,43 @@ RSpec.describe "Admin::Users" do
end end
end end
describe 'tabs' do
it 'has multiple tabs to filter users' do
expect(page).to have_link('Active', href: admin_users_path)
expect(page).to have_link('Admins', href: admin_users_path(filter: 'admins'))
expect(page).to have_link('2FA Enabled', href: admin_users_path(filter: 'two_factor_enabled'))
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('Deactivated', href: admin_users_path(filter: 'deactivated'))
expect(page).to have_link('Without projects', href: admin_users_path(filter: 'wop'))
end
context '`Pending approval` tab' do
context 'feature is enabled' do
before do
stub_feature_flags(admin_approval_for_new_user_signups: true)
visit admin_users_path
end
it 'shows the `Pending approval` tab' do
expect(page).to have_link('Pending approval', href: admin_users_path(filter: 'blocked_pending_approval'))
end
end
context 'feature is disabled' do
before do
stub_feature_flags(admin_approval_for_new_user_signups: false)
visit admin_users_path
end
it 'does not show the `Pending approval` tab' do
expect(page).not_to have_link('Pending approval', href: admin_users_path(filter: 'blocked_pending_approval'))
end
end
end
end
describe 'search and sort' do describe 'search and sort' do
before do before do
create(:user, name: 'Foo Bar', last_activity_on: 3.days.ago) create(:user, name: 'Foo Bar', last_activity_on: 3.days.ago)
...@@ -160,6 +197,27 @@ RSpec.describe "Admin::Users" do ...@@ -160,6 +197,27 @@ RSpec.describe "Admin::Users" do
expect(page).to have_content(user.email) expect(page).to have_content(user.email)
end end
end end
describe 'Pending approval filter' do
it 'counts users who are pending approval' do
create_list(:user, 2, :blocked_pending_approval)
visit admin_users_path
page.within('.filter-blocked-pending-approval small') do
expect(page).to have_content('2')
end
end
it 'filters by users who are pending approval' do
user = create(:user, :blocked_pending_approval)
visit admin_users_path
click_link 'Pending approval'
expect(page).to have_content(user.email)
end
end
end end
describe "GET /admin/users/new" do describe "GET /admin/users/new" do
...@@ -301,6 +359,23 @@ RSpec.describe "Admin::Users" do ...@@ -301,6 +359,23 @@ RSpec.describe "Admin::Users" do
expect(page).to have_button('Delete user and contributions') expect(page).to have_button('Delete user and contributions')
end end
context 'user pending approval' do
it 'shows user info' do
user = create(:user, :blocked_pending_approval)
visit admin_users_path
click_link 'Pending approval'
click_link user.name
expect(page).to have_content(user.name)
expect(page).to have_content('Pending approval')
expect(page).to have_link('Approve user')
expect(page).to have_button('Block user')
expect(page).to have_button('Delete user')
expect(page).to have_button('Delete user and contributions')
end
end
describe 'Impersonation' do describe 'Impersonation' do
let(:another_user) { create(:user) } let(:another_user) { create(:user) }
......
...@@ -126,6 +126,16 @@ RSpec.describe UsersHelper do ...@@ -126,6 +126,16 @@ RSpec.describe UsersHelper do
end end
end end
context 'with a pending approval user' do
it 'returns the pending approval badge' do
blocked_pending_approval_user = create(:user, :blocked_pending_approval)
badges = helper.user_badges_in_admin_section(blocked_pending_approval_user)
expect(filter_ee_badges(badges)).to eq([text: 'Pending approval', variant: 'info'])
end
end
context 'with an admin user' do context 'with an admin user' do
it "returns the admin badge" do it "returns the admin badge" do
admin_user = create(:admin) admin_user = create(:admin)
...@@ -179,6 +189,20 @@ RSpec.describe UsersHelper do ...@@ -179,6 +189,20 @@ RSpec.describe UsersHelper do
end end
end end
describe '#can_force_email_confirmation?' do
subject { helper.can_force_email_confirmation?(user) }
context 'for a user that is already confirmed' do
it { is_expected.to eq(false) }
end
context 'for a user that is not confirmed' do
let(:user) { create(:user, :unconfirmed) }
it { is_expected.to eq(true) }
end
end
describe '#work_information' do describe '#work_information' do
subject { helper.work_information(user) } subject { helper.work_information(user) }
......
...@@ -705,22 +705,31 @@ RSpec.describe User do ...@@ -705,22 +705,31 @@ RSpec.describe User do
end end
describe "scopes" do describe "scopes" do
describe '.blocked' do context 'blocked users' do
subject { described_class.blocked } let_it_be(:active_user) { create(:user) }
let_it_be(:blocked_user) { create(:user, :blocked) }
it 'returns only blocked users' do let_it_be(:ldap_blocked_user) { create(:omniauth_user, :ldap_blocked) }
active_user = create(:user) let_it_be(:blocked_pending_approval_user) { create(:user, :blocked_pending_approval) }
blocked_user = create(:user, :blocked)
blocked_pending_approval_user = create(:user, :blocked_pending_approval) describe '.blocked' do
ldap_blocked_user = create(:omniauth_user, :ldap_blocked) subject { described_class.blocked }
expect(subject).to include( it 'returns only blocked users' do
blocked_user, expect(subject).to include(
blocked_pending_approval_user, blocked_user,
ldap_blocked_user ldap_blocked_user
) )
expect(subject).not_to include(active_user, blocked_pending_approval_user)
end
end
expect(subject).not_to include(active_user) describe '.blocked_pending_approval' do
subject { described_class.blocked_pending_approval }
it 'returns only pending approval users' do
expect(subject).to contain_exactly(blocked_pending_approval_user)
end
end end
end end
...@@ -1752,6 +1761,12 @@ RSpec.describe User do ...@@ -1752,6 +1761,12 @@ RSpec.describe User do
expect(described_class.filter_items('blocked')).to include user expect(described_class.filter_items('blocked')).to include user
end end
it 'filters by blocked pending approval' do
expect(described_class).to receive(:blocked_pending_approval).and_return([user])
expect(described_class.filter_items('blocked_pending_approval')).to include user
end
it 'filters by deactivated' do it 'filters by deactivated' do
expect(described_class).to receive(:deactivated).and_return([user]) expect(described_class).to receive(:deactivated).and_return([user])
......
...@@ -130,6 +130,24 @@ RSpec.describe GlobalPolicy do ...@@ -130,6 +130,24 @@ RSpec.describe GlobalPolicy do
end end
end end
describe 'approving users' do
context 'regular user' do
it { is_expected.not_to be_allowed(:approve_user) }
end
context 'admin' do
let(:current_user) { create(:admin) }
context 'when admin mode is enabled', :enable_admin_mode do
it { is_expected.to be_allowed(:approve_user) }
end
context 'when admin mode is disabled' do
it { is_expected.to be_disallowed(:approve_user) }
end
end
end
describe 'using project statistics filters' do describe 'using project statistics filters' do
context 'regular user' do context 'regular user' do
it { is_expected.not_to be_allowed(:use_project_statistics_filters) } it { is_expected.not_to be_allowed(:use_project_statistics_filters) }
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Users::ApproveService do
let_it_be(:current_user) { create(:admin) }
let(:user) { create(:user, :blocked_pending_approval) }
subject(:execute) { described_class.new(current_user).execute(user) }
describe '#execute' do
context 'failures' do
context 'when the executor user is not allowed to approve users' do
let(:current_user) { create(:user) }
it 'returns error result' do
expect(subject[:status]).to eq(:error)
expect(subject[:message]).to match(/You are not allowed to approve a user/)
end
end
context 'when user is not in pending approval state' do
let(:user) { create(:user, state: 'active') }
it 'returns error result' do
expect(subject[:status]).to eq(:error)
expect(subject[:message])
.to match(/The user you are trying to approve is not pending an approval/)
end
end
context 'when user cannot be activated' do
let(:user) do
build(:user, state: 'blocked_pending_approval', email: 'invalid email')
end
it 'returns error result' do
expect(subject[:status]).to eq(:error)
expect(subject[:message]).to match(/Email is invalid/)
end
it 'does not change the state of the user' do
expect { subject }.not_to change { user.state }
end
end
end
context 'success' do
it 'activates the user' do
expect(subject[:status]).to eq(:success)
expect(user.reload).to be_active
end
context 'email confirmation status' do
context 'user is unconfirmed' do
let(:user) { create(:user, :blocked_pending_approval, :unconfirmed) }
it 'sends confirmation instructions' do
expect { subject }
.to have_enqueued_mail(DeviseMailer, :confirmation_instructions)
end
end
context 'user is confirmed' do
it 'does not send a confirmation email' do
expect { subject }
.not_to have_enqueued_mail(DeviseMailer, :confirmation_instructions)
end
end
end
context 'pending invitiations' do
let!(:project_member_invite) { create(:project_member, :invited, invite_email: user.email) }
let!(:group_member_invite) { create(:group_member, :invited, invite_email: user.email) }
context 'user is unconfirmed' do
let(:user) { create(:user, :blocked_pending_approval, :unconfirmed) }
it 'does not accept pending invites of the user' do
expect(subject[:status]).to eq(:success)
group_member_invite.reload
project_member_invite.reload
expect(group_member_invite).to be_invite
expect(project_member_invite).to be_invite
end
end
context 'user is confirmed' do
it 'accepts pending invites of the user' do
expect(subject[:status]).to eq(:success)
group_member_invite.reload
project_member_invite.reload
expect(group_member_invite).not_to be_invite
expect(project_member_invite).not_to be_invite
expect(group_member_invite.user).to eq(user)
expect(project_member_invite.user).to eq(user)
end
end
end
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment