2018-11-18 11:00:15 +05:30
# frozen_string_literal: true
2018-03-17 18:26:18 +05:30
module Groups
class TransferService < Groups :: BaseService
TransferError = Class . new ( StandardError )
2019-12-26 22:10:19 +05:30
attr_reader :error , :new_parent_group
2018-03-17 18:26:18 +05:30
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-07-31 22:56:46 +05:30
@error = s_ ( " TransferGroup|Transfer failed: %{error_message} " ) % { error_message : e . message }
2018-03-17 18:26:18 +05:30
false
end
private
def proceed_to_transfer
2022-06-21 17:19:12 +05:30
old_root_ancestor_id = @group . root_ancestor . id
was_root_group = @group . root?
2018-03-17 18:26:18 +05:30
Group . transaction do
update_group_attributes
2019-07-07 11:18:12 +05:30
ensure_ownership
2021-02-22 17:27:13 +05:30
update_integrations
2022-06-21 17:19:12 +05:30
remove_issue_contacts ( old_root_ancestor_id , was_root_group )
update_crm_objects ( was_root_group )
2018-03-17 18:26:18 +05:30
end
2019-07-07 11:18:12 +05:30
2019-12-20 00:11:08 +05:30
post_update_hooks ( @updated_project_ids )
2021-02-22 17:27:13 +05:30
propagate_integrations
2022-01-26 12:08:38 +05:30
update_pending_builds
2019-12-20 00:11:08 +05:30
2019-07-07 11:18:12 +05:30
true
2018-03-17 18:26:18 +05:30
end
2019-12-20 00:11:08 +05:30
# Overridden in EE
def post_update_hooks ( updated_project_ids )
2020-08-18 19:51:02 +05:30
refresh_project_authorizations
2021-01-03 14:25:43 +05:30
refresh_descendant_groups if @new_parent_group
2019-12-20 00:11:08 +05:30
end
2018-03-17 18:26:18 +05:30
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-09-30 23:02:18 +05:30
raise_transfer_error ( :has_subscription ) if has_subscription?
2018-03-17 18:26:18 +05:30
raise_transfer_error ( :invalid_policies ) unless valid_policies?
raise_transfer_error ( :namespace_with_same_path ) if namespace_with_same_path?
2019-12-21 20:55:43 +05:30
raise_transfer_error ( :group_contains_images ) if group_projects_contain_registry_images?
2020-06-23 00:09:42 +05:30
raise_transfer_error ( :cannot_transfer_to_subgroup ) if transfer_to_subgroup?
2020-10-24 23:57:45 +05:30
raise_transfer_error ( :group_contains_npm_packages ) if group_with_npm_packages?
2022-06-21 17:19:12 +05:30
raise_transfer_error ( :no_permissions_to_migrate_crm ) if no_permissions_to_migrate_crm?
end
def no_permissions_to_migrate_crm?
return false unless group && @new_parent_group
return false if group . root_ancestor == @new_parent_group . root_ancestor
return true if group . contacts . exists? && ! current_user . can? ( :admin_crm_contact , @new_parent_group . root_ancestor )
return true if group . organizations . exists? && ! current_user . can? ( :admin_crm_organization , @new_parent_group . root_ancestor )
false
2020-10-24 23:57:45 +05:30
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
different_root_ancestor? && npm_packages . exists?
end
def different_root_ancestor?
group . root_ancestor != new_parent_group & . root_ancestor
2018-03-17 18:26:18 +05:30
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-09-30 23:02:18 +05:30
def has_subscription?
@group . paid?
end
2020-06-23 00:09:42 +05:30
def transfer_to_subgroup?
@new_parent_group && \
@group . self_and_descendants . pluck_primary_key . include? ( @new_parent_group . id )
end
2018-03-17 18:26:18 +05:30
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-12-05 23:21:45 +05:30
# rubocop: disable CodeReuse/ActiveRecord
2018-03-17 18:26:18 +05:30
def namespace_with_same_path?
Namespace . exists? ( path : @group . path , parent : @new_parent_group )
end
2018-12-05 23:21:45 +05:30
# rubocop: enable CodeReuse/ActiveRecord
2018-03-17 18:26:18 +05:30
2019-12-21 20:55:43 +05:30
def group_projects_contain_registry_images?
2019-12-26 22:10:19 +05:30
@group . has_container_repository_including_subgroups?
2019-12-21 20:55:43 +05:30
end
2018-03-17 18:26:18 +05:30
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
2021-01-03 14:25:43 +05:30
update_two_factor_authentication if @new_parent_group
2018-03-17 18:26:18 +05:30
@group . parent = @new_parent_group
2020-06-23 00:09:42 +05:30
@group . clear_memoization ( :self_and_ancestors_ids )
2021-09-04 01:27:46 +05:30
@group . clear_memoization ( :root_ancestor ) if different_root_ancestor?
2021-01-03 14:25:43 +05:30
inherit_group_shared_runners_settings
2018-03-17 18:26:18 +05:30
@group . save!
2021-09-04 01:27:46 +05:30
# #reload is called to make sure traversal_ids are reloaded
@group . reload # rubocop:disable Cop/ActiveRecordAssociationReload
2018-03-17 18:26:18 +05:30
end
2018-12-05 23:21:45 +05:30
# rubocop: disable CodeReuse/ActiveRecord
2018-03-17 18:26:18 +05:30
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-20 00:11:08 +05:30
projects_to_update = @group
2018-03-17 18:26:18 +05:30
. all_projects
. where ( " visibility_level > ? " , @new_parent_group . visibility_level )
2019-12-20 00:11:08 +05:30
# 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 )
2021-11-18 22:05:49 +05:30
Namespaces :: ProjectNamespace
. where ( id : projects_to_update . select ( :project_namespace_id ) )
. update_all ( visibility_level : @new_parent_group . visibility_level )
2019-12-20 00:11:08 +05:30
projects_to_update
2018-03-17 18:26:18 +05:30
. update_all ( visibility_level : @new_parent_group . visibility_level )
end
2021-01-03 14:25:43 +05:30
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-12-05 23:21:45 +05:30
# rubocop: enable CodeReuse/ActiveRecord
2018-03-17 18:26:18 +05:30
2021-01-03 14:25:43 +05:30
def namespace_parent_allows_two_factor_auth
@new_parent_group . namespace_settings . allow_mfa_for_subgroups
end
2019-07-07 11:18:12 +05:30
def ensure_ownership
return if @new_parent_group
return unless @group . owners . empty?
@group . add_owner ( current_user )
end
2020-08-18 19:51:02 +05:30
def refresh_project_authorizations
2021-12-11 22:18:48 +05:30
projects_to_update = Set . new
2020-08-18 19:51:02 +05:30
2021-12-11 22:18:48 +05:30
# All projects in this hierarchy need to have their project authorizations recalculated
@group . all_projects . each_batch { | prjs | projects_to_update . merge ( prjs . ids ) } # rubocop: disable CodeReuse/ActiveRecord
2021-10-29 20:43:33 +05:30
# When a group is transferred, it also affects who gets access to the projects shared to
# the subgroups within its hierarchy, so we also schedule jobs that refresh authorizations for all such shared projects.
2021-12-11 22:18:48 +05:30
ProjectGroupLink . in_group ( @group . self_and_descendants . select ( :id ) ) . each_batch do | project_group_links |
projects_to_update . merge ( project_group_links . pluck ( :project_id ) ) # rubocop: disable CodeReuse/ActiveRecord
2021-10-29 20:43:33 +05:30
end
2021-12-11 22:18:48 +05:30
AuthorizedProjectUpdate :: ProjectAccessChangedService . new ( projects_to_update . to_a ) . execute unless projects_to_update . empty?
2020-08-18 19:51:02 +05:30
end
2018-03-17 18:26:18 +05:30
def raise_transfer_error ( message )
2020-04-22 19:07:51 +05:30
raise TransferError , localized_error_messages [ message ]
end
def localized_error_messages
{
database_not_supported : s_ ( 'TransferGroup|Database is not supported.' ) ,
2021-12-11 22:18:48 +05:30
namespace_with_same_path : s_ ( 'TransferGroup|The parent group already has a subgroup or a project with the same path.' ) ,
2020-04-22 19:07:51 +05:30
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-06-23 00:09:42 +05:30
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-10-24 23:57:45 +05:30
cannot_transfer_to_subgroup : s_ ( 'TransferGroup|Cannot transfer group to one of its subgroup.' ) ,
2022-06-21 17:19:12 +05:30
group_contains_npm_packages : s_ ( 'TransferGroup|Group contains projects with NPM packages.' ) ,
no_permissions_to_migrate_crm : s_ ( " TransferGroup|Group contains contacts/organizations and you don't have enough permissions to move them to the new root group. " )
2020-04-22 19:07:51 +05:30
} . freeze
2018-03-17 18:26:18 +05:30
end
2021-01-03 14:25:43 +05:30
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
2021-02-22 17:27:13 +05:30
def update_integrations
2021-10-27 15:23:28 +05:30
@group . integrations . with_default_settings . delete_all
2021-06-08 01:23:25 +05:30
Integration . create_from_active_default_integrations ( @group , :group_id )
2021-02-22 17:27:13 +05:30
end
def propagate_integrations
2021-10-27 15:23:28 +05:30
@group . integrations . with_default_settings . each do | integration |
2021-02-22 17:27:13 +05:30
PropagateIntegrationWorker . perform_async ( integration . id )
end
end
2021-11-18 22:05:49 +05:30
2022-01-26 12:08:38 +05:30
def update_pending_builds
:: Ci :: PendingBuilds :: UpdateGroupWorker . perform_async ( group . id , pending_builds_params )
end
def pending_builds_params
{
2021-11-18 22:05:49 +05:30
namespace_traversal_ids : group . traversal_ids ,
namespace_id : group . id
}
end
2022-06-21 17:19:12 +05:30
def update_crm_objects ( was_root_group )
return unless was_root_group
CustomerRelations :: Contact . move_to_root_group ( group )
CustomerRelations :: Organization . move_to_root_group ( group )
end
def remove_issue_contacts ( old_root_ancestor_id , was_root_group )
return if was_root_group
return if old_root_ancestor_id == @group . root_ancestor . id
CustomerRelations :: IssueContact . delete_for_group ( @group )
end
2018-03-17 18:26:18 +05:30
end
end
2019-12-20 00:11:08 +05:30
2021-06-08 01:23:25 +05:30
Groups :: TransferService . prepend_mod_with ( 'Groups::TransferService' )