summaryrefslogtreecommitdiff
path: root/app/services/groups/transfer_service.rb
blob: fe7e07ef9f0856646531d6a234ef535bde8c80b2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# frozen_string_literal: true

module Groups
  class TransferService < Groups::BaseService
    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.")
    }.freeze

    TransferError = Class.new(StandardError)

    attr_reader :error

    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
      @error = s_("TransferGroup|Transfer failed: %{error_message}") % { error_message: e.message }
      false
    end

    private

    def proceed_to_transfer
      Group.transaction do
        update_group_attributes
        ensure_ownership
      end

      true
    end

    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?
      raise_transfer_error(:invalid_policies) unless valid_policies?
      raise_transfer_error(:namespace_with_same_path) if namespace_with_same_path?
    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

    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

    # rubocop: disable CodeReuse/ActiveRecord
    def namespace_with_same_path?
      Namespace.exists?(path: @group.path, parent: @new_parent_group)
    end
    # rubocop: enable CodeReuse/ActiveRecord

    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

      @group.parent = @new_parent_group
      @group.save!
    end

    # rubocop: disable CodeReuse/ActiveRecord
    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)

      @group
        .all_projects
        .where("visibility_level > ?", @new_parent_group.visibility_level)
        .update_all(visibility_level: @new_parent_group.visibility_level)
    end
    # rubocop: enable CodeReuse/ActiveRecord

    def ensure_ownership
      return if @new_parent_group
      return unless @group.owners.empty?

      @group.add_owner(current_user)
    end

    def raise_transfer_error(message)
      raise TransferError, ERROR_MESSAGES[message]
    end
  end
end