File: create_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 (194 lines) | stat: -rw-r--r-- 6,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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# frozen_string_literal: true

module Groups
  class CreateService < Groups::BaseService
    def initialize(user, params = {})
      @current_user = user
      @params = params.dup
      @chat_team = @params.delete(:create_chat_team)
    end

    def execute
      build_group
      after_build_hook

      return error_response unless valid?

      @group.name ||= @group.path.dup

      create_chat_team
      create_group

      return error_response unless @group.persisted?

      after_successful_creation_hook

      ServiceResponse.success(payload: { group: @group })
    end

    private

    def valid?
      valid_visibility_level? && valid_user_permissions?
    end

    def error_response
      ServiceResponse.error(message: 'Group has errors', payload: { group: @group })
    end

    def create_chat_team
      return unless valid_to_create_chat_team?

      response = ::Mattermost::CreateTeamService.new(@group, current_user).execute
      return ServiceResponse.error(message: 'Group has errors', payload: { group: @group }) if @group.errors.any?

      @group.build_chat_team(name: response['name'], team_id: response['id'])
    end

    def build_group
      remove_unallowed_params

      set_visibility_level

      except_keys = ::NamespaceSetting.allowed_namespace_settings_params + [:organization_id, :import_export_upload]
      @group = Group.new(params.except(*except_keys))

      set_organization

      @group.import_export_uploads << params[:import_export_upload] if params[:import_export_upload]
      @group.build_namespace_settings
      handle_namespace_settings
    end

    def create_group
      Gitlab::Database::QueryAnalyzers::PreventCrossDatabaseModification.temporary_ignore_tables_in_transaction(
        %w[routes redirect_routes], url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/424281'
      ) do
        Group.transaction do
          if @group.save
            @group.add_owner(current_user)
            @group.add_creator(current_user)
            Integration.create_from_default_integrations(@group, :group_id)
          end
        end
      end
    end

    def after_build_hook
      inherit_group_shared_runners_settings
    end

    def after_successful_creation_hook
      # overridden in EE
    end

    def remove_unallowed_params
      unless can?(current_user, :create_group_with_default_branch_protection)
        params.delete(:default_branch_protection)
        params.delete(:default_branch_protection_defaults)
      end

      params.delete(:allow_mfa_for_subgroups)
      params.delete(:remove_dormant_members)
      params.delete(:remove_dormant_members_period)
      params.delete(:math_rendering_limits_enabled)
      params.delete(:lock_math_rendering_limits_enabled)
    end

    def valid_to_create_chat_team?
      Gitlab.config.mattermost.enabled && @chat_team && @group.chat_team.nil?
    end

    def valid_user_permissions?
      if @group.subgroup?
        unless can?(current_user, :create_subgroup, @group.parent)
          @group.parent = nil
          @group.errors.add(:parent_id, s_('CreateGroup|You don’t have permission to create a subgroup in this group.'))

          return false
        end
      else
        unless can?(current_user, :create_group)
          @group.errors.add(:base, s_('CreateGroup|You don’t have permission to create groups.'))

          return false
        end
      end

      return true if organization_setting_valid?

      # We are unsetting this here to match behavior of invalid parent_id above and protect against possible
      # committing to the database of a value that isn't allowed.
      @group.organization = nil

      false
    end

    def can_create_group_in_organization?
      return true if can?(current_user, :create_group, @group.organization)

      message = s_("CreateGroup|You don't have permission to create a group in the provided organization.")
      @group.errors.add(:organization_id, message)

      false
    end

    def matches_parent_organization?
      return true if @group.parent_id.blank?
      return true if @group.parent.organization_id == @group.organization_id

      message = s_("CreateGroup|You can't create a group in a different organization than the parent group.")
      @group.errors.add(:organization_id, message)

      false
    end

    def organization_setting_valid?
      # we check for the params presence explicitly since:
      # 1. We have a default organization_id at db level set and organization exists and may not have the entry
      #    in organization_users table to allow authorization. This shouldn't be the case longterm as we
      #    plan on populating organization_users correctly.
      # 2. We shouldn't need to check if this is allowed if the user didn't try to set it themselves. i.e.
      #    provided in the params
      return true if params[:organization_id].blank?
      # There is a chance the organization is still blank(if not default organization), but that is the only case
      # where we should allow this to not actually be a record in the database.
      # Otherwise it isn't valid to set this to a non-existent record id and we'll check that in the lines after
      # this code.
      return true if @group.organization.blank? && Organizations::Organization.default?(params[:organization_id])

      can_create_group_in_organization? && matches_parent_organization?
    end

    def valid_visibility_level?
      return true if Gitlab::VisibilityLevel.allowed_for?(current_user, visibility_level)

      deny_visibility_level(@group)

      false
    end

    def set_visibility_level
      return if visibility_level.present?

      params[:visibility_level] = Gitlab::CurrentSettings.current_application_settings.default_group_visibility
    end

    def inherit_group_shared_runners_settings
      return unless @group.parent

      @group.shared_runners_enabled = @group.parent.shared_runners_enabled
      @group.allow_descendants_override_disabled_shared_runners = @group.parent.allow_descendants_override_disabled_shared_runners
    end

    def set_organization
      if params[:organization_id]
        @group.organization_id = params[:organization_id]
      elsif @group.parent_id
        @group.organization = @group.parent.organization
      end
    end
  end
end

Groups::CreateService.prepend_mod_with('Groups::CreateService')