2018-11-18 11:00:15 +05:30
# frozen_string_literal: true
2014-09-02 18:07:02 +05:30
require 'carrierwave/orm/activerecord'
class Group < Namespace
2015-09-11 14:41:01 +05:30
include Gitlab :: ConfigHelper
2018-03-17 18:26:18 +05:30
include AfterCommitQueue
2016-06-16 23:09:34 +05:30
include AccessRequestable
2017-09-10 17:25:29 +05:30
include Avatarable
2015-09-11 14:41:01 +05:30
include Referable
2017-08-17 22:00:37 +05:30
include SelectForProjectAuthorization
2018-03-17 18:26:18 +05:30
include LoadedInGroupList
include GroupDescendant
2018-10-15 14:42:47 +05:30
include TokenAuthenticatable
2018-11-08 19:23:39 +05:30
include WithUploads
include Gitlab :: Utils :: StrongMemoize
2019-12-21 20:55:43 +05:30
include GroupAPICompatibility
2021-01-03 14:25:43 +05:30
include EachBatch
2021-09-04 01:27:46 +05:30
include BulkMemberAccessLoad
2019-12-04 20:38:33 +05:30
2021-11-11 11:23:49 +05:30
def self . sti_name
'Group'
end
2020-11-24 15:15:51 +05:30
has_many :all_group_members , - > { where ( requested_at : nil ) } , dependent : :destroy , as : :source , class_name : 'GroupMember' # rubocop:disable Cop/ActiveRecordDependent
has_many :group_members , - > { where ( requested_at : nil ) . where . not ( members : { access_level : Gitlab :: Access :: MINIMAL_ACCESS } ) } , dependent : :destroy , as : :source # rubocop:disable Cop/ActiveRecordDependent
2015-11-26 14:37:03 +05:30
alias_method :members , :group_members
2020-11-24 15:15:51 +05:30
2016-08-24 12:49:21 +05:30
has_many :users , through : :group_members
2016-06-22 15:30:34 +05:30
has_many :owners ,
- > { where ( members : { access_level : Gitlab :: Access :: OWNER } ) } ,
through : :group_members ,
source : :user
2017-09-10 17:25:29 +05:30
has_many :requesters , - > { where . not ( requested_at : nil ) } , dependent : :destroy , as : :source , class_name : 'GroupMember' # rubocop:disable Cop/ActiveRecordDependent
2018-03-17 18:26:18 +05:30
has_many :members_and_requesters , as : :source , class_name : 'GroupMember'
2016-08-24 12:49:21 +05:30
2017-09-10 17:25:29 +05:30
has_many :milestones
2021-06-08 01:23:25 +05:30
has_many :integrations
2019-12-26 22:10:19 +05:30
has_many :shared_group_links , foreign_key : :shared_with_group_id , class_name : 'GroupGroupLink'
has_many :shared_with_group_links , foreign_key : :shared_group_id , class_name : 'GroupGroupLink'
has_many :shared_groups , through : :shared_group_links , source : :shared_group
has_many :shared_with_groups , through : :shared_with_group_links , source : :shared_with_group
2017-09-10 17:25:29 +05:30
has_many :project_group_links , dependent : :destroy # rubocop:disable Cop/ActiveRecordDependent
2016-06-02 11:05:42 +05:30
has_many :shared_projects , through : :project_group_links , source : :project
2018-11-08 19:23:39 +05:30
# Overridden on another method
# Left here just to be dependent: :destroy
2017-09-10 17:25:29 +05:30
has_many :notification_settings , dependent : :destroy , as : :source # rubocop:disable Cop/ActiveRecordDependent
2018-11-08 19:23:39 +05:30
2016-11-03 12:29:30 +05:30
has_many :labels , class_name : 'GroupLabel'
2017-09-10 17:25:29 +05:30
has_many :variables , class_name : 'Ci::GroupVariable'
2021-03-11 19:13:27 +05:30
has_many :daily_build_group_report_results , class_name : 'Ci::DailyBuildGroupReportResult'
2018-03-17 18:26:18 +05:30
has_many :custom_attributes , class_name : 'GroupCustomAttribute'
2014-09-02 18:07:02 +05:30
2018-03-27 19:54:05 +05:30
has_many :boards
has_many :badges , class_name : 'GroupBadge'
2018-12-13 13:39:08 +05:30
has_many :cluster_groups , class_name : 'Clusters::Group'
has_many :clusters , through : :cluster_groups , class_name : 'Clusters::Cluster'
2019-10-12 21:52:04 +05:30
has_many :container_repositories , through : :projects
2018-11-18 11:00:15 +05:30
has_many :todos
2019-12-26 22:10:19 +05:30
has_one :import_export_upload
2020-03-13 15:44:24 +05:30
has_many :import_failures , inverse_of : :group
2020-05-24 23:13:21 +05:30
has_one :import_state , class_name : 'GroupImportState' , inverse_of : :group
2021-06-08 01:23:25 +05:30
has_many :bulk_import_exports , class_name : 'BulkImports::Export' , inverse_of : :group
2020-10-24 23:57:45 +05:30
has_many :group_deploy_keys_groups , inverse_of : :group
has_many :group_deploy_keys , through : :group_deploy_keys_groups
2020-04-08 14:13:33 +05:30
has_many :group_deploy_tokens
has_many :deploy_tokens , through : :group_deploy_tokens
2021-04-29 21:17:54 +05:30
has_many :oauth_applications , class_name : 'Doorkeeper::Application' , as : :owner , dependent : :destroy # rubocop:disable Cop/ActiveRecordDependent
2020-04-08 14:13:33 +05:30
2021-01-29 00:20:46 +05:30
has_one :dependency_proxy_setting , class_name : 'DependencyProxy::GroupSetting'
2021-11-11 11:23:49 +05:30
has_one :dependency_proxy_image_ttl_policy , class_name : 'DependencyProxy::ImageTtlGroupPolicy'
2021-01-29 00:20:46 +05:30
has_many :dependency_proxy_blobs , class_name : 'DependencyProxy::Blob'
2021-02-22 17:27:13 +05:30
has_many :dependency_proxy_manifests , class_name : 'DependencyProxy::Manifest'
2021-01-29 00:20:46 +05:30
2021-03-11 19:13:27 +05:30
# debian_distributions and associated component_files must be destroyed by ruby code in order to properly remove carrierwave uploads
2021-03-08 18:12:59 +05:30
has_many :debian_distributions , class_name : 'Packages::Debian::GroupDistribution' , dependent : :destroy # rubocop:disable Cop/ActiveRecordDependent
2021-11-11 11:23:49 +05:30
has_many :group_callouts , class_name : 'Users::GroupCallout' , foreign_key : :group_id
delegate :prevent_sharing_groups_outside_hierarchy , :new_user_signups_cap , :setup_for_company , :jobs_to_be_done , to : :namespace_settings
2021-09-04 01:27:46 +05:30
2018-03-17 18:26:18 +05:30
accepts_nested_attributes_for :variables , allow_destroy : true
2014-09-02 18:07:02 +05:30
2018-03-17 18:26:18 +05:30
validate :visibility_level_allowed_by_projects
validate :visibility_level_allowed_by_sub_groups
validate :visibility_level_allowed_by_parent
2021-01-03 14:25:43 +05:30
validate :two_factor_authentication_allowed
2021-04-29 21:17:54 +05:30
validates :variables , nested_attributes_duplicates : { scope : :environment_scope }
2017-08-17 22:00:37 +05:30
2018-03-17 18:26:18 +05:30
validates :two_factor_grace_period , presence : true , numericality : { greater_than_or_equal_to : 0 }
2020-07-02 01:45:43 +05:30
2020-03-28 13:19:24 +05:30
validates :name ,
2020-07-02 01:45:43 +05:30
html_safety : true ,
format : { with : Gitlab :: Regex . group_name_regex ,
message : Gitlab :: Regex . group_name_regex_message } ,
if : :name_changed?
2015-04-26 12:48:37 +05:30
2019-07-07 11:18:12 +05:30
add_authentication_token_field :runners_token , encrypted : - > { Feature . enabled? ( :groups_tokens_optional_encryption , default_enabled : true ) ? :optional : :required }
2018-10-15 14:42:47 +05:30
2015-04-26 12:48:37 +05:30
after_create :post_create_hook
after_destroy :post_destroy_hook
2017-08-17 22:00:37 +05:30
after_save :update_two_factor_requirement
2019-07-31 22:56:46 +05:30
after_update :path_changed_hook , if : :saved_change_to_path?
2015-04-26 12:48:37 +05:30
2019-09-30 21:07:59 +05:30
scope :with_users , - > { includes ( :users ) }
2021-06-08 01:23:25 +05:30
scope :with_onboarding_progress , - > { joins ( :onboarding_progress ) }
2020-07-28 23:09:34 +05:30
scope :by_id , - > ( groups ) { where ( id : groups ) }
2021-01-29 00:20:46 +05:30
scope :for_authorized_group_members , - > ( user_ids ) do
joins ( :group_members )
2021-06-08 01:23:25 +05:30
. where ( members : { user_id : user_ids } )
2021-01-29 00:20:46 +05:30
. where ( " access_level >= ? " , Gitlab :: Access :: GUEST )
end
scope :for_authorized_project_members , - > ( user_ids ) do
joins ( projects : :project_authorizations )
2021-06-08 01:23:25 +05:30
. where ( project_authorizations : { user_id : user_ids } )
2021-01-29 00:20:46 +05:30
end
2015-04-26 12:48:37 +05:30
class << self
2018-05-09 12:01:36 +05:30
def sort_by_attribute ( method )
2017-08-17 22:00:37 +05:30
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
2015-04-26 12:48:37 +05:30
end
2015-09-11 14:41:01 +05:30
def reference_prefix
User . reference_prefix
end
def reference_pattern
User . reference_pattern
end
2015-11-26 14:37:03 +05:30
2018-11-08 19:23:39 +05:30
# 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
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 )
2015-11-26 14:37:03 +05:30
end
2017-08-17 22:00:37 +05:30
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' )
2021-06-08 01:23:25 +05:30
. where ( project_namespace : { share_with_group_lock : false } )
2021-10-27 15:23:28 +05:30
. select ( " projects.id AS project_id " , " LEAST(project_group_links.group_access, members.access_level) AS access_level " )
2017-08-17 22:00:37 +05:30
else
super
end
end
2018-11-08 19:23:39 +05:30
2021-01-03 14:25:43 +05:30
def without_integration ( integration )
2021-06-08 01:23:25 +05:30
integrations = Integration
2021-01-03 14:25:43 +05:30
. select ( '1' )
2021-09-30 23:02:18 +05:30
. where ( " #{ Integration . table_name } .group_id = namespaces.id " )
2021-01-03 14:25:43 +05:30
. where ( type : integration . type )
2021-06-08 01:23:25 +05:30
where ( 'NOT EXISTS (?)' , integrations )
2021-01-03 14:25:43 +05:30
end
2021-03-11 19:13:27 +05:30
# This method can be used only if all groups have the same top-level
# group
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
2021-04-29 21:17:54 +05:30
# Returns the ids of the passed group models where the `emails_disabled`
# column is set to true anywhere in the ancestor hierarchy.
def ids_with_disabled_email ( groups )
2021-11-18 22:05:49 +05:30
inner_groups = Group . where ( 'id = namespaces_with_emails_disabled.id' )
inner_ancestors = if Feature . enabled? ( :linear_group_ancestor_scopes , default_enabled : :yaml )
inner_groups . self_and_ancestors
else
Gitlab :: ObjectHierarchy . new ( inner_groups ) . base_and_ancestors
end
inner_query = inner_ancestors
2021-04-29 21:17:54 +05:30
. 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 } )
2021-11-18 22:05:49 +05:30
. where ( 'EXISTS (?)' , inner_query )
2021-04-29 21:17:54 +05:30
. pluck ( :id )
Set . new ( group_ids )
end
2018-11-08 19:23:39 +05:30
private
def public_to_user_arel ( user )
self . arel_table [ :visibility_level ]
. in ( Gitlab :: VisibilityLevel . levels_for_user ( user ) )
end
def visible_to_user_arel ( user )
groups_table = self . arel_table
2019-12-26 22:10:19 +05:30
authorized_groups = user . authorized_groups . arel . as ( 'authorized' )
2018-11-08 19:23:39 +05:30
groups_table . project ( 1 )
. from ( authorized_groups )
. where ( authorized_groups [ :id ] . eq ( groups_table [ :id ] ) )
. exists
end
end
# Overrides notification_settings has_many association
# This allows to apply notification settings from parent groups
# to child groups and projects.
2019-09-04 21:01:54 +05:30
def notification_settings ( hierarchy_order : nil )
2018-11-08 19:23:39 +05:30
source_type = self . class . base_class . name
2019-09-04 21:01:54 +05:30
settings = NotificationSetting . where ( source_type : source_type , source_id : self_and_ancestors_ids )
2018-11-08 19:23:39 +05:30
2019-09-04 21:01:54 +05:30
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
def notification_settings_for ( user , hierarchy_order : nil )
notification_settings ( hierarchy_order : hierarchy_order ) . where ( user : user )
2015-09-11 14:41:01 +05:30
end
2020-10-24 23:57:45 +05:30
def packages_feature_enabled?
:: Gitlab . config . packages . enabled
end
2021-01-29 00:20:46 +05:30
def dependency_proxy_feature_available?
:: Gitlab . config . dependency_proxy . enabled
end
2019-10-12 21:52:04 +05:30
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
2020-05-24 23:13:21 +05:30
def to_reference ( _from = nil , target_project : nil , full : nil )
2017-08-17 22:00:37 +05:30
" #{ self . class . reference_prefix } #{ full_path } "
2015-04-26 12:48:37 +05:30
end
2014-09-02 18:07:02 +05:30
2020-04-22 19:07:51 +05:30
def web_url ( only_path : nil )
Gitlab :: UrlBuilder . build ( self , only_path : only_path )
2016-06-16 23:09:34 +05:30
end
2021-11-11 11:23:49 +05:30
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
2014-09-02 18:07:02 +05:30
def human_name
2017-08-17 22:00:37 +05:30
full_name
2014-09-02 18:07:02 +05:30
end
2018-03-17 18:26:18 +05:30
def visibility_level_allowed_by_parent? ( level = self . visibility_level )
return true unless parent_id && parent_id . nonzero?
2016-06-02 11:05:42 +05:30
2018-03-17 18:26:18 +05:30
level < = parent . visibility_level
end
def visibility_level_allowed_by_projects? ( level = self . visibility_level )
! projects . where ( 'visibility_level > ?' , level ) . exists?
end
2016-06-02 11:05:42 +05:30
2018-03-17 18:26:18 +05:30
def visibility_level_allowed_by_sub_groups? ( level = self . visibility_level )
! children . where ( 'visibility_level > ?' , level ) . exists?
2016-06-02 11:05:42 +05:30
end
2018-03-17 18:26:18 +05:30
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 )
2015-09-11 14:41:01 +05:30
end
2016-09-29 09:46:39 +05:30
def lfs_enabled?
return false unless Gitlab . config . lfs . enabled
return Gitlab . config . lfs . enabled if self [ :lfs_enabled ] . nil?
self [ :lfs_enabled ]
end
2018-10-15 14:42:47 +05:30
def owned_by? ( user )
owners . include? ( user )
end
2016-11-03 12:29:30 +05:30
def add_users ( users , access_level , current_user : nil , expires_at : nil )
2021-11-11 11:23:49 +05:30
Members :: Groups :: BulkCreatorService . add_users ( # rubocop:disable CodeReuse/ServiceClass
2016-11-03 12:29:30 +05:30
self ,
users ,
access_level ,
current_user : current_user ,
expires_at : expires_at
)
2014-09-02 18:07:02 +05:30
end
2018-11-08 19:23:39 +05:30
def add_user ( user , access_level , current_user : nil , expires_at : nil , ldap : false )
2021-10-27 15:23:28 +05:30
Members :: Groups :: CreatorService . new ( self , # rubocop:disable CodeReuse/ServiceClass
2021-09-30 23:02:18 +05:30
user ,
access_level ,
current_user : current_user ,
expires_at : expires_at ,
ldap : ldap )
. execute
2014-09-02 18:07:02 +05:30
end
2015-09-11 14:41:01 +05:30
def add_guest ( user , current_user = nil )
2016-11-03 12:29:30 +05:30
add_user ( user , :guest , current_user : current_user )
2015-09-11 14:41:01 +05:30
end
def add_reporter ( user , current_user = nil )
2016-11-03 12:29:30 +05:30
add_user ( user , :reporter , current_user : current_user )
2015-09-11 14:41:01 +05:30
end
def add_developer ( user , current_user = nil )
2016-11-03 12:29:30 +05:30
add_user ( user , :developer , current_user : current_user )
2015-09-11 14:41:01 +05:30
end
2018-11-18 11:00:15 +05:30
def add_maintainer ( user , current_user = nil )
add_user ( user , :maintainer , current_user : current_user )
2015-09-11 14:41:01 +05:30
end
2015-04-26 12:48:37 +05:30
def add_owner ( user , current_user = nil )
2016-11-03 12:29:30 +05:30
add_user ( user , :owner , current_user : current_user )
2014-09-02 18:07:02 +05:30
end
2018-03-17 18:26:18 +05:30
def member? ( user , min_access_level = Gitlab :: Access :: GUEST )
return false unless user
max_member_access_for_user ( user ) > = min_access_level
end
2014-09-02 18:07:02 +05:30
def has_owner? ( user )
2017-09-10 17:25:29 +05:30
return false unless user
2019-07-07 11:18:12 +05:30
members_with_parents . owners . exists? ( user_id : user )
2014-09-02 18:07:02 +05:30
end
2021-04-29 21:17:54 +05:30
def blocked_owners
members . blocked . where ( access_level : Gitlab :: Access :: OWNER )
end
2018-11-18 11:00:15 +05:30
def has_maintainer? ( user )
2017-09-10 17:25:29 +05:30
return false unless user
2019-07-07 11:18:12 +05:30
members_with_parents . maintainers . exists? ( user_id : user )
2014-09-02 18:07:02 +05:30
end
2019-12-26 22:10:19 +05:30
def has_container_repository_including_subgroups?
:: ContainerRepository . for_group_and_its_subgroups ( self ) . exists?
2019-12-21 20:55:43 +05:30
end
2017-08-17 22:00:37 +05:30
# Check if user is a last owner of the group.
2014-09-02 18:07:02 +05:30
def last_owner? ( user )
2021-04-29 21:17:54 +05:30
has_owner? ( user ) && single_owner?
end
def member_last_owner? ( member )
return member . last_owner unless member . last_owner . nil?
last_owner? ( member . user )
end
def single_owner?
members_with_parents . owners . size == 1
2014-09-02 18:07:02 +05:30
end
2021-04-29 21:17:54 +05:30
def single_blocked_owner?
blocked_owners . size == 1
end
def member_last_blocked_owner? ( member )
return member . last_blocked_owner unless member . last_blocked_owner . nil?
2021-04-17 20:07:23 +05:30
return false if members_with_parents . owners . any?
2021-04-29 21:17:54 +05:30
single_blocked_owner? && blocked_owners . exists? ( user_id : member . user )
2021-04-17 20:07:23 +05:30
end
2018-11-08 19:23:39 +05:30
def ldap_synced?
false
end
2015-04-26 12:48:37 +05:30
def post_create_hook
2015-09-11 14:41:01 +05:30
Gitlab :: AppLogger . info ( " Group \" #{ name } \" was created " )
2015-04-26 12:48:37 +05:30
system_hook_service . execute_hooks_for ( self , :create )
end
2014-09-02 18:07:02 +05:30
2015-04-26 12:48:37 +05:30
def post_destroy_hook
2015-09-11 14:41:01 +05:30
Gitlab :: AppLogger . info ( " Group \" #{ name } \" was removed " )
2015-04-26 12:48:37 +05:30
system_hook_service . execute_hooks_for ( self , :destroy )
end
2018-12-05 23:21:45 +05:30
# rubocop: disable CodeReuse/ServiceClass
2015-04-26 12:48:37 +05:30
def system_hook_service
SystemHooksService . new
2014-09-02 18:07:02 +05:30
end
2018-12-05 23:21:45 +05:30
# rubocop: enable CodeReuse/ServiceClass
2017-08-17 22:00:37 +05:30
2018-12-05 23:21:45 +05:30
# rubocop: disable CodeReuse/ServiceClass
2021-04-17 20:07:23 +05:30
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
2020-05-24 23:13:21 +05:30
UserProjectAccessChangedService
2021-04-17 20:07:23 +05:30
. new ( user_ids )
2020-05-24 23:13:21 +05:30
. execute ( blocking : blocking , priority : priority )
2017-08-17 22:00:37 +05:30
end
2018-12-05 23:21:45 +05:30
# rubocop: enable CodeReuse/ServiceClass
2017-08-17 22:00:37 +05:30
2021-04-17 20:07:23 +05:30
def users_ids_of_direct_members
direct_members . pluck ( :user_id )
end
2017-08-17 22:00:37 +05:30
def user_ids_for_project_authorizations
2021-04-17 20:07:23 +05:30
members_with_parents . pluck ( Arel . sql ( 'DISTINCT members.user_id' ) )
2017-08-17 22:00:37 +05:30
end
2018-11-08 19:23:39 +05:30
def self_and_ancestors_ids
strong_memoize ( :self_and_ancestors_ids ) do
self_and_ancestors . pluck ( :id )
end
end
2021-09-04 01:27:46 +05:30
def self_and_descendants_ids
strong_memoize ( :self_and_descendants_ids ) do
self_and_descendants . pluck ( :id )
end
end
2021-04-17 20:07:23 +05:30
def direct_members
GroupMember . active_without_invites_and_requests
. non_minimal_access
. where ( source_id : id )
end
2021-06-08 01:23:25 +05:30
def authorizable_members_with_parents
source_ids =
if has_parent?
self_and_ancestors . reorder ( nil ) . select ( :id )
else
id
end
2021-10-27 15:23:28 +05:30
group_hierarchy_members = GroupMember . where ( source_id : source_ids ) . select ( * GroupMember . cached_column_list )
2021-06-08 01:23:25 +05:30
GroupMember . from_union ( [ group_hierarchy_members ,
members_from_self_and_ancestor_group_shares ] ) . authorizable
end
2017-08-17 22:00:37 +05:30
def members_with_parents
2017-09-10 17:25:29 +05:30
# Avoids an unnecessary SELECT when the group has no parents
source_ids =
2020-06-23 00:09:42 +05:30
if has_parent?
2017-09-10 17:25:29 +05:30
self_and_ancestors . reorder ( nil ) . select ( :id )
else
id
end
2020-06-23 00:09:42 +05:30
group_hierarchy_members = GroupMember . active_without_invites_and_requests
2021-01-03 14:25:43 +05:30
. non_minimal_access
2020-06-23 00:09:42 +05:30
. where ( source_id : source_ids )
2021-10-27 15:23:28 +05:30
. select ( * GroupMember . cached_column_list )
2020-06-23 00:09:42 +05:30
GroupMember . from_union ( [ group_hierarchy_members ,
members_from_self_and_ancestor_group_shares ] )
2017-09-10 17:25:29 +05:30
end
2020-05-24 23:13:21 +05:30
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
2017-09-10 17:25:29 +05:30
def members_with_descendants
GroupMember
2018-04-04 21:44:52 +05:30
. active_without_invites_and_requests
2017-09-10 17:25:29 +05:30
. where ( source_id : self_and_descendants . reorder ( nil ) . select ( :id ) )
2017-08-17 22:00:37 +05:30
end
2018-10-15 14:42:47 +05:30
# Returns all members that are part of the group, it's subgroups, and ancestor groups
def direct_and_indirect_members
GroupMember
. active_without_invites_and_requests
. where ( source_id : self_and_hierarchy . reorder ( nil ) . select ( :id ) )
end
2021-02-22 17:27:13 +05:30
def direct_and_indirect_members_with_inactive
GroupMember
. non_request
. non_invite
. where ( source_id : self_and_hierarchy . reorder ( nil ) . select ( :id ) )
end
2017-08-17 22:00:37 +05:30
def users_with_parents
2017-09-10 17:25:29 +05:30
User
. where ( id : members_with_parents . select ( :user_id ) )
. reorder ( nil )
end
def users_with_descendants
User
. where ( id : members_with_descendants . select ( :user_id ) )
. reorder ( nil )
end
2018-10-15 14:42:47 +05:30
# 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
def direct_and_indirect_users
2018-12-05 23:21:45 +05:30
User . from_union ( [
2018-10-15 14:42:47 +05:30
User
. where ( id : direct_and_indirect_members . select ( :user_id ) )
. reorder ( nil ) ,
project_users_with_descendants
] )
end
2021-02-22 17:27:13 +05:30
# 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
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
2020-11-24 15:15:51 +05:30
def users_count
members . count
end
2018-10-15 14:42:47 +05:30
# Returns all users that are members of projects
# belonging to the current group or sub-groups
def project_users_with_descendants
User
. joins ( projects : :group )
. where ( namespaces : { id : self_and_descendants . select ( :id ) } )
end
2020-11-24 15:15:51 +05:30
# 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
#
# @param user [User]
# @param only_concrete_membership [Bool] whether require admin concrete membership status
def max_member_access_for_user ( user , only_concrete_membership : false )
2019-09-04 21:01:54 +05:30
return GroupMember :: NO_ACCESS unless user
2021-04-17 20:07:23 +05:30
return GroupMember :: OWNER if user . can_admin_all_resources? && ! only_concrete_membership
2021-06-08 01:23:25 +05:30
2021-09-04 01:27:46 +05:30
max_member_access ( [ user . id ] ) [ user . id ]
2017-08-17 22:00:37 +05:30
end
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
2021-04-17 20:07:23 +05:30
def ci_variables_for ( ref , project , environment : nil )
cache_key = " ci_variables_for:group: #{ self & . id } :project: #{ project & . id } :ref: #{ ref } :environment: #{ environment } "
2020-04-08 14:13:33 +05:30
:: Gitlab :: SafeRequestStore . fetch ( cache_key ) do
2021-04-17 20:07:23 +05:30
uncached_ci_variables_for ( ref , project , environment : environment )
2020-04-08 14:13:33 +05:30
end
2017-09-10 17:25:29 +05:30
end
2018-03-17 18:26:18 +05:30
def group_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
2019-03-02 22:35:43 +05:30
def highest_group_member ( user )
GroupMember . where ( source_id : self_and_ancestors_ids , user_id : user . id ) . order ( :access_level ) . last
end
2020-03-13 15:44:24 +05:30
def related_group_ids
[ id ,
* ancestors . pluck ( :id ) ,
* shared_with_group_links . pluck ( :shared_with_group_id ) ]
end
2018-03-17 18:26:18 +05:30
def hashed_storage? ( _feature )
false
end
2018-05-09 12:01:36 +05:30
def refresh_project_authorizations
refresh_members_authorized_projects ( blocking : false )
end
2018-10-15 14:42:47 +05:30
# each existing group needs to have a `runners_token`.
# we do this on read since migrating all existing groups is not a feasible
# solution.
def runners_token
ensure_runners_token!
end
2019-07-07 11:18:12 +05:30
def project_creation_level
super || :: Gitlab :: CurrentSettings . default_project_creation
end
2019-10-12 21:52:04 +05:30
def subgroup_creation_level
super || :: Gitlab :: Access :: OWNER_SUBGROUP_ACCESS
end
2019-12-04 20:38:33 +05:30
def access_request_approvers_to_be_notified
2021-09-04 01:27:46 +05:30
members . owners . connected_to_user . order_recent_sign_in . limit ( Member :: ACCESS_REQUEST_APPROVERS_TO_BE_NOTIFIED_LIMIT )
2019-12-04 20:38:33 +05:30
end
2021-11-11 11:23:49 +05:30
def membership_locked?
false # to support project and group calling this as 'source'
end
2019-12-21 20:55:43 +05:30
def supports_events?
false
end
2019-12-26 22:10:19 +05:30
def export_file_exists?
2021-09-04 01:27:46 +05:30
import_export_upload & . export_file_exists?
2019-12-26 22:10:19 +05:30
end
def export_file
import_export_upload & . export_file
end
2021-09-04 01:27:46 +05:30
def export_archive_exists?
import_export_upload & . export_archive_exists?
end
2020-03-13 15:44:24 +05:30
def adjourned_deletion?
false
end
2020-05-24 23:13:21 +05:30
def execute_hooks ( data , hooks_scope )
# NOOP
# TODO: group hooks https://gitlab.com/gitlab-org/gitlab/-/issues/216904
end
2021-09-30 23:02:18 +05:30
def execute_integrations ( data , hooks_scope )
2020-05-24 23:13:21 +05:30
# NOOP
# TODO: group hooks https://gitlab.com/gitlab-org/gitlab/-/issues/216904
2020-04-22 19:07:51 +05:30
end
2020-06-23 00:09:42 +05:30
def preload_shared_group_links
preloader = ActiveRecord :: Associations :: Preloader . new
preloader . preload ( self , shared_with_group_links : [ shared_with_group : :route ] )
end
2021-01-03 14:25:43 +05:30
def update_shared_runners_setting! ( state )
raise ArgumentError unless SHARED_RUNNERS_SETTINGS . include? ( state )
2020-07-28 23:09:34 +05:30
2021-01-03 14:25:43 +05:30
case state
2021-11-18 22:05:49 +05:30
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
2021-01-03 14:25:43 +05:30
end
2020-07-28 23:09:34 +05:30
end
2021-01-03 14:25:43 +05:30
def default_owner
owners . first || parent & . default_owner || owner
2020-07-28 23:09:34 +05:30
end
2021-01-03 14:25:43 +05:30
def default_branch_name
namespace_settings & . default_branch_name
2020-07-28 23:09:34 +05:30
end
2021-01-03 14:25:43 +05:30
def access_level_roles
GroupMember . access_level_roles
2020-07-28 23:09:34 +05:30
end
2021-01-03 14:25:43 +05:30
def access_level_values
access_level_roles . values
2020-07-28 23:09:34 +05:30
end
2021-01-03 14:25:43 +05:30
def parent_allows_two_factor_authentication?
return true unless has_parent?
2020-07-28 23:09:34 +05:30
2021-01-03 14:25:43 +05:30
ancestor_settings = ancestors . find_by ( parent_id : nil ) . namespace_settings
ancestor_settings . allow_mfa_for_subgroups
2020-10-24 23:57:45 +05:30
end
2021-01-29 00:20:46 +05:30
def has_project_with_service_desk_enabled?
Gitlab :: ServiceDesk . supported? && all_projects . service_desk_enabled . exists?
end
2021-06-08 01:23:25 +05:30
def activity_path
Gitlab :: Routing . url_helpers . activity_group_path ( self )
end
2021-09-04 01:27:46 +05:30
# rubocop: disable CodeReuse/ServiceClass
def open_issues_count ( current_user = nil )
Groups :: OpenIssuesCountService . new ( self , current_user ) . count
end
# rubocop: enable CodeReuse/ServiceClass
# rubocop: disable CodeReuse/ServiceClass
def open_merge_requests_count ( current_user = nil )
Groups :: MergeRequestsCountService . new ( self , current_user ) . count
end
# rubocop: enable CodeReuse/ServiceClass
2021-10-27 15:23:28 +05:30
def timelogs
Timelog . in_group ( self )
end
2021-11-11 11:23:49 +05:30
def cached_issues_state_count_enabled?
Feature . enabled? ( :cached_issues_state_count , self , default_enabled : :yaml )
end
def organizations
:: CustomerRelations :: Organization . where ( group_id : self . id )
end
def contacts
:: CustomerRelations :: Contact . where ( group_id : self . id )
end
def dependency_proxy_image_ttl_policy
super || build_dependency_proxy_image_ttl_policy
end
2018-03-17 18:26:18 +05:30
private
2017-08-17 22:00:37 +05:30
2021-09-04 01:27:46 +05:30
def max_member_access ( user_ids )
max_member_access_for_resource_ids ( User , user_ids ) do | user_ids |
members_with_parents . where ( user_id : user_ids ) . group ( :user_id ) . maximum ( :access_level )
end
end
2017-08-17 22:00:37 +05:30
def update_two_factor_requirement
2019-07-31 22:56:46 +05:30
return unless saved_change_to_require_two_factor_authentication? || saved_change_to_two_factor_grace_period?
2017-08-17 22:00:37 +05:30
2021-01-29 00:20:46 +05:30
direct_and_indirect_members . find_each ( & :update_two_factor_requirement )
2017-08-17 22:00:37 +05:30
end
2018-03-17 18:26:18 +05:30
def path_changed_hook
system_hook_service . execute_hooks_for ( self , :rename )
end
def visibility_level_allowed_by_parent
return if visibility_level_allowed_by_parent?
errors . add ( :visibility_level , " #{ visibility } is not allowed since the parent group has a #{ parent . visibility } visibility. " )
end
def visibility_level_allowed_by_projects
return if visibility_level_allowed_by_projects?
errors . add ( :visibility_level , " #{ visibility } is not allowed since this group contains projects with higher visibility. " )
end
def visibility_level_allowed_by_sub_groups
return if visibility_level_allowed_by_sub_groups?
errors . add ( :visibility_level , " #{ visibility } is not allowed since there are sub-groups with higher visibility. " )
end
2019-12-21 20:55:43 +05:30
2021-01-03 14:25:43 +05:30
def two_factor_authentication_allowed
return unless has_parent?
return unless require_two_factor_authentication
return if parent_allows_two_factor_authentication?
errors . add ( :require_two_factor_authentication , _ ( 'is forbidden by a top-level group' ) )
end
2020-06-23 00:09:42 +05:30
def members_from_self_and_ancestor_group_shares
2019-12-26 22:10:19 +05:30
group_group_link_table = GroupGroupLink . arel_table
group_member_table = GroupMember . arel_table
2020-06-23 00:09:42 +05:30
source_ids =
if has_parent?
self_and_ancestors . reorder ( nil ) . select ( :id )
else
id
end
group_group_links_query = GroupGroupLink . where ( shared_group_id : source_ids )
2019-12-26 22:10:19 +05:30
cte = Gitlab :: SQL :: CTE . new ( :group_group_links_cte , group_group_links_query )
2020-03-07 23:17:34 +05:30
cte_alias = cte . table . alias ( GroupGroupLink . table_name )
2019-12-26 22:10:19 +05:30
2020-06-23 00:09:42 +05:30
# Instead of members.access_level, we need to maximize that access_level at
# the respective group_group_links.group_access.
member_columns = GroupMember . attribute_names . map do | column_name |
if column_name == 'access_level'
smallest_value_arel ( [ cte_alias [ :group_access ] , group_member_table [ :access_level ] ] ,
'access_level' )
else
group_member_table [ column_name ]
end
end
GroupMember
. with ( cte . to_arel )
. select ( * member_columns )
. from ( [ group_member_table , cte . alias_to ( group_group_link_table ) ] )
. where ( group_member_table [ :requested_at ] . eq ( nil ) )
. where ( group_member_table [ :source_id ] . eq ( group_group_link_table [ :shared_with_group_id ] ) )
. where ( group_member_table [ :source_type ] . eq ( 'Namespace' ) )
2020-11-24 15:15:51 +05:30
. non_minimal_access
2019-12-26 22:10:19 +05:30
end
2020-03-07 23:17:34 +05:30
def smallest_value_arel ( args , column_alias )
Arel :: Nodes :: As . new (
Arel :: Nodes :: NamedFunction . new ( 'LEAST' , args ) ,
Arel :: Nodes :: SqlLiteral . new ( column_alias ) )
end
2019-12-21 20:55:43 +05:30
def self . groups_including_descendants_by ( group_ids )
2021-11-11 11:23:49 +05:30
groups = Group . where ( id : group_ids )
if Feature . enabled? ( :linear_group_including_descendants_by , default_enabled : :yaml )
groups . self_and_descendants
else
Gitlab :: ObjectHierarchy
. new ( groups )
2019-12-21 20:55:43 +05:30
. base_and_descendants
2021-11-11 11:23:49 +05:30
end
2019-12-21 20:55:43 +05:30
end
2021-01-03 14:25:43 +05:30
def disable_shared_runners!
update! (
shared_runners_enabled : false ,
allow_descendants_override_disabled_shared_runners : false )
group_ids = descendants
unless group_ids . empty?
Group . by_id ( group_ids ) . update_all (
shared_runners_enabled : false ,
allow_descendants_override_disabled_shared_runners : false )
end
all_projects . update_all ( shared_runners_enabled : false )
end
def disable_shared_runners_and_allow_override!
# enabled -> disabled_with_override
if shared_runners_enabled?
update! (
shared_runners_enabled : false ,
allow_descendants_override_disabled_shared_runners : true )
group_ids = descendants
unless group_ids . empty?
Group . by_id ( group_ids ) . update_all ( shared_runners_enabled : false )
end
all_projects . update_all ( shared_runners_enabled : false )
# disabled_and_unoverridable -> disabled_with_override
else
update! ( allow_descendants_override_disabled_shared_runners : true )
end
end
def enable_shared_runners!
update! ( shared_runners_enabled : true )
end
2021-04-17 20:07:23 +05:30
def uncached_ci_variables_for ( ref , project , environment : nil )
2021-06-08 01:23:25 +05:30
list_of_ids = if root_ancestor . use_traversal_ids?
[ self ] + ancestors ( hierarchy_order : :asc )
else
[ self ] + ancestors
end
2021-04-17 20:07:23 +05:30
variables = Ci :: GroupVariable . where ( group : list_of_ids )
variables = variables . unprotected unless project . protected_for? ( ref )
2021-04-29 21:17:54 +05:30
variables = if environment
variables . on_environment ( environment )
else
variables . where ( environment_scope : '*' )
end
2021-04-17 20:07:23 +05:30
variables = variables . group_by ( & :group_id )
list_of_ids . reverse . flat_map { | group | variables [ group . id ] } . compact
end
2014-09-02 18:07:02 +05:30
end
2019-12-04 20:38:33 +05:30
2021-06-08 01:23:25 +05:30
Group . prepend_mod_with ( 'Group' )