Class: Group

Defined Under Namespace

Classes: CrmSettings

Constant Summary

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::PATH_TRAILING_VIOLATIONS, Namespace::SHARED_RUNNERS_SETTINGS, 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_WORD

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

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 Gitlab::Utils::StrongMemoize

#clear_memoization, #strong_memoize, #strong_memoized?

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 Referable

#referable_inspect, #reference_link_text, #to_reference_base

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_container_repositories, #all_project_ids_except, #all_projects, #any_project_has_container_registry_tags?, #any_project_with_pages_deployed?, #any_project_with_shared_runners_enabled?, #auto_devops_enabled?, 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?, #enabled_git_access_protocol, find_by_pages_host, 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, #pages_virtual_domain, #paid?, #project_namespace?, #recent?, #root?, search, #send_update_instructions, #shared_runners, #shared_runners_setting, #shared_runners_setting_higher_than?, sti_class_for, #storage_enforcement_date, #subgroup?, #to_param, top_most, #user_namespace?, #visibility_level_field

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, #root_ancestor, #self_and_ancestor_ids, #self_and_ancestors, #self_and_descendant_ids, #self_and_descendants, #self_and_hierarchy, #use_traversal_ids?, #use_traversal_ids_for_ancestors?, #use_traversal_ids_for_ancestors_upto?, #use_traversal_ids_for_root_ancestor?, #use_traversal_ids_for_self_and_hierarchy?

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 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, #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


257
258
259
# File 'app/models/group.rb', line 257

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.


239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
# File 'app/models/group.rb', line 239

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


230
231
232
233
234
235
# File 'app/models/group.rb', line 230

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


199
200
201
202
203
204
205
206
207
# File 'app/models/group.rb', line 199

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

.reference_patternObject


192
193
194
# File 'app/models/group.rb', line 192

def reference_pattern
  User.reference_pattern
end

.reference_prefixObject


188
189
190
# File 'app/models/group.rb', line 188

def reference_prefix
  User.reference_prefix
end

.select_for_project_authorizationObject


209
210
211
212
213
214
215
216
217
# File 'app/models/group.rb', line 209

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


178
179
180
181
182
183
184
185
186
# File 'app/models/group.rb', line 178

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


26
27
28
# File 'app/models/group.rb', line 26

def self.sti_name
  'Group'
end

.without_integration(integration) ⇒ Object


219
220
221
222
223
224
225
226
# File 'app/models/group.rb', line 219

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


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

def access_level_roles
  GroupMember.access_level_roles
end

#access_level_valuesObject


786
787
788
# File 'app/models/group.rb', line 786

def access_level_values
  access_level_roles.values
end

#access_request_approvers_to_be_notifiedObject


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

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

#activity_pathObject


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

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

#add_developer(user, current_user = nil) ⇒ Object


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

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

#add_guest(user, current_user = nil) ⇒ Object


388
389
390
# File 'app/models/group.rb', line 388

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

#add_maintainer(user, current_user = nil) ⇒ Object


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

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, blocking_refresh: true) ⇒ Object


376
377
378
379
380
381
382
383
384
385
386
# File 'app/models/group.rb', line 376

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

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


364
365
366
367
368
369
370
371
372
373
374
# File 'app/models/group.rb', line 364

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


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

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

#add_reporter(user, current_user = nil) ⇒ Object


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

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

#adjourned_deletion?Boolean

Returns:

  • (Boolean)

745
746
747
# File 'app/models/group.rb', line 745

def adjourned_deletion?
  false
end

#all_owners_excluding_project_botsObject


446
447
448
# File 'app/models/group.rb', line 446

def all_owners_excluding_project_bots
  members_with_parents.owners.merge(User.without_project_bot)
end

#authorizable_members_with_parentsObject


529
530
531
532
533
534
535
536
537
538
539
540
541
# File 'app/models/group.rb', line 529

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


420
421
422
# File 'app/models/group.rb', line 420

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

#botsObject


683
684
685
# File 'app/models/group.rb', line 683

def bots
  users.project_bot
end

#ci_variables_for(ref, project, environment: nil) ⇒ Object


663
664
665
666
667
668
669
# File 'app/models/group.rb', line 663

def ci_variables_for(ref, project, environment: nil)
  cache_key = "ci_variables_for:group:#{self&.id}:project:#{project&.id}:ref:#{ref}:environment:#{environment}"

  ::Gitlab::SafeRequestStore.fetch(cache_key) do
    uncached_ci_variables_for(ref, project, environment: environment)
  end
end

#crm_enabled?Boolean

Returns:

  • (Boolean)

833
834
835
# File 'app/models/group.rb', line 833

def crm_enabled?
  crm_settings&.enabled?
end

#default_branch_nameObject


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

def default_branch_name
  namespace_settings&.default_branch_name
end

#dependency_proxy_feature_available?Boolean

Returns:

  • (Boolean)

302
303
304
# File 'app/models/group.rb', line 302

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

#dependency_proxy_image_prefixObject


320
321
322
323
324
325
326
327
# File 'app/models/group.rb', line 320

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


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

def dependency_proxy_image_ttl_policy
  super || build_dependency_proxy_image_ttl_policy
end

#dependency_proxy_settingObject


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

def dependency_proxy_setting
  super || build_dependency_proxy_setting
end

#direct_and_indirect_membersObject

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


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

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

#direct_and_indirect_members_with_inactiveObject


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

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

#direct_and_indirect_usersObject

Returns all users that are members of the group because:

  1. They belong to the group

  2. They belong to a project that belongs to the group

  3. They belong to a sub-group or project in such sub-group

  4. They belong to an ancestor group


603
604
605
606
607
608
609
610
# File 'app/models/group.rb', line 603

def direct_and_indirect_users
  User.from_union([
    User
      .where(id: direct_and_indirect_members.select(:user_id))
      .reorder(nil),
    project_users_with_descendants
  ])
end

#direct_and_indirect_users_with_inactiveObject

Returns all users (also inactive) that are members of the group because:

  1. They belong to the group

  2. They belong to a project that belongs to the group

  3. They belong to a sub-group or project in such sub-group

  4. They belong to an ancestor group


617
618
619
620
621
622
623
624
# File 'app/models/group.rb', line 617

def direct_and_indirect_users_with_inactive
  User.from_union([
    User
      .where(id: direct_and_indirect_members_with_inactive.select(:user_id))
      .reorder(nil),
    project_users_with_descendants
  ])
end

#direct_membersObject


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

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

#enforced_runner_token_expiration_intervalObject


841
842
843
844
845
846
847
848
849
850
# File 'app/models/group.rb', line 841

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


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

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


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

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

#export_archive_exists?Boolean

Returns:

  • (Boolean)

741
742
743
# File 'app/models/group.rb', line 741

def export_archive_exists?
  import_export_upload&.export_archive_exists?
end

#export_fileObject


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

def export_file
  import_export_upload&.export_file
end

#export_file_exists?Boolean

Returns:

  • (Boolean)

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

def export_file_exists?
  import_export_upload&.export_file_exists?
end

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

Returns:

  • (Boolean)

859
860
861
862
863
864
865
# File 'app/models/group.rb', line 859

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


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

def first_owner
  owners.first || parent&.first_owner || owner
end

#format_runners_token(token) ⇒ Object


709
710
711
# File 'app/models/group.rb', line 709

def format_runners_token(token)
  "#{RunnersTokenPrefixable::RUNNERS_TOKEN_PREFIX}#{token}"
end

#gitlab_deploy_tokenObject


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

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

#group_featureObject


829
830
831
# File 'app/models/group.rb', line 829

def group_feature
  super || build_group_feature
end

#has_container_repository_including_subgroups?Boolean

Returns:

  • (Boolean)

430
431
432
# File 'app/models/group.rb', line 430

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

#has_maintainer?(user) ⇒ Boolean

Returns:

  • (Boolean)

424
425
426
427
428
# File 'app/models/group.rb', line 424

def has_maintainer?(user)
  return false unless user

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

#has_owner?(user) ⇒ Boolean

Returns:

  • (Boolean)

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

def has_owner?(user)
  return false unless user

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

#has_project_with_service_desk_enabled?Boolean

Returns:

  • (Boolean)

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

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

#hashed_storage?(_feature) ⇒ Boolean

Returns:

  • (Boolean)

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

def hashed_storage?(_feature)
  false
end

#highest_group_member(user) ⇒ Object


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

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

#human_nameObject


329
330
331
# File 'app/models/group.rb', line 329

def human_name
  full_name
end

#last_owner?(user) ⇒ Boolean

Check if user is a last owner of the group. Excludes project_bots

Returns:

  • (Boolean)

436
437
438
# File 'app/models/group.rb', line 436

def last_owner?(user)
  has_owner?(user) && all_owners_excluding_project_bots.size == 1
end

#ldap_synced?Boolean

Returns:

  • (Boolean)

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

def ldap_synced?
  false
end

#lfs_enabled?Boolean

Returns:

  • (Boolean)

353
354
355
356
357
358
# File 'app/models/group.rb', line 353

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

  self[:lfs_enabled]
end

#mattermost_team_paramsObject


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

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


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

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


671
672
673
674
675
676
677
# File 'app/models/group.rb', line 671

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)

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

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

  max_member_access_for_user(user) >= min_access_level
end

#member_last_blocked_owner?(member) ⇒ Boolean

Returns:

  • (Boolean)

454
455
456
457
458
459
460
# File 'app/models/group.rb', line 454

def member_last_blocked_owner?(member)
  return member.last_blocked_owner unless member.last_blocked_owner.nil?

  return false if members_with_parents.owners.any?

  single_blocked_owner? && blocked_owners.exists?(user_id: member.user)
end

#member_last_owner?(member) ⇒ Boolean

Returns:

  • (Boolean)

440
441
442
443
444
# File 'app/models/group.rb', line 440

def member_last_owner?(member)
  return member.last_owner unless member.last_owner.nil?

  last_owner?(member.user)
end

#members_from_self_and_ancestors_with_effective_access_levelObject


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

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


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

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

#members_with_parentsObject


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

def members_with_parents
  # 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.active_without_invites_and_requests
                                       .non_minimal_access
                                       .where(source_id: source_ids)
                                       .select(*GroupMember.cached_column_list)

  GroupMember.from_union([group_hierarchy_members,
                          members_from_self_and_ancestor_group_shares])
end

#membership_locked?Boolean

Returns:

  • (Boolean)

725
726
727
# File 'app/models/group.rb', line 725

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

#notification_email_for(user) ⇒ Object


306
307
308
309
310
# File 'app/models/group.rb', line 306

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.


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

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


294
295
296
# File 'app/models/group.rb', line 294

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


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

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


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

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

#owned_by?(user) ⇒ Boolean

Returns:

  • (Boolean)

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

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

#packages_feature_enabled?Boolean

Returns:

  • (Boolean)

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

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

#parent_allows_two_factor_authentication?Boolean

Returns:

  • (Boolean)

790
791
792
793
794
795
# File 'app/models/group.rb', line 790

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


466
467
468
469
470
# File 'app/models/group.rb', line 466

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

  system_hook_service.execute_hooks_for(self, :create)
end

#post_destroy_hookObject


472
473
474
475
476
# File 'app/models/group.rb', line 472

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

  system_hook_service.execute_hooks_for(self, :destroy)
end

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

def preload_shared_group_links
  preloader = ActiveRecord::Associations::Preloader.new
  preloader.preload(self, shared_with_group_links: [shared_with_group: :route])
end

#project_creation_levelObject


713
714
715
# File 'app/models/group.rb', line 713

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


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

def project_users_with_descendants
  User
    .joins(projects: :group)
    .where(namespaces: { id: self_and_descendants.select(:id) })
end

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

rubocop: disable CodeReuse/ServiceClass


485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
# File 'app/models/group.rb', line 485

def refresh_members_authorized_projects(
  blocking: true,
  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(blocking: blocking, priority: priority)
end

#refresh_project_authorizationsObject


697
698
699
# File 'app/models/group.rb', line 697

def refresh_project_authorizations
  refresh_members_authorized_projects(blocking: false)
end

687
688
689
690
691
# File 'app/models/group.rb', line 687

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.


704
705
706
# File 'app/models/group.rb', line 704

def runners_token
  ensure_runners_token!
end

#self_and_ancestors_idsObject


511
512
513
514
515
# File 'app/models/group.rb', line 511

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

#self_and_descendants_idsObject


517
518
519
520
521
# File 'app/models/group.rb', line 517

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

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

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

#single_blocked_owner?Boolean

Returns:

  • (Boolean)

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

def single_blocked_owner?
  blocked_owners.size == 1
end

#subgroup_creation_levelObject


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

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

#supports_events?Boolean

Returns:

  • (Boolean)

729
730
731
# File 'app/models/group.rb', line 729

def supports_events?
  false
end

#system_hook_serviceObject

rubocop: disable CodeReuse/ServiceClass


479
480
481
# File 'app/models/group.rb', line 479

def system_hook_service
  SystemHooksService.new
end

#timelogsObject

rubocop: enable CodeReuse/ServiceClass


817
818
819
# File 'app/models/group.rb', line 817

def timelogs
  Timelog.in_group(self)
end

#to_reference(_from = nil, target_project: nil, full: nil) ⇒ Object


312
313
314
# File 'app/models/group.rb', line 312

def to_reference(_from = nil, target_project: nil, full: nil)
  "#{self.class.reference_prefix}#{full_path}"
end

#update_shared_runners_setting!(state) ⇒ Object

Raises:

  • (ArgumentError)

764
765
766
767
768
769
770
771
772
# File 'app/models/group.rb', line 764

def update_shared_runners_setting!(state)
  raise ArgumentError unless SHARED_RUNNERS_SETTINGS.include?(state)

  case state
  when SR_DISABLED_AND_UNOVERRIDABLE then disable_shared_runners! # also disallows override
  when SR_DISABLED_WITH_OVERRIDE then disable_shared_runners_and_allow_override!
  when SR_ENABLED then enable_shared_runners! # set both to true
  end
end

#user_ids_for_project_authorizationsObject


507
508
509
# File 'app/models/group.rb', line 507

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

#users_countObject


626
627
628
# File 'app/models/group.rb', line 626

def users_count
  members.count
end

#users_ids_of_direct_membersObject

rubocop: enable CodeReuse/ServiceClass


503
504
505
# File 'app/models/group.rb', line 503

def users_ids_of_direct_members
  direct_members.pluck(:user_id)
end

#users_with_descendantsObject


592
593
594
595
596
# File 'app/models/group.rb', line 592

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

#users_with_parentsObject


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

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)

347
348
349
350
351
# File 'app/models/group.rb', line 347

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)

333
334
335
336
337
# File 'app/models/group.rb', line 333

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)

339
340
341
# File 'app/models/group.rb', line 339

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

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

Returns:

  • (Boolean)

343
344
345
# File 'app/models/group.rb', line 343

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

#web_url(only_path: nil) ⇒ Object


316
317
318
# File 'app/models/group.rb', line 316

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

#work_items_feature_flag_enabled?Boolean

Returns:

  • (Boolean)

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

def work_items_feature_flag_enabled?
  feature_flag_enabled_for_self_or_ancestor?(:work_items)
end