File: destroy_service.rb

package info (click to toggle)
gitlab 17.6.5-19
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 629,368 kB
  • sloc: ruby: 1,915,304; javascript: 557,307; sql: 60,639; xml: 6,509; sh: 4,567; makefile: 1,239; python: 406
file content (130 lines) | stat: -rw-r--r-- 4,374 bytes parent folder | download
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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# frozen_string_literal: true

module Groups
  class DestroyService < Groups::BaseService
    DestroyError = Class.new(StandardError)

    def async_execute
      mark_deleted

      job_id = GroupDestroyWorker.perform_async(group.id, current_user.id)
      Gitlab::AppLogger.info("User #{current_user.id} scheduled a deletion of group ID #{group.id} with job ID #{job_id}")
    end

    # rubocop: disable CodeReuse/ActiveRecord
    def execute
      # TODO - add a policy check here https://gitlab.com/gitlab-org/gitlab/-/issues/353082
      raise DestroyError, "You can't delete this group because you're blocked." if current_user.blocked?

      mark_deleted

      group.projects.includes(:project_feature).each do |project|
        # Execute the destruction of the models immediately to ensure atomic cleanup.
        success = ::Projects::DestroyService.new(project, current_user).execute

        raise DestroyError, "Project #{project.id} can't be deleted" unless success
      end

      # reload the relation to prevent triggering destroy hooks on the projects again
      group.projects.reset

      group.children.each do |group|
        # This needs to be synchronous since the namespace gets destroyed below
        DestroyService.new(group, current_user).execute
      end

      group.chat_team&.remove_mattermost_team(current_user)

      user_ids_for_project_authorizations_refresh = obtain_user_ids_for_project_authorizations_refresh

      destroy_associated_users

      group.destroy

      if user_ids_for_project_authorizations_refresh.present?
        UserProjectAccessChangedService
          .new(user_ids_for_project_authorizations_refresh)
          .execute
      end

      publish_event

      group
    rescue Exception # rubocop:disable Lint/RescueException -- Namespace.transaction can raise Exception
      unmark_deleted
      raise
    end
    # rubocop: enable CodeReuse/ActiveRecord

    private

    def mark_deleted
      group.update_attribute(:deleted_at, Time.current)
    end

    def unmark_deleted
      group.update_attribute(:deleted_at, nil)
    end

    def any_groups_shared_with_this_group?
      group.shared_group_links.any?
    end

    def any_projects_shared_with_this_group?
      group.project_group_links.any?
    end

    # Destroying a group automatically destroys all project authorizations directly
    # associated with the group and descendents. However, project authorizations
    # for projects and groups this group is shared with are not. Without a manual
    # refresh, the project authorization records of these users to shared projects
    # and projects within the shared groups will never be removed, causing
    # inconsistencies with access permissions.
    #
    # This method retrieves the user IDs that need to be refreshed. If only
    # groups are shared with this group, only direct members need to be refreshed.
    # If projects are also shared with the group, direct members *and* shared
    # members of other groups need to be refreshed.
    # `Group#user_ids_for_project_authorizations` returns both direct and shared
    # members' user IDs.
    def obtain_user_ids_for_project_authorizations_refresh
      return unless any_projects_shared_with_this_group? || any_groups_shared_with_this_group?
      return group.user_ids_for_project_authorizations if any_projects_shared_with_this_group?

      group.users_ids_of_direct_members
    end

    def destroy_associated_users
      current_user_id = current_user.id
      bot_ids = users_to_destroy

      group.run_after_commit do
        bot_ids.each do |user_id|
          DeleteUserWorker.perform_async(current_user_id, user_id, skip_authorization: true)
        end
      end
    end

    # rubocop:disable CodeReuse/ActiveRecord
    def users_to_destroy
      group.members_and_requesters.joins(:user)
        .merge(User.project_bot)
        .allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/422405')
        .pluck(:user_id)
    end
    # rubocop:enable CodeReuse/ActiveRecord

    def publish_event
      event = Groups::GroupDeletedEvent.new(
        data: {
          group_id: group.id,
          root_namespace_id: group.root_ancestor.id
        }
      )

      Gitlab::EventStore.publish(event)
    end
  end
end

Groups::DestroyService.prepend_mod_with('Groups::DestroyService')