1# frozen_string_literal: true 2 3class Namespace < ApplicationRecord 4 include CacheMarkdownField 5 include Sortable 6 include Gitlab::VisibilityLevel 7 include Routable 8 include AfterCommitQueue 9 include Storage::LegacyNamespace 10 include Gitlab::SQL::Pattern 11 include FeatureGate 12 include FromUnion 13 include Gitlab::Utils::StrongMemoize 14 include IgnorableColumns 15 include Namespaces::Traversal::Recursive 16 include Namespaces::Traversal::Linear 17 include EachBatch 18 19 # Temporary column used for back-filling project namespaces. 20 # Remove it once the back-filling of all project namespaces is done. 21 ignore_column :tmp_project_id, remove_with: '14.7', remove_after: '2022-01-22' 22 23 # Tells ActiveRecord not to store the full class name, in order to save some space 24 # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/69794 25 self.store_full_sti_class = false 26 self.store_full_class_name = false 27 28 # Prevent users from creating unreasonably deep level of nesting. 29 # The number 20 was taken based on maximum nesting level of 30 # Android repo (15) + some extra backup. 31 NUMBER_OF_ANCESTORS_ALLOWED = 20 32 33 SR_DISABLED_AND_UNOVERRIDABLE = 'disabled_and_unoverridable' 34 SR_DISABLED_WITH_OVERRIDE = 'disabled_with_override' 35 SR_ENABLED = 'enabled' 36 SHARED_RUNNERS_SETTINGS = [SR_DISABLED_AND_UNOVERRIDABLE, SR_DISABLED_WITH_OVERRIDE, SR_ENABLED].freeze 37 URL_MAX_LENGTH = 255 38 39 PATH_TRAILING_VIOLATIONS = %w[.git .atom .].freeze 40 41 cache_markdown_field :description, pipeline: :description 42 43 has_many :projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent 44 has_many :project_statistics 45 has_one :namespace_settings, inverse_of: :namespace, class_name: 'NamespaceSetting', autosave: true 46 47 has_many :runner_namespaces, inverse_of: :namespace, class_name: 'Ci::RunnerNamespace' 48 has_many :runners, through: :runner_namespaces, source: :runner, class_name: 'Ci::Runner' 49 has_many :pending_builds, class_name: 'Ci::PendingBuild' 50 has_one :onboarding_progress 51 52 # This should _not_ be `inverse_of: :namespace`, because that would also set 53 # `user.namespace` when this user creates a group with themselves as `owner`. 54 belongs_to :owner, class_name: 'User' 55 56 belongs_to :parent, class_name: "Namespace" 57 has_many :children, -> { where(type: Group.sti_name) }, class_name: "Namespace", foreign_key: :parent_id 58 has_many :custom_emoji, inverse_of: :namespace 59 has_one :chat_team, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent 60 has_one :root_storage_statistics, class_name: 'Namespace::RootStorageStatistics' 61 has_one :aggregation_schedule, class_name: 'Namespace::AggregationSchedule' 62 has_one :package_setting_relation, inverse_of: :namespace, class_name: 'PackageSetting' 63 64 has_one :admin_note, inverse_of: :namespace 65 accepts_nested_attributes_for :admin_note, update_only: true 66 67 has_one :ci_namespace_mirror, class_name: 'Ci::NamespaceMirror' 68 has_many :sync_events, class_name: 'Namespaces::SyncEvent' 69 70 validates :owner, presence: true, if: ->(n) { n.owner_required? } 71 validates :name, 72 presence: true, 73 length: { maximum: 255 } 74 75 validates :description, length: { maximum: 255 } 76 77 validates :path, 78 presence: true, 79 length: { maximum: URL_MAX_LENGTH } 80 81 validates :path, namespace_path: true, if: ->(n) { !n.project_namespace? } 82 # Project path validator is used for project namespaces for now to assure 83 # compatibility with project paths 84 # Issue: https://gitlab.com/gitlab-org/gitlab/-/issues/341764 85 validates :path, project_path: true, if: ->(n) { n.project_namespace? } 86 87 # Introduce minimal path length of 2 characters. 88 # Allow change of other attributes without forcing users to 89 # rename their user or group. At the same time prevent changing 90 # the path without complying with new 2 chars requirement. 91 # Issue https://gitlab.com/gitlab-org/gitlab/-/issues/225214 92 # 93 # For ProjectNamespace we don't check minimal path length to keep 94 # compatibility with existing project restrictions. 95 # Issue: https://gitlab.com/gitlab-org/gitlab/-/issues/341764 96 validates :path, length: { minimum: 2 }, if: :enforce_minimum_path_length? 97 98 validates :max_artifacts_size, numericality: { only_integer: true, greater_than: 0, allow_nil: true } 99 100 validate :validate_parent_type 101 102 # ProjectNamespaces excluded as they are not meant to appear in the group hierarchy at the moment. 103 validate :nesting_level_allowed, unless: -> { project_namespace? } 104 validate :changing_shared_runners_enabled_is_allowed, unless: -> { project_namespace? } 105 validate :changing_allow_descendants_override_disabled_shared_runners_is_allowed, unless: -> { project_namespace? } 106 107 delegate :name, to: :owner, allow_nil: true, prefix: true 108 delegate :avatar_url, to: :owner, allow_nil: true 109 110 after_save :schedule_sync_event_worker, if: -> { saved_change_to_id? || saved_change_to_parent_id? } 111 112 after_commit :refresh_access_of_projects_invited_groups, on: :update, if: -> { previous_changes.key?('share_with_group_lock') } 113 114 before_create :sync_share_with_group_lock_with_parent 115 before_update :sync_share_with_group_lock_with_parent, if: :parent_changed? 116 after_update :force_share_with_group_lock_on_descendants, if: -> { saved_change_to_share_with_group_lock? && share_with_group_lock? } 117 118 # Legacy Storage specific hooks 119 120 after_update :move_dir, if: :saved_change_to_path_or_parent?, unless: -> { is_a?(Namespaces::ProjectNamespace) } 121 before_destroy(prepend: true) { prepare_for_destroy } 122 after_destroy :rm_dir 123 after_commit :expire_child_caches, on: :update, if: -> { 124 Feature.enabled?(:cached_route_lookups, self, type: :ops, default_enabled: :yaml) && 125 saved_change_to_name? || saved_change_to_path? || saved_change_to_parent_id? 126 } 127 128 scope :user_namespaces, -> { where(type: Namespaces::UserNamespace.sti_name) } 129 scope :without_project_namespaces, -> { where(Namespace.arel_table[:type].not_eq(Namespaces::ProjectNamespace.sti_name)) } 130 scope :sort_by_type, -> { order(Gitlab::Database.nulls_first_order(:type)) } 131 scope :include_route, -> { includes(:route) } 132 scope :by_parent, -> (parent) { where(parent_id: parent) } 133 scope :filter_by_path, -> (query) { where('lower(path) = :query', query: query.downcase) } 134 135 scope :with_statistics, -> do 136 joins('LEFT JOIN project_statistics ps ON ps.namespace_id = namespaces.id') 137 .group('namespaces.id') 138 .select( 139 'namespaces.*', 140 'COALESCE(SUM(ps.storage_size), 0) AS storage_size', 141 'COALESCE(SUM(ps.repository_size), 0) AS repository_size', 142 'COALESCE(SUM(ps.wiki_size), 0) AS wiki_size', 143 'COALESCE(SUM(ps.snippets_size), 0) AS snippets_size', 144 'COALESCE(SUM(ps.lfs_objects_size), 0) AS lfs_objects_size', 145 'COALESCE(SUM(ps.build_artifacts_size), 0) AS build_artifacts_size', 146 'COALESCE(SUM(ps.pipeline_artifacts_size), 0) AS pipeline_artifacts_size', 147 'COALESCE(SUM(ps.packages_size), 0) AS packages_size', 148 'COALESCE(SUM(ps.uploads_size), 0) AS uploads_size' 149 ) 150 end 151 152 scope :sorted_by_similarity_and_parent_id_desc, -> (search) do 153 order_expression = Gitlab::Database::SimilarityScore.build_expression(search: search, rules: [ 154 { column: arel_table["path"], multiplier: 1 }, 155 { column: arel_table["name"], multiplier: 0.7 } 156 ]) 157 reorder(order_expression.desc, Namespace.arel_table['parent_id'].desc.nulls_last, Namespace.arel_table['id'].desc) 158 end 159 160 # Make sure that the name is same as strong_memoize name in root_ancestor 161 # method 162 attr_writer :root_ancestor, :emails_disabled_memoized 163 164 class << self 165 def sti_class_for(type_name) 166 case type_name 167 when Group.sti_name 168 Group 169 when Namespaces::ProjectNamespace.sti_name 170 Namespaces::ProjectNamespace 171 when Namespaces::UserNamespace.sti_name 172 Namespaces::UserNamespace 173 else 174 Namespace 175 end 176 end 177 178 def by_path(path) 179 find_by('lower(path) = :value', value: path.downcase) 180 end 181 182 # Case insensitive search for namespace by path or name 183 def find_by_path_or_name(path) 184 find_by("lower(path) = :path OR lower(name) = :path", path: path.downcase) 185 end 186 187 # Searches for namespaces matching the given query. 188 # 189 # This method uses ILIKE on PostgreSQL. 190 # 191 # query - The search query as a String. 192 # 193 # Returns an ActiveRecord::Relation. 194 def search(query, include_parents: false) 195 if include_parents 196 without_project_namespaces.where(id: Route.for_routable_type(Namespace.name).fuzzy_search(query, [Route.arel_table[:path], Route.arel_table[:name]]).select(:source_id)) 197 else 198 without_project_namespaces.fuzzy_search(query, [:path, :name]) 199 end 200 end 201 202 def clean_path(path) 203 path = path.dup 204 # Get the email username by removing everything after an `@` sign. 205 path.gsub!(/@.*\z/, "") 206 # Remove everything that's not in the list of allowed characters. 207 path.gsub!(/[^a-zA-Z0-9_\-\.]/, "") 208 # Remove trailing violations ('.atom', '.git', or '.') 209 loop do 210 orig = path 211 PATH_TRAILING_VIOLATIONS.each { |ext| path = path.chomp(ext) } 212 break if orig == path 213 end 214 215 # Remove leading violations ('-') 216 path.gsub!(/\A\-+/, "") 217 218 # Users with the great usernames of "." or ".." would end up with a blank username. 219 # Work around that by setting their username to "blank", followed by a counter. 220 path = "blank" if path.blank? 221 222 uniquify = Uniquify.new 223 uniquify.string(path) { |s| Namespace.find_by_path_or_name(s) } 224 end 225 226 def clean_name(value) 227 value.scan(Gitlab::Regex.group_name_regex_chars).join(' ') 228 end 229 230 def find_by_pages_host(host) 231 gitlab_host = "." + Settings.pages.host.downcase 232 host = host.downcase 233 return unless host.ends_with?(gitlab_host) 234 235 name = host.delete_suffix(gitlab_host) 236 Namespace.where(parent_id: nil).by_path(name) 237 end 238 239 def top_most 240 where(parent_id: nil) 241 end 242 end 243 244 def package_settings 245 package_setting_relation || build_package_setting_relation 246 end 247 248 def default_branch_protection 249 super || Gitlab::CurrentSettings.default_branch_protection 250 end 251 252 def visibility_level_field 253 :visibility_level 254 end 255 256 def to_param 257 full_path 258 end 259 260 def human_name 261 owner_name 262 end 263 264 def any_project_has_container_registry_tags? 265 all_projects.includes(:container_repositories).any?(&:has_container_registry_tags?) 266 end 267 268 def first_project_with_container_registry_tags 269 all_projects.find(&:has_container_registry_tags?) 270 end 271 272 def send_update_instructions 273 projects.each do |project| 274 project.send_move_instructions("#{full_path_before_last_save}/#{project.path}") 275 end 276 end 277 278 def kind 279 return 'group' if group_namespace? 280 return 'project' if project_namespace? 281 282 'user' # defaults to user 283 end 284 285 def group_namespace? 286 type == Group.sti_name 287 end 288 289 def project_namespace? 290 type == Namespaces::ProjectNamespace.sti_name 291 end 292 293 def user_namespace? 294 # That last bit ensures we're considered a user namespace as a default 295 type.nil? || type == Namespaces::UserNamespace.sti_name || !(group_namespace? || project_namespace?) 296 end 297 298 def owner_required? 299 user_namespace? 300 end 301 302 def find_fork_of(project) 303 return unless project.fork_network 304 305 if Gitlab::SafeRequestStore.active? 306 forks_in_namespace = Gitlab::SafeRequestStore.fetch("namespaces:#{id}:forked_projects") do 307 Hash.new do |found_forks, project| 308 found_forks[project] = project.fork_network.find_forks_in(projects).first 309 end 310 end 311 312 forks_in_namespace[project] 313 else 314 project.fork_network.find_forks_in(projects).first 315 end 316 end 317 318 # any ancestor can disable emails for all descendants 319 def emails_disabled? 320 strong_memoize(:emails_disabled_memoized) do 321 if parent_id 322 self_and_ancestors.where(emails_disabled: true).exists? 323 else 324 !!emails_disabled 325 end 326 end 327 end 328 329 def lfs_enabled? 330 # User namespace will always default to the global setting 331 Gitlab.config.lfs.enabled 332 end 333 334 def any_project_with_shared_runners_enabled? 335 projects.with_shared_runners.any? 336 end 337 338 def user_ids_for_project_authorizations 339 [owner_id] 340 end 341 342 # Includes projects from this namespace and projects from all subgroups 343 # that belongs to this namespace 344 def all_projects 345 if Feature.enabled?(:recursive_approach_for_all_projects, default_enabled: :yaml) 346 namespace = user_namespace? ? self : self_and_descendant_ids 347 Project.where(namespace: namespace) 348 else 349 Project.inside_path(full_path) 350 end 351 end 352 353 def has_parent? 354 parent_id.present? || parent.present? 355 end 356 357 def subgroup? 358 has_parent? 359 end 360 361 # Overridden on EE module 362 def multiple_issue_boards_available? 363 false 364 end 365 366 # Deprecated, use #licensed_feature_available? instead. Remove once Namespace#feature_available? isn't used anymore. 367 def feature_available?(feature) 368 licensed_feature_available?(feature) 369 end 370 371 # Overridden in EE::Namespace 372 def licensed_feature_available?(_feature) 373 false 374 end 375 376 def full_path_before_last_save 377 if parent_id_before_last_save.nil? 378 path_before_last_save 379 else 380 previous_parent = Group.find_by(id: parent_id_before_last_save) 381 previous_parent.full_path + '/' + path_before_last_save 382 end 383 end 384 385 def refresh_project_authorizations 386 owner.refresh_authorized_projects 387 end 388 389 def auto_devops_enabled? 390 first_auto_devops_config[:status] 391 end 392 393 def first_auto_devops_config 394 return { scope: :group, status: auto_devops_enabled } unless auto_devops_enabled.nil? 395 396 strong_memoize(:first_auto_devops_config) do 397 if has_parent? 398 parent.first_auto_devops_config 399 else 400 { scope: :instance, status: Gitlab::CurrentSettings.auto_devops_enabled? } 401 end 402 end 403 end 404 405 def aggregation_scheduled? 406 aggregation_schedule.present? 407 end 408 409 def pages_virtual_domain 410 Pages::VirtualDomain.new( 411 all_projects_with_pages.includes(:route, :project_feature, pages_metadatum: :pages_deployment), 412 trim_prefix: full_path 413 ) 414 end 415 416 def any_project_with_pages_deployed? 417 all_projects.with_pages_deployed.any? 418 end 419 420 def closest_setting(name) 421 self_and_ancestors(hierarchy_order: :asc) 422 .find { |n| !n.read_attribute(name).nil? } 423 .try(name) 424 end 425 426 def actual_plan 427 Plan.default 428 end 429 430 def paid? 431 root? && actual_plan.paid? 432 end 433 434 def actual_limits 435 # We default to PlanLimits.new otherwise a lot of specs would fail 436 # On production each plan should already have associated limits record 437 # https://gitlab.com/gitlab-org/gitlab/issues/36037 438 actual_plan.actual_limits 439 end 440 441 def actual_plan_name 442 actual_plan.name 443 end 444 445 def changing_shared_runners_enabled_is_allowed 446 return unless new_record? || changes.has_key?(:shared_runners_enabled) 447 448 if shared_runners_enabled && has_parent? && parent.shared_runners_setting == SR_DISABLED_AND_UNOVERRIDABLE 449 errors.add(:shared_runners_enabled, _('cannot be enabled because parent group has shared Runners disabled')) 450 end 451 end 452 453 def changing_allow_descendants_override_disabled_shared_runners_is_allowed 454 return unless new_record? || changes.has_key?(:allow_descendants_override_disabled_shared_runners) 455 456 if shared_runners_enabled && !new_record? 457 errors.add(:allow_descendants_override_disabled_shared_runners, _('cannot be changed if shared runners are enabled')) 458 end 459 460 if allow_descendants_override_disabled_shared_runners && has_parent? && parent.shared_runners_setting == SR_DISABLED_AND_UNOVERRIDABLE 461 errors.add(:allow_descendants_override_disabled_shared_runners, _('cannot be enabled because parent group does not allow it')) 462 end 463 end 464 465 def shared_runners_setting 466 if shared_runners_enabled 467 SR_ENABLED 468 else 469 if allow_descendants_override_disabled_shared_runners 470 SR_DISABLED_WITH_OVERRIDE 471 else 472 SR_DISABLED_AND_UNOVERRIDABLE 473 end 474 end 475 end 476 477 def shared_runners_setting_higher_than?(other_setting) 478 if other_setting == SR_ENABLED 479 false 480 elsif other_setting == SR_DISABLED_WITH_OVERRIDE 481 shared_runners_setting == SR_ENABLED 482 elsif other_setting == SR_DISABLED_AND_UNOVERRIDABLE 483 shared_runners_setting == SR_ENABLED || shared_runners_setting == SR_DISABLED_WITH_OVERRIDE 484 else 485 raise ArgumentError 486 end 487 end 488 489 def root? 490 !has_parent? 491 end 492 493 def recent? 494 created_at >= 90.days.ago 495 end 496 497 def issue_repositioning_disabled? 498 Feature.enabled?(:block_issue_repositioning, self, type: :ops, default_enabled: :yaml) 499 end 500 501 def project_namespace_creation_enabled? 502 Feature.enabled?(:create_project_namespace_on_project_create, self, default_enabled: :yaml) 503 end 504 505 private 506 507 def expire_child_caches 508 Namespace.where(id: descendants).each_batch do |namespaces| 509 namespaces.touch_all 510 end 511 512 all_projects.each_batch do |projects| 513 projects.touch_all 514 end 515 end 516 517 def all_projects_with_pages 518 all_projects.with_pages_deployed 519 end 520 521 def parent_changed? 522 parent_id_changed? 523 end 524 525 def saved_change_to_parent? 526 saved_change_to_parent_id? 527 end 528 529 def saved_change_to_path_or_parent? 530 saved_change_to_path? || saved_change_to_parent_id? 531 end 532 533 def refresh_access_of_projects_invited_groups 534 if Feature.enabled?(:specialized_worker_for_group_lock_update_auth_recalculation) 535 Project 536 .where(namespace_id: id) 537 .joins(:project_group_links) 538 .distinct 539 .find_each do |project| 540 AuthorizedProjectUpdate::ProjectRecalculateWorker.perform_async(project.id) 541 end 542 543 # Until we compare the inconsistency rates of the new specialized worker and 544 # the old approach, we still run AuthorizedProjectsWorker 545 # but with some delay and lower urgency as a safety net. 546 enqueue_jobs_for_groups_requiring_authorizations_refresh(priority: UserProjectAccessChangedService::LOW_PRIORITY) 547 else 548 enqueue_jobs_for_groups_requiring_authorizations_refresh(priority: UserProjectAccessChangedService::HIGH_PRIORITY) 549 end 550 end 551 552 def enqueue_jobs_for_groups_requiring_authorizations_refresh(priority:) 553 groups_requiring_authorizations_refresh = Group 554 .joins(project_group_links: :project) 555 .where(projects: { namespace_id: id }) 556 .distinct 557 558 groups_requiring_authorizations_refresh.find_each do |group| 559 group.refresh_members_authorized_projects( 560 blocking: false, 561 priority: priority 562 ) 563 end 564 end 565 566 def nesting_level_allowed 567 if ancestors.count > Group::NUMBER_OF_ANCESTORS_ALLOWED 568 errors.add(:parent_id, _('has too deep level of nesting')) 569 end 570 end 571 572 def validate_parent_type 573 unless has_parent? 574 if project_namespace? 575 errors.add(:parent_id, _('must be set for a project namespace')) 576 end 577 578 return 579 end 580 581 if parent.project_namespace? 582 errors.add(:parent_id, _('project namespace cannot be the parent of another namespace')) 583 end 584 585 if user_namespace? 586 errors.add(:parent_id, _('cannot be used for user namespace')) 587 elsif group_namespace? 588 errors.add(:parent_id, _('user namespace cannot be the parent of another namespace')) if parent.user_namespace? 589 end 590 end 591 592 def sync_share_with_group_lock_with_parent 593 if parent&.share_with_group_lock? 594 self.share_with_group_lock = true 595 end 596 end 597 598 def force_share_with_group_lock_on_descendants 599 # We can't use `descendants.update_all` since Rails will throw away the WITH 600 # RECURSIVE statement. We also can't use WHERE EXISTS since we can't use 601 # different table aliases, hence we're just using WHERE IN. Since we have a 602 # maximum of 20 nested groups this should be fine. 603 Namespace.where(id: descendants.select(:id)) 604 .update_all(share_with_group_lock: true) 605 end 606 607 def write_projects_repository_config 608 all_projects.find_each do |project| 609 project.set_full_path 610 project.track_project_repository 611 end 612 end 613 614 def enforce_minimum_path_length? 615 path_changed? && !project_namespace? 616 end 617 618 # SyncEvents are created by PG triggers (with the function `insert_namespaces_sync_event`) 619 def schedule_sync_event_worker 620 run_after_commit do 621 Namespaces::SyncEvent.enqueue_worker 622 end 623 end 624end 625 626Namespace.prepend_mod_with('Namespace') 627