2018-07-16 12:31:01 -04:00
# frozen_string_literal: true
2018-02-05 19:10:58 -05:00
module Groups
class TransferService < Groups :: BaseService
TransferError = Class . new ( StandardError )
2019-10-31 11:06:41 -04:00
attr_reader :error , :new_parent_group
2018-02-05 19:10:58 -05:00
def initialize ( group , user , params = { } )
super
@error = nil
end
def execute ( new_parent_group )
@new_parent_group = new_parent_group
ensure_allowed_transfer
proceed_to_transfer
rescue TransferError , ActiveRecord :: RecordInvalid , Gitlab :: UpdatePathError = > e
@group . errors . clear
2019-04-15 08:25:48 -04:00
@error = s_ ( " TransferGroup|Transfer failed: %{error_message} " ) % { error_message : e . message }
2018-02-05 19:10:58 -05:00
false
end
private
def proceed_to_transfer
Group . transaction do
update_group_attributes
2019-02-25 18:27:16 -05:00
ensure_ownership
2020-12-04 04:09:36 -05:00
update_integrations
2018-02-05 19:10:58 -05:00
end
2019-02-25 18:27:16 -05:00
2019-12-10 16:08:01 -05:00
post_update_hooks ( @updated_project_ids )
2020-12-04 04:09:36 -05:00
propagate_integrations
2019-12-10 16:08:01 -05:00
2019-02-25 18:27:16 -05:00
true
2018-02-05 19:10:58 -05:00
end
2019-12-10 16:08:01 -05:00
# Overridden in EE
def post_update_hooks ( updated_project_ids )
2020-08-05 17:09:40 -04:00
refresh_project_authorizations
2020-10-15 08:09:06 -04:00
refresh_descendant_groups if @new_parent_group
2019-12-10 16:08:01 -05:00
end
2018-02-05 19:10:58 -05:00
def ensure_allowed_transfer
raise_transfer_error ( :group_is_already_root ) if group_is_already_root?
raise_transfer_error ( :same_parent_as_current ) if same_parent?
2021-07-13 11:08:38 -04:00
raise_transfer_error ( :has_subscription ) if has_subscription?
2018-02-05 19:10:58 -05:00
raise_transfer_error ( :invalid_policies ) unless valid_policies?
raise_transfer_error ( :namespace_with_same_path ) if namespace_with_same_path?
2019-10-16 05:07:51 -04:00
raise_transfer_error ( :group_contains_images ) if group_projects_contain_registry_images?
2020-05-22 05:08:09 -04:00
raise_transfer_error ( :cannot_transfer_to_subgroup ) if transfer_to_subgroup?
2020-07-19 23:09:39 -04:00
raise_transfer_error ( :group_contains_npm_packages ) if group_with_npm_packages?
end
def group_with_npm_packages?
return false unless group . packages_feature_enabled?
npm_packages = :: Packages :: GroupPackagesFinder . new ( current_user , group , package_type : :npm ) . execute
2020-08-06 02:09:38 -04:00
different_root_ancestor? && npm_packages . exists?
2020-07-19 23:09:39 -04:00
end
def different_root_ancestor?
group . root_ancestor != new_parent_group & . root_ancestor
2018-02-05 19:10:58 -05:00
end
def group_is_already_root?
! @new_parent_group && ! @group . has_parent?
end
def same_parent?
@new_parent_group && @new_parent_group . id == @group . parent_id
end
2021-07-13 11:08:38 -04:00
def has_subscription?
@group . paid?
end
2020-05-22 05:08:09 -04:00
def transfer_to_subgroup?
@new_parent_group && \
@group . self_and_descendants . pluck_primary_key . include? ( @new_parent_group . id )
end
2018-02-05 19:10:58 -05:00
def valid_policies?
return false unless can? ( current_user , :admin_group , @group )
if @new_parent_group
can? ( current_user , :create_subgroup , @new_parent_group )
else
can? ( current_user , :create_group )
end
end
2018-08-27 11:31:01 -04:00
# rubocop: disable CodeReuse/ActiveRecord
2018-02-05 19:10:58 -05:00
def namespace_with_same_path?
Namespace . exists? ( path : @group . path , parent : @new_parent_group )
end
2018-08-27 11:31:01 -04:00
# rubocop: enable CodeReuse/ActiveRecord
2018-02-05 19:10:58 -05:00
2019-10-16 05:07:51 -04:00
def group_projects_contain_registry_images?
2019-11-08 04:06:07 -05:00
@group . has_container_repository_including_subgroups?
2019-10-16 05:07:51 -04:00
end
2018-02-05 19:10:58 -05:00
def update_group_attributes
if @new_parent_group && @new_parent_group . visibility_level < @group . visibility_level
update_children_and_projects_visibility
@group . visibility_level = @new_parent_group . visibility_level
end
2020-10-15 08:09:06 -04:00
update_two_factor_authentication if @new_parent_group
2018-02-05 19:10:58 -05:00
@group . parent = @new_parent_group
2020-06-04 11:08:21 -04:00
@group . clear_memoization ( :self_and_ancestors_ids )
2021-06-02 11:09:59 -04:00
@group . clear_memoization ( :root_ancestor ) if different_root_ancestor?
2020-09-30 11:09:46 -04:00
inherit_group_shared_runners_settings
2018-02-05 19:10:58 -05:00
@group . save!
2021-06-02 11:09:59 -04:00
# #reload is called to make sure traversal_ids are reloaded
@group . reload # rubocop:disable Cop/ActiveRecordAssociationReload
2018-02-05 19:10:58 -05:00
end
2018-08-27 11:31:01 -04:00
# rubocop: disable CodeReuse/ActiveRecord
2018-02-05 19:10:58 -05:00
def update_children_and_projects_visibility
descendants = @group . descendants . where ( " visibility_level > ? " , @new_parent_group . visibility_level )
Group
. where ( id : descendants . select ( :id ) )
. update_all ( visibility_level : @new_parent_group . visibility_level )
2019-12-10 16:08:01 -05:00
projects_to_update = @group
2018-02-05 19:10:58 -05:00
. all_projects
. where ( " visibility_level > ? " , @new_parent_group . visibility_level )
2019-12-10 16:08:01 -05:00
# Used in post_update_hooks in EE. Must use pluck (and not select)
# here as after we perform the update below we won't be able to find
# these records again.
@updated_project_ids = projects_to_update . pluck ( :id )
projects_to_update
2018-02-05 19:10:58 -05:00
. update_all ( visibility_level : @new_parent_group . visibility_level )
end
2020-10-15 08:09:06 -04:00
def update_two_factor_authentication
return if namespace_parent_allows_two_factor_auth
@group . require_two_factor_authentication = false
end
def refresh_descendant_groups
return if namespace_parent_allows_two_factor_auth
if @group . descendants . where ( require_two_factor_authentication : true ) . any?
DisallowTwoFactorForSubgroupsWorker . perform_async ( @group . id )
end
end
2018-08-27 11:31:01 -04:00
# rubocop: enable CodeReuse/ActiveRecord
2018-02-05 19:10:58 -05:00
2020-10-15 08:09:06 -04:00
def namespace_parent_allows_two_factor_auth
@new_parent_group . namespace_settings . allow_mfa_for_subgroups
end
2019-02-25 18:27:16 -05:00
def ensure_ownership
return if @new_parent_group
return unless @group . owners . empty?
@group . add_owner ( current_user )
end
2020-08-05 17:09:40 -04:00
def refresh_project_authorizations
ProjectAuthorization . where ( project_id : @group . all_projects . select ( :id ) ) . delete_all # rubocop: disable CodeReuse/ActiveRecord
# refresh authorized projects for current_user immediately
current_user . refresh_authorized_projects
# schedule refreshing projects for all the members of the group
@group . refresh_members_authorized_projects
end
2018-02-05 19:10:58 -05:00
def raise_transfer_error ( message )
2020-04-21 11:21:10 -04:00
raise TransferError , localized_error_messages [ message ]
end
def localized_error_messages
{
database_not_supported : s_ ( 'TransferGroup|Database is not supported.' ) ,
namespace_with_same_path : s_ ( 'TransferGroup|The parent group already has a subgroup with the same path.' ) ,
group_is_already_root : s_ ( 'TransferGroup|Group is already a root group.' ) ,
same_parent_as_current : s_ ( 'TransferGroup|Group is already associated to the parent group.' ) ,
invalid_policies : s_ ( " TransferGroup|You don't have enough permissions. " ) ,
2020-05-22 05:08:09 -04:00
group_contains_images : s_ ( 'TransferGroup|Cannot update the path because there are projects under this group that contain Docker images in their Container Registry. Please remove the images from your projects first and try again.' ) ,
2020-07-19 23:09:39 -04:00
cannot_transfer_to_subgroup : s_ ( 'TransferGroup|Cannot transfer group to one of its subgroup.' ) ,
group_contains_npm_packages : s_ ( 'TransferGroup|Group contains projects with NPM packages.' )
2020-04-21 11:21:10 -04:00
} . freeze
2018-02-05 19:10:58 -05:00
end
2020-09-30 11:09:46 -04:00
def inherit_group_shared_runners_settings
parent_setting = @group . parent & . shared_runners_setting
return unless parent_setting
if @group . shared_runners_setting_higher_than? ( parent_setting )
result = Groups :: UpdateSharedRunnersService . new ( @group , current_user , shared_runners_setting : parent_setting ) . execute
raise TransferError , result [ :message ] unless result [ :status ] == :success
end
end
2020-12-04 04:09:36 -05:00
def update_integrations
2021-08-03 05:15:56 -04:00
@group . integrations . with_default_settings . delete_all
2021-05-12 08:10:24 -04:00
Integration . create_from_active_default_integrations ( @group , :group_id )
2020-12-04 04:09:36 -05:00
end
def propagate_integrations
2021-08-03 05:15:56 -04:00
@group . integrations . with_default_settings . each do | integration |
2020-12-04 04:09:36 -05:00
PropagateIntegrationWorker . perform_async ( integration . id )
end
end
2018-02-05 19:10:58 -05:00
end
end
2019-10-31 11:06:41 -04:00
2021-05-11 17:10:21 -04:00
Groups :: TransferService . prepend_mod_with ( 'Groups::TransferService' )