Class: Group

Defined Under Namespace

Classes: CrmSettings

Constant Summary collapse

README_PROJECT_PATH =
'gitlab-profile'

Constants included from WithUploads

WithUploads::FILE_UPLOADERS

Constants included from Avatarable

Avatarable::ALLOWED_IMAGE_SCALER_WIDTHS, Avatarable::GROUP_AVATAR_SIZES, Avatarable::MAXIMUM_FILE_SIZE, Avatarable::PROJECT_AVATAR_SIZES, Avatarable::USER_AVATAR_SIZES

Constants inherited from Namespace

Namespace::NUMBER_OF_ANCESTORS_ALLOWED, Namespace::SHARED_RUNNERS_SETTINGS, Namespace::SR_DISABLED_AND_OVERRIDABLE, Namespace::SR_DISABLED_AND_UNOVERRIDABLE, Namespace::SR_DISABLED_WITH_OVERRIDE, Namespace::SR_ENABLED, Namespace::URL_MAX_LENGTH

Constants included from BlocksUnsafeSerialization

BlocksUnsafeSerialization::UnsafeSerializationError

Constants included from Namespaces::Traversal::Linear

Namespaces::Traversal::Linear::UnboundedSearch

Constants included from Gitlab::SQL::Pattern

Gitlab::SQL::Pattern::MIN_CHARS_FOR_PARTIAL_MATCHING, Gitlab::SQL::Pattern::REGEX_QUOTED_TERM

Constants included from Gitlab::VisibilityLevel

Gitlab::VisibilityLevel::INTERNAL, Gitlab::VisibilityLevel::PRIVATE, Gitlab::VisibilityLevel::PUBLIC

Constants included from CacheMarkdownField

CacheMarkdownField::INVALIDATED_BY

Constants inherited from ApplicationRecord

ApplicationRecord::MAX_PLUCK

Constants included from ResetOnUnionError

ResetOnUnionError::MAX_RESET_PERIOD

Instance Attribute Summary

Attributes inherited from Namespace

#emails_disabled_memoized, #root_ancestor

Attributes included from CacheMarkdownField

#skip_markdown_cache_validation

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Gitlab::Utils::Override

extended, extensions, included, method_added, override, prepended, queue_verification, verify!

Methods included from RunnerTokenExpirationInterval

#effective_runner_token_expiration_interval, #effective_runner_token_expiration_interval_human_readable, #enforced_runner_token_expiration_interval_human_readable

Methods included from ChronicDurationAttribute

#chronic_duration_attributes, #output_chronic_duration_attribute

Methods included from GroupAPICompatibility

#project_creation_level_str, #project_creation_level_str=, #subgroup_creation_level_str, #subgroup_creation_level_str=

Methods included from WithUploads

#retrieve_upload

Methods included from FastDestroyAll::Helpers

#perform_fast_destroy

Methods included from AfterCommitQueue

#run_after_commit, #run_after_commit_or_now

Methods included from GroupDescendant

build_hierarchy, #hierarchy

Methods included from LoadedInGroupList

#children_count, #guest_count, #member_count, #project_count, #subgroup_count

Methods included from Avatarable

#avatar_path, #avatar_type, #uncached_avatar_path, #upload_paths

Methods included from AccessRequestable

#request_access

Methods included from Gitlab::ConfigHelper

#gitlab_config, #gitlab_config_features

Methods inherited from Namespace

#actual_limits, #actual_plan, #actual_plan_name, #aggregation_scheduled?, #all_ancestors_have_runner_registration_enabled?, #all_container_repositories, #all_project_ids, #all_project_ids_except, #all_projects, #all_projects_except_soft_deleted, #all_projects_with_pages, #allow_runner_registration_token?, #any_project_has_container_registry_tags?, #any_project_with_pages_deployed?, #any_project_with_shared_runners_enabled?, #auto_devops_enabled?, #bot_user_namespace?, by_path, #certificate_based_clusters_enabled?, #changing_allow_descendants_override_disabled_shared_runners_is_allowed, #changing_shared_runners_enabled_is_allowed, clean_name, clean_path, #closest_setting, #container_repositories_size, #container_repositories_size_cache_key, #default_branch_protection, #emails_disabled?, #emails_enabled?, #enabled_git_access_protocol, find_by_path_or_name, #find_fork_of, #first_auto_devops_config, #first_project_with_container_registry_tags, #full_path_before_last_save, #group_namespace?, #has_parent?, #issue_repositioning_disabled?, #kind, #licensed_feature_available?, #multiple_issue_boards_available?, #owner_required?, #package_settings, #paid?, #prevent_delete?, #project_namespace?, #recent?, reference_pattern, reference_prefix, #root?, search, #send_update_instructions, #shared_runners, #shared_runners_setting, #shared_runners_setting_higher_than?, sti_class_for, #subgroup?, #to_param, #to_reference, #to_reference_base, top_most, #user_namespace?, #visibility_level_field

Methods included from Referable

#referable_inspect, #reference_link_text, #to_reference, #to_reference_base

Methods included from Ci::NamespaceSettings

#allow_stale_runner_pruning=, #allow_stale_runner_pruning?

Methods included from BlocksUnsafeSerialization

#serializable_hash

Methods included from Namespaces::Traversal::Linear

#ancestor_ids, #ancestors, #ancestors_upto, #descendants, #parent=, #parent_id=, #root_ancestor, #self_and_ancestor_ids, #self_and_ancestors, #self_and_descendant_ids, #self_and_descendants, #self_and_hierarchy, #traversal_ids, #traversal_ids=, #use_traversal_ids?

Methods included from Namespaces::Traversal::Recursive

#ancestor_ids, #ancestors, #ancestors_upto, #descendants, #object_hierarchy, #root_ancestor, #self_and_ancestor_ids, #self_and_ancestors, #self_and_descendant_ids, #self_and_descendants, #self_and_hierarchy

Methods included from FeatureGate

#flipper_id

Methods included from Gitlab::SQL::Pattern

split_query_to_search_terms

Methods included from Storage::LegacyNamespace

#move_dir, #prepare_for_destroy

Methods included from Gitlab::ShellAdapter

#gitlab_shell

Methods included from Routable

#build_full_path, find_by_full_path, #full_name, #full_path, #full_path_components, optimize_routable_enabled?, #parent_loaded?, #route_loaded?

Methods included from Gitlab::VisibilityLevel

allowed_for?, allowed_level?, allowed_levels, closest_allowed_level, #internal?, level_name, level_value, levels_for_user, non_restricted_level?, options, #private?, #public?, public_visibility_restricted?, restricted_level?, string_level, string_options, string_values, valid_level?, #visibility, #visibility=, #visibility_attribute_present?, #visibility_attribute_value, #visibility_level_attributes, #visibility_level_previous_changes, #visibility_level_value

Methods included from CacheMarkdownField

#attribute_invalidated?, #banzai_render_context, #cached_html_for, #cached_html_up_to_date?, #can_cache_field?, #invalidated_markdown_cache?, #latest_cached_markdown_version, #local_version, #mentionable_attributes_changed?, #mentioned_filtered_user_ids_for, #parent_user, #refresh_markdown_cache, #refresh_markdown_cache!, #rendered_field_content, #skip_project_check?, #store_mentions!, #updated_cached_html_for

Methods inherited from ApplicationRecord

cached_column_list, #create_or_load_association, declarative_enum, default_select_columns, id_in, id_not_in, iid_in, pluck_primary_key, primary_key_in, #readable_by?, safe_ensure_unique, safe_find_or_create_by, safe_find_or_create_by!, #to_ability_name, underscore, where_exists, where_not_exists, with_fast_read_statement_timeout, without_order

Methods included from SensitiveSerializableHash

#serializable_hash

Class Method Details

.get_ids_by_ids_or_paths(ids, paths) ⇒ Object



323
324
325
# File 'app/models/group.rb', line 323

def get_ids_by_ids_or_paths(ids, paths)
  by_ids_or_paths(ids, paths).pluck(:id)
end

.ids_with_disabled_email(groups) ⇒ Object

Returns the ids of the passed group models where the ‘emails_disabled` column is set to true anywhere in the ancestor hierarchy.



305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
# File 'app/models/group.rb', line 305

def ids_with_disabled_email(groups)
  inner_groups = Group.where('id = namespaces_with_emails_disabled.id')

  inner_query = inner_groups
    .self_and_ancestors
    .where(emails_disabled: true)
    .select('1')
    .limit(1)

  group_ids = Namespace
    .from('(SELECT * FROM namespaces) as namespaces_with_emails_disabled')
    .where(namespaces_with_emails_disabled: { id: groups })
    .where('EXISTS (?)', inner_query)
    .pluck(:id)

  Set.new(group_ids)
end

.preset_root_ancestor_for(groups) ⇒ Object

This method can be used only if all groups have the same top-level group



296
297
298
299
300
301
# File 'app/models/group.rb', line 296

def preset_root_ancestor_for(groups)
  return groups if groups.size < 2

  root = groups.first.root_ancestor
  groups.drop(1).each { |group| group.root_ancestor = root }
end

.public_or_visible_to_user(user) ⇒ Object

WARNING: This method should never be used on its own please do make sure the number of rows you are filtering is small enough for this query



265
266
267
268
269
270
271
272
273
# File 'app/models/group.rb', line 265

def public_or_visible_to_user(user)
  return public_to_user unless user

  public_for_user = public_to_user_arel(user)
  visible_for_user = visible_to_user_arel(user)
  public_or_visible = public_for_user.or(visible_for_user)

  where(public_or_visible)
end

.select_for_project_authorizationObject



275
276
277
278
279
280
281
282
283
# File 'app/models/group.rb', line 275

def select_for_project_authorization
  if current_scope.joins_values.include?(:shared_projects)
    joins('INNER JOIN namespaces project_namespace ON project_namespace.id = projects.namespace_id')
      .where(project_namespace: { share_with_group_lock: false })
      .select("projects.id AS project_id", "LEAST(project_group_links.group_access, members.access_level) AS access_level")
  else
    super
  end
end

.sort_by_attribute(method) ⇒ Object



252
253
254
255
256
257
258
259
260
# File 'app/models/group.rb', line 252

def sort_by_attribute(method)
  if method == 'storage_size_desc'
    # storage_size is a virtual column so we need to
    # pass a string to avoid AR adding the table name
    reorder('storage_size DESC, namespaces.id DESC')
  else
    order_by(method)
  end
end

.sti_nameObject



28
29
30
# File 'app/models/group.rb', line 28

def self.sti_name
  'Group'
end

.without_integration(integration) ⇒ Object



285
286
287
288
289
290
291
292
# File 'app/models/group.rb', line 285

def without_integration(integration)
  integrations = Integration
    .select('1')
    .where("#{Integration.table_name}.group_id = namespaces.id")
    .where(type: integration.type)

  where('NOT EXISTS (?)', integrations)
end

Instance Method Details

#access_level_rolesObject



811
812
813
# File 'app/models/group.rb', line 811

def access_level_roles
  GroupMember.access_level_roles
end

#access_level_valuesObject



815
816
817
# File 'app/models/group.rb', line 815

def access_level_values
  access_level_roles.values
end

#access_request_approvers_to_be_notifiedObject



755
756
757
# File 'app/models/group.rb', line 755

def access_request_approvers_to_be_notified
  members.owners.connected_to_user..limit(Member::ACCESS_REQUEST_APPROVERS_TO_BE_NOTIFIED_LIMIT)
end

#activity_pathObject



831
832
833
# File 'app/models/group.rb', line 831

def activity_path
  Gitlab::Routing.url_helpers.activity_group_path(self)
end

#add_developer(user, current_user = nil) ⇒ Object



457
458
459
# File 'app/models/group.rb', line 457

def add_developer(user, current_user = nil)
  add_member(user, :developer, current_user: current_user)
end

#add_guest(user, current_user = nil) ⇒ Object



449
450
451
# File 'app/models/group.rb', line 449

def add_guest(user, current_user = nil)
  add_member(user, :guest, current_user: current_user)
end

#add_maintainer(user, current_user = nil) ⇒ Object



461
462
463
# File 'app/models/group.rb', line 461

def add_maintainer(user, current_user = nil)
  add_member(user, :maintainer, current_user: current_user)
end

#add_member(user, access_level, current_user: nil, expires_at: nil, ldap: false) ⇒ Object



438
439
440
441
442
443
444
445
446
447
# File 'app/models/group.rb', line 438

def add_member(user, access_level, current_user: nil, expires_at: nil, ldap: false)
  Members::Groups::CreatorService.add_member( # rubocop:disable CodeReuse/ServiceClass
    self,
    user,
    access_level,
    current_user: current_user,
    expires_at: expires_at,
    ldap: ldap
  )
end

#add_members(users, access_level, current_user: nil, expires_at: nil, tasks_to_be_done: [], tasks_project_id: nil) ⇒ Object



426
427
428
429
430
431
432
433
434
435
436
# File 'app/models/group.rb', line 426

def add_members(users, access_level, current_user: nil, expires_at: nil, tasks_to_be_done: [], tasks_project_id: nil)
  Members::Groups::CreatorService.add_members( # rubocop:disable CodeReuse/ServiceClass
    self,
    users,
    access_level,
    current_user: current_user,
    expires_at: expires_at,
    tasks_to_be_done: tasks_to_be_done,
    tasks_project_id: tasks_project_id
  )
end

#add_owner(user, current_user = nil) ⇒ Object



465
466
467
# File 'app/models/group.rb', line 465

def add_owner(user, current_user = nil)
  add_member(user, :owner, current_user: current_user)
end

#add_reporter(user, current_user = nil) ⇒ Object



453
454
455
# File 'app/models/group.rb', line 453

def add_reporter(user, current_user = nil)
  add_member(user, :reporter, current_user: current_user)
end

#adjourned_deletion?Boolean

Returns:

  • (Boolean)


779
780
781
# File 'app/models/group.rb', line 779

def adjourned_deletion?
  false
end

#authorizable_members_with_parentsObject



591
592
593
594
595
596
597
598
599
600
601
602
603
# File 'app/models/group.rb', line 591

def authorizable_members_with_parents
  source_ids =
    if has_parent?
      self_and_ancestors.reorder(nil).select(:id)
    else
      id
    end

  group_hierarchy_members = GroupMember.where(source_id: source_ids).select(*GroupMember.cached_column_list)

  GroupMember.from_union([group_hierarchy_members,
                          members_from_self_and_ancestor_group_shares]).authorizable
end

#blocked_ownersObject



481
482
483
# File 'app/models/group.rb', line 481

def blocked_owners
  members.blocked.where(access_level: Gitlab::Access::OWNER)
end

#botsObject



722
723
724
# File 'app/models/group.rb', line 722

def bots
  users.project_bot
end

#content_editor_on_issues_feature_flag_enabled?Boolean

Returns:

  • (Boolean)


882
883
884
# File 'app/models/group.rb', line 882

def content_editor_on_issues_feature_flag_enabled?
  feature_flag_enabled_for_self_or_ancestor?(:content_editor_on_issues)
end

#crm_enabled?Boolean

Returns:

  • (Boolean)


863
864
865
# File 'app/models/group.rb', line 863

def crm_enabled?
  crm_settings&.enabled?
end

#default_branch_nameObject



807
808
809
# File 'app/models/group.rb', line 807

def default_branch_name
  namespace_settings&.default_branch_name
end

#dependency_proxy_feature_available?Boolean

Returns:

  • (Boolean)


368
369
370
# File 'app/models/group.rb', line 368

def dependency_proxy_feature_available?
  ::Gitlab.config.dependency_proxy.enabled
end

#dependency_proxy_image_prefixObject



382
383
384
385
386
387
388
389
# File 'app/models/group.rb', line 382

def dependency_proxy_image_prefix
  # The namespace path can include uppercase letters, which
  # Docker doesn't allow. The proxy expects it to be downcased.
  url = "#{Gitlab::Routing.url_helpers.group_url(self).downcase}#{DependencyProxy::URL_SUFFIX}"

  # Docker images do not include the protocol
  url.partition('//').last
end

#dependency_proxy_image_ttl_policyObject



851
852
853
# File 'app/models/group.rb', line 851

def dependency_proxy_image_ttl_policy
  super || build_dependency_proxy_image_ttl_policy
end

#dependency_proxy_settingObject



855
856
857
# File 'app/models/group.rb', line 855

def dependency_proxy_setting
  super || build_dependency_proxy_setting
end

#descendant_project_members_with_inactiveObject



653
654
655
656
657
658
# File 'app/models/group.rb', line 653

def descendant_project_members_with_inactive
  ProjectMember
    .with_source_id(all_projects)
    .non_request
    .non_invite
end

#direct_membersObject



585
586
587
588
589
# File 'app/models/group.rb', line 585

def direct_members
  GroupMember.active_without_invites_and_requests
             .non_minimal_access
             .where(source_id: id)
end

#enforced_runner_token_expiration_intervalObject



871
872
873
874
875
876
877
878
879
880
# File 'app/models/group.rb', line 871

def enforced_runner_token_expiration_interval
  all_parent_groups = Gitlab::ObjectHierarchy.new(Group.where(id: id)).ancestors
  all_group_settings = NamespaceSetting.where(namespace_id: all_parent_groups)
  group_interval = all_group_settings.where.not(subgroup_runner_token_expiration_interval: nil).minimum(:subgroup_runner_token_expiration_interval)&.seconds

  [
    Gitlab::CurrentSettings.group_runner_token_expiration_interval&.seconds,
    group_interval
  ].compact.min
end

#execute_hooks(data, hooks_scope) ⇒ Object



783
784
785
786
# File 'app/models/group.rb', line 783

def execute_hooks(data, hooks_scope)
  # NOOP
  # TODO: group hooks https://gitlab.com/gitlab-org/gitlab/-/issues/216904
end

#execute_integrations(data, hooks_scope) ⇒ Object



788
789
790
791
792
# File 'app/models/group.rb', line 788

def execute_integrations(data, hooks_scope)
  integrations.public_send(hooks_scope).each do |integration| # rubocop:disable GitlabSecurity/PublicSend
    integration.async_execute(data)
  end
end

#export_archive_exists?Boolean

Returns:

  • (Boolean)


775
776
777
# File 'app/models/group.rb', line 775

def export_archive_exists?
  import_export_upload&.export_archive_exists?
end

#export_fileObject



771
772
773
# File 'app/models/group.rb', line 771

def export_file
  import_export_upload&.export_file
end

#export_file_exists?Boolean

Returns:

  • (Boolean)


767
768
769
# File 'app/models/group.rb', line 767

def export_file_exists?
  import_export_upload&.export_file_exists?
end

#feature_available?(feature, user = nil) ⇒ Boolean

Returns:

  • (Boolean)


913
914
915
916
917
918
919
# File 'app/models/group.rb', line 913

def feature_available?(feature, user = nil)
  if ::Groups::FeatureSetting.available_features.include?(feature)
    group_feature.feature_available?(feature, user) # rubocop:disable Gitlab/FeatureAvailableUsage
  else
    super
  end
end

#first_ownerObject



801
802
803
804
805
# File 'app/models/group.rb', line 801

def first_owner
  first_owner_member = all_group_members.all_owners.order(:user_id).first

  first_owner_member&.user || parent&.first_owner || owner
end

#gitlab_deploy_tokenObject



921
922
923
924
925
# File 'app/models/group.rb', line 921

def gitlab_deploy_token
  strong_memoize(:gitlab_deploy_token) do
    deploy_tokens.gitlab_deploy_token
  end
end

#group_featureObject



859
860
861
# File 'app/models/group.rb', line 859

def group_feature
  super || build_group_feature
end

#group_readmeObject



940
941
942
# File 'app/models/group.rb', line 940

def group_readme
  readme_project&.repository&.readme
end

#has_container_repository_including_subgroups?Boolean

Returns:

  • (Boolean)


491
492
493
# File 'app/models/group.rb', line 491

def has_container_repository_including_subgroups?
  ::ContainerRepository.for_group_and_its_subgroups(self).exists?
end

#has_maintainer?(user) ⇒ Boolean

Returns:

  • (Boolean)


485
486
487
488
489
# File 'app/models/group.rb', line 485

def has_maintainer?(user)
  return false unless user

  members_with_parents.maintainers.exists?(user_id: user)
end

#has_owner?(user) ⇒ Boolean

Returns:

  • (Boolean)


475
476
477
478
479
# File 'app/models/group.rb', line 475

def has_owner?(user)
  return false unless user

  members_with_parents.all_owners.exists?(user_id: user)
end

#has_project_with_service_desk_enabled?Boolean

Returns:

  • (Boolean)


826
827
828
# File 'app/models/group.rb', line 826

def has_project_with_service_desk_enabled?
  Gitlab::ServiceDesk.supported? && all_projects.service_desk_enabled.exists?
end

#hashed_storage?(_feature) ⇒ Boolean

Returns:

  • (Boolean)


732
733
734
# File 'app/models/group.rb', line 732

def hashed_storage?(_feature)
  false
end

#hierarchy_membersObject

Returns all members that are part of the group, it’s subgroups, and ancestor groups



640
641
642
643
644
# File 'app/models/group.rb', line 640

def hierarchy_members
  GroupMember
    .active_without_invites_and_requests
    .where(source_id: self_and_hierarchy.reorder(nil).select(:id))
end

#hierarchy_members_with_inactiveObject



646
647
648
649
650
651
# File 'app/models/group.rb', line 646

def hierarchy_members_with_inactive
  GroupMember
    .non_request
    .non_invite
    .where(source_id: self_and_hierarchy.reorder(nil).select(:id))
end

#highest_group_member(user) ⇒ Object



718
719
720
# File 'app/models/group.rb', line 718

def highest_group_member(user)
  GroupMember.where(source_id: self_and_ancestors_ids, user_id: user.id).order(:access_level).last
end

#human_nameObject



391
392
393
# File 'app/models/group.rb', line 391

def human_name
  full_name
end

#last_owner?(user) ⇒ Boolean

Check if user is a last owner of the group. Excludes non-direct owners for top-level group Excludes project_bots

Returns:

  • (Boolean)


498
499
500
501
502
503
504
# File 'app/models/group.rb', line 498

def last_owner?(user)
  return false unless user

  all_owners = member_owners_excluding_project_bots

  all_owners.size == 1 && all_owners.first.user_id == user.id
end

#ldap_synced?Boolean

Returns:

  • (Boolean)


520
521
522
# File 'app/models/group.rb', line 520

def ldap_synced?
  false
end

#lfs_enabled?Boolean

Returns:

  • (Boolean)


415
416
417
418
419
420
# File 'app/models/group.rb', line 415

def lfs_enabled?
  return false unless Gitlab.config.lfs.enabled
  return Gitlab.config.lfs.enabled if self[:lfs_enabled].nil?

  self[:lfs_enabled]
end

#linked_work_items_feature_flag_enabled?Boolean

Returns:

  • (Boolean)


898
899
900
# File 'app/models/group.rb', line 898

def linked_work_items_feature_flag_enabled?
  feature_flag_enabled_for_self_or_ancestor?(:linked_work_items)
end

#mattermost_team_paramsObject



700
701
702
703
704
705
706
707
708
# File 'app/models/group.rb', line 700

def mattermost_team_params
  max_length = 59

  {
    name: path[0..max_length],
    display_name: name[0..max_length],
    type: public? ? 'O' : 'I' # Open vs Invite-only
  }
end

#max_member_access_for_user(user, only_concrete_membership: false) ⇒ Object

Return the highest access level for a user

A special case is handled here when the user is a GitLab admin which implies it has “OWNER” access everywhere, but should not officially appear as a member of a group unless specifically added to it

Parameters:

  • user (User)
  • only_concrete_membership (Bool) (defaults to: false)

    whether require admin concrete membership status



693
694
695
696
697
698
# File 'app/models/group.rb', line 693

def max_member_access_for_user(user, only_concrete_membership: false)
  return GroupMember::NO_ACCESS unless user
  return GroupMember::OWNER if user.can_admin_all_resources? && !only_concrete_membership

  max_member_access([user.id])[user.id]
end

#member(user) ⇒ Object



710
711
712
713
714
715
716
# File 'app/models/group.rb', line 710

def member(user)
  if group_members.loaded?
    group_members.find { |gm| gm.user_id == user.id }
  else
    group_members.find_by(user_id: user)
  end
end

#member?(user, min_access_level = Gitlab::Access::GUEST) ⇒ Boolean

Returns:

  • (Boolean)


469
470
471
472
473
# File 'app/models/group.rb', line 469

def member?(user, min_access_level = Gitlab::Access::GUEST)
  return false unless user

  max_member_access_for_user(user) >= min_access_level
end

#member_owners_excluding_project_botsObject

Excludes non-direct owners for top-level group Excludes project_bots



508
509
510
511
512
513
514
515
516
517
518
# File 'app/models/group.rb', line 508

def member_owners_excluding_project_bots
  members_from_hiearchy = if root?
                            members.non_minimal_access.without_invites_and_requests
                          else
                            members_with_parents(only_active_users: false)
                          end

  members_from_hiearchy.all_owners.left_outer_joins(:user)
    .merge(User.without_project_bot)
    .allow_cross_joins_across_databases(url: "https://gitlab.com/gitlab-org/gitlab/-/issues/417455")
end

#members_from_self_and_ancestors_with_effective_access_levelObject



628
629
630
631
# File 'app/models/group.rb', line 628

def members_from_self_and_ancestors_with_effective_access_level
  members_with_parents.select([:user_id, 'MAX(access_level) AS access_level'])
                      .group(:user_id)
end

#members_with_descendantsObject



633
634
635
636
637
# File 'app/models/group.rb', line 633

def members_with_descendants
  GroupMember
    .active_without_invites_and_requests
    .where(source_id: self_and_descendants.reorder(nil).select(:id))
end

#members_with_parents(only_active_users: true) ⇒ Object



605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
# File 'app/models/group.rb', line 605

def members_with_parents(only_active_users: true)
  # Avoids an unnecessary SELECT when the group has no parents
  source_ids =
    if has_parent?
      self_and_ancestors.reorder(nil).select(:id)
    else
      id
    end

  group_hierarchy_members = GroupMember.non_minimal_access
                                       .where(source_id: source_ids)
                                       .select(*GroupMember.cached_column_list)

  group_hierarchy_members = if only_active_users
                              group_hierarchy_members.active_without_invites_and_requests
                            else
                              group_hierarchy_members.without_invites_and_requests
                            end

  GroupMember.from_union([group_hierarchy_members,
                          members_from_self_and_ancestor_group_shares])
end

#membership_locked?Boolean

Returns:

  • (Boolean)


759
760
761
# File 'app/models/group.rb', line 759

def membership_locked?
  false # to support project and group calling this as 'source'
end

#notification_email_for(user) ⇒ Object



372
373
374
375
376
# File 'app/models/group.rb', line 372

def notification_email_for(user)
  # Finds the closest notification_setting with a `notification_email`
  notification_settings = notification_settings_for(user, hierarchy_order: :asc)
  notification_settings.find { |n| n.notification_email.present? }&.notification_email
end

#notification_settings(hierarchy_order: nil) ⇒ Object

Overrides notification_settings has_many association This allows to apply notification settings from parent groups to child groups and projects.



348
349
350
351
352
353
354
355
356
357
358
# File 'app/models/group.rb', line 348

def notification_settings(hierarchy_order: nil)
  source_type = self.class.base_class.name
  settings = NotificationSetting.where(source_type: source_type, source_id: self_and_ancestors_ids)

  return settings unless hierarchy_order && self_and_ancestors_ids.length > 1

  settings
    .joins("LEFT JOIN (#{self_and_ancestors(hierarchy_order: hierarchy_order).to_sql}) AS ordered_groups ON notification_settings.source_id = ordered_groups.id")
    .select('notification_settings.*, ordered_groups.depth AS depth')
    .order("ordered_groups.depth #{hierarchy_order}")
end

#notification_settings_for(user, hierarchy_order: nil) ⇒ Object



360
361
362
# File 'app/models/group.rb', line 360

def notification_settings_for(user, hierarchy_order: nil)
  notification_settings(hierarchy_order: hierarchy_order).where(user: user)
end

#open_issues_count(current_user = nil) ⇒ Object

rubocop: disable CodeReuse/ServiceClass



836
837
838
# File 'app/models/group.rb', line 836

def open_issues_count(current_user = nil)
  Groups::OpenIssuesCountService.new(self, current_user).count
end

#open_merge_requests_count(current_user = nil) ⇒ Object

rubocop: disable CodeReuse/ServiceClass



842
843
844
# File 'app/models/group.rb', line 842

def open_merge_requests_count(current_user = nil)
  Groups::MergeRequestsCountService.new(self, current_user).count
end

#owned_by?(user) ⇒ Boolean

Returns:

  • (Boolean)


422
423
424
# File 'app/models/group.rb', line 422

def owned_by?(user)
  owners.include?(user)
end

#packages_feature_enabled?Boolean

Returns:

  • (Boolean)


364
365
366
# File 'app/models/group.rb', line 364

def packages_feature_enabled?
  ::Gitlab.config.packages.enabled
end

#packages_policy_subjectObject



927
928
929
# File 'app/models/group.rb', line 927

def packages_policy_subject
  ::Packages::Policies::Group.new(self)
end

#parent_allows_two_factor_authentication?Boolean

Returns:

  • (Boolean)


819
820
821
822
823
824
# File 'app/models/group.rb', line 819

def parent_allows_two_factor_authentication?
  return true unless has_parent?

  ancestor_settings = ancestors.find_by(parent_id: nil).namespace_settings
  ancestor_settings.allow_mfa_for_subgroups
end

#post_create_hookObject



524
525
526
527
528
# File 'app/models/group.rb', line 524

def post_create_hook
  Gitlab::AppLogger.info("Group \"#{name}\" was created")

  system_hook_service.execute_hooks_for(self, :create)
end

#post_destroy_hookObject



530
531
532
533
534
# File 'app/models/group.rb', line 530

def post_destroy_hook
  Gitlab::AppLogger.info("Group \"#{name}\" was removed")

  system_hook_service.execute_hooks_for(self, :destroy)
end


794
795
796
797
798
799
# File 'app/models/group.rb', line 794

def preload_shared_group_links
  ActiveRecord::Associations::Preloader.new(
    records: [self],
    associations: { shared_with_group_links: [shared_with_group: :route] }
  ).call
end

#project_creation_levelObject



747
748
749
# File 'app/models/group.rb', line 747

def project_creation_level
  super || ::Gitlab::CurrentSettings.default_project_creation
end

#project_users_with_descendantsObject

Returns all users that are members of projects belonging to the current group or sub-groups



678
679
680
681
682
683
# File 'app/models/group.rb', line 678

def project_users_with_descendants
  User
    .joins(projects: :group)
    .where(namespaces: { id: self_and_descendants.select(:id) })
    .allow_cross_joins_across_databases(url: "https://gitlab.com/gitlab-org/gitlab/-/issues/417455")
end

#readme_projectObject



935
936
937
# File 'app/models/group.rb', line 935

def readme_project
  projects.find_by(path: README_PROJECT_PATH)
end

#refresh_members_authorized_projects(priority: UserProjectAccessChangedService::HIGH_PRIORITY, direct_members_only: false) ⇒ Object

rubocop: disable CodeReuse/ServiceClass



543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
# File 'app/models/group.rb', line 543

def refresh_members_authorized_projects(
  priority: UserProjectAccessChangedService::HIGH_PRIORITY,
  direct_members_only: false
)

  user_ids = if direct_members_only
               users_ids_of_direct_members
             else
               user_ids_for_project_authorizations
             end

  UserProjectAccessChangedService
    .new(user_ids)
    .execute(priority: priority)
end

#refresh_project_authorizationsObject



736
737
738
# File 'app/models/group.rb', line 736

def refresh_project_authorizations
  refresh_members_authorized_projects
end


726
727
728
729
730
# File 'app/models/group.rb', line 726

def related_group_ids
  [id,
   *ancestors.pluck(:id),
   *shared_with_group_links.pluck(:shared_with_group_id)]
end

#runners_tokenObject

each existing group needs to have a ‘runners_token`. we do this on read since migrating all existing groups is not a feasible solution.



743
744
745
# File 'app/models/group.rb', line 743

def runners_token
  ensure_runners_token!
end

#self_and_ancestors_idsObject



573
574
575
576
577
# File 'app/models/group.rb', line 573

def self_and_ancestors_ids
  strong_memoize(:self_and_ancestors_ids) do
    self_and_ancestors.pluck(:id)
  end
end

#self_and_descendants_idsObject



579
580
581
582
583
# File 'app/models/group.rb', line 579

def self_and_descendants_ids
  strong_memoize(:self_and_descendants_ids) do
    self_and_descendants.pluck(:id)
  end
end

#self_and_hierarchy_intersecting_with_user_groups(user) ⇒ Object



568
569
570
571
# File 'app/models/group.rb', line 568

def self_and_hierarchy_intersecting_with_user_groups(user)
  user_groups = GroupsFinder.new(user).execute.unscope(:order)
  self_and_hierarchy.unscope(:order).where(id: user_groups)
end


867
868
869
# File 'app/models/group.rb', line 867

def shared_with_group_links_visible_to_user(user)
  shared_with_group_links.preload_shared_with_groups.filter { |link| Ability.allowed?(user, :read_group, link.shared_with_group) }
end

#subgroup_creation_levelObject



751
752
753
# File 'app/models/group.rb', line 751

def subgroup_creation_level
  super || ::Gitlab::Access::OWNER_SUBGROUP_ACCESS
end

#supports_events?Boolean

Returns:

  • (Boolean)


763
764
765
# File 'app/models/group.rb', line 763

def supports_events?
  false
end

#supports_lock_on_merge?Boolean

Returns:

  • (Boolean)


902
903
904
# File 'app/models/group.rb', line 902

def supports_lock_on_merge?
  feature_flag_enabled_for_self_or_ancestor?(:enforce_locked_labels_on_merge, type: :ops)
end

#system_hook_serviceObject

rubocop: disable CodeReuse/ServiceClass



537
538
539
# File 'app/models/group.rb', line 537

def system_hook_service
  SystemHooksService.new
end

#timelogsObject

rubocop: enable CodeReuse/ServiceClass



847
848
849
# File 'app/models/group.rb', line 847

def timelogs
  Timelog.in_group(self)
end

#update_two_factor_requirement_for_membersObject



931
932
933
# File 'app/models/group.rb', line 931

def update_two_factor_requirement_for_members
  hierarchy_members.find_each(&:update_two_factor_requirement)
end

#usage_quotas_enabled?Boolean

Returns:

  • (Boolean)


906
907
908
# File 'app/models/group.rb', line 906

def usage_quotas_enabled?
  ::Feature.enabled?(:usage_quotas_for_all_editions, self) && root?
end

#user_ids_for_project_authorizationsObject



564
565
566
# File 'app/models/group.rb', line 564

def user_ids_for_project_authorizations
  members_with_parents.pluck(Arel.sql('DISTINCT members.user_id'))
end

#users_countObject



672
673
674
# File 'app/models/group.rb', line 672

def users_count
  members.count
end

#users_ids_of_direct_membersObject

rubocop: enable CodeReuse/ServiceClass



560
561
562
# File 'app/models/group.rb', line 560

def users_ids_of_direct_members
  direct_members.pluck_user_ids
end

#users_with_descendantsObject



666
667
668
669
670
# File 'app/models/group.rb', line 666

def users_with_descendants
  User
    .where(id: members_with_descendants.select(:user_id))
    .reorder(nil)
end

#users_with_parentsObject



660
661
662
663
664
# File 'app/models/group.rb', line 660

def users_with_parents
  User
    .where(id: members_with_parents.select(:user_id))
    .reorder(nil)
end

#visibility_level_allowed?(level = self.visibility_level) ⇒ Boolean

Returns:

  • (Boolean)


409
410
411
412
413
# File 'app/models/group.rb', line 409

def visibility_level_allowed?(level = self.visibility_level)
  visibility_level_allowed_by_parent?(level) &&
    visibility_level_allowed_by_projects?(level) &&
    visibility_level_allowed_by_sub_groups?(level)
end

#visibility_level_allowed_by_parent?(level = self.visibility_level) ⇒ Boolean

Returns:

  • (Boolean)


395
396
397
398
399
# File 'app/models/group.rb', line 395

def visibility_level_allowed_by_parent?(level = self.visibility_level)
  return true unless parent_id && parent_id.nonzero?

  level <= parent.visibility_level
end

#visibility_level_allowed_by_projects?(level = self.visibility_level) ⇒ Boolean

Returns:

  • (Boolean)


401
402
403
# File 'app/models/group.rb', line 401

def visibility_level_allowed_by_projects?(level = self.visibility_level)
  !projects.without_deleted.where('visibility_level > ?', level).exists?
end

#visibility_level_allowed_by_sub_groups?(level = self.visibility_level) ⇒ Boolean

Returns:

  • (Boolean)


405
406
407
# File 'app/models/group.rb', line 405

def visibility_level_allowed_by_sub_groups?(level = self.visibility_level)
  !children.where('visibility_level > ?', level).exists?
end

#web_url(only_path: nil) ⇒ Object



378
379
380
# File 'app/models/group.rb', line 378

def web_url(only_path: nil)
  Gitlab::UrlBuilder.build(self, only_path: only_path)
end

#work_items_feature_flag_enabled?Boolean

Returns:

  • (Boolean)


886
887
888
# File 'app/models/group.rb', line 886

def work_items_feature_flag_enabled?
  feature_flag_enabled_for_self_or_ancestor?(:work_items)
end

#work_items_mvc_2_feature_flag_enabled?Boolean

Returns:

  • (Boolean)


894
895
896
# File 'app/models/group.rb', line 894

def work_items_mvc_2_feature_flag_enabled?
  feature_flag_enabled_for_self_or_ancestor?(:work_items_mvc_2)
end

#work_items_mvc_feature_flag_enabled?Boolean

Returns:

  • (Boolean)


890
891
892
# File 'app/models/group.rb', line 890

def work_items_mvc_feature_flag_enabled?
  feature_flag_enabled_for_self_or_ancestor?(:work_items_mvc)
end