2018-07-25 05:30:33 -04:00
# frozen_string_literal: true
2014-01-27 16:34:05 -05:00
require 'carrierwave/orm/activerecord'
2012-11-22 13:34:16 -05:00
class Group < Namespace
2015-08-24 10:46:06 -04:00
include Gitlab :: ConfigHelper
2017-11-29 10:30:17 -05:00
include AfterCommitQueue
2016-04-18 12:53:32 -04:00
include AccessRequestable
2017-05-10 00:26:17 -04:00
include Avatarable
2015-05-02 23:11:21 -04:00
include Referable
2016-10-11 08:25:17 -04:00
include SelectForProjectAuthorization
2017-10-05 04:32:52 -04:00
include LoadedInGroupList
2017-09-19 07:11:09 -04:00
include GroupDescendant
2017-09-06 09:46:57 -04:00
include TokenAuthenticatable
2018-05-07 09:49:32 -04:00
include WithUploads
2018-05-28 14:43:46 -04:00
include Gitlab :: Utils :: StrongMemoize
2019-10-07 11:05:59 -04:00
include GroupAPICompatibility
2020-09-29 11:10:08 -04:00
include EachBatch
2021-05-25 02:10:50 -04:00
include BulkMemberAccessLoad
2022-01-19 07:17:41 -05:00
include ChronicDurationAttribute
include RunnerTokenExpirationInterval
2016-03-01 10:22:29 -05:00
2022-02-25 19:14:54 -05:00
extend :: Gitlab :: Utils :: Override
# Prefix for runners_token which can be used to invalidate existing tokens.
# The value chosen here is GR (for Gitlab Runner) combined with the rotation
# date (20220225) decimal to hex encoded.
RUNNERS_TOKEN_PREFIX = 'GR1348941'
2021-09-08 17:10:58 -04:00
def self . sti_name
'Group'
end
2020-09-10 14:08:54 -04:00
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-11 10:42:27 -05:00
alias_method :members , :group_members
2020-09-10 14:08:54 -04:00
2018-04-18 09:41:42 -04:00
has_many :users , through : :group_members
2016-06-15 09:22:05 -04:00
has_many :owners ,
2018-04-18 09:41:42 -04:00
- > { where ( members : { access_level : Gitlab :: Access :: OWNER } ) } ,
2016-06-15 09:22:05 -04:00
through : :group_members ,
source : :user
2017-06-08 11:16:27 -04:00
has_many :requesters , - > { where . not ( requested_at : nil ) } , dependent : :destroy , as : :source , class_name : 'GroupMember' # rubocop:disable Cop/ActiveRecordDependent
2017-09-05 12:03:24 -04:00
has_many :members_and_requesters , as : :source , class_name : 'GroupMember'
2016-06-27 10:20:57 -04:00
2017-07-07 11:08:49 -04:00
has_many :milestones
2021-05-12 08:10:24 -04:00
has_many :integrations
2019-10-30 11:14:17 -04:00
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-06-08 11:16:27 -04:00
has_many :project_group_links , dependent : :destroy # rubocop:disable Cop/ActiveRecordDependent
2018-04-18 09:41:42 -04:00
has_many :shared_projects , through : :project_group_links , source : :project
2018-05-28 14:43:46 -04:00
# Overridden on another method
# Left here just to be dependent: :destroy
2017-06-08 11:16:27 -04:00
has_many :notification_settings , dependent : :destroy , as : :source # rubocop:disable Cop/ActiveRecordDependent
2018-05-28 14:43:46 -04:00
2016-09-19 11:04:38 -04:00
has_many :labels , class_name : 'GroupLabel'
2017-05-03 14:51:55 -04:00
has_many :variables , class_name : 'Ci::GroupVariable'
2021-02-16 16:09:23 -05:00
has_many :daily_build_group_report_results , class_name : 'Ci::DailyBuildGroupReportResult'
2017-09-18 11:07:38 -04:00
has_many :custom_attributes , class_name : 'GroupCustomAttribute'
2013-04-02 18:28:12 -04:00
2018-02-19 14:06:16 -05:00
has_many :boards
2018-03-05 12:51:40 -05:00
has_many :badges , class_name : 'GroupBadge'
2018-02-19 14:06:16 -05:00
2021-11-11 07:10:41 -05:00
has_many :organizations , class_name : 'CustomerRelations::Organization' , inverse_of : :group
has_many :contacts , class_name : 'CustomerRelations::Contact' , inverse_of : :group
2018-10-14 16:42:29 -04:00
has_many :cluster_groups , class_name : 'Clusters::Group'
has_many :clusters , through : :cluster_groups , class_name : 'Clusters::Cluster'
2019-08-05 16:00:50 -04:00
has_many :container_repositories , through : :projects
2018-07-16 09:35:19 -04:00
has_many :todos
2019-11-11 16:06:20 -05:00
has_one :import_export_upload
2020-01-17 16:08:29 -05:00
has_many :import_failures , inverse_of : :group
2020-04-25 11:09:53 -04:00
has_one :import_state , class_name : 'GroupImportState' , inverse_of : :group
2021-04-30 08:12:30 -04:00
has_many :bulk_import_exports , class_name : 'BulkImports::Export' , inverse_of : :group
2020-08-13 23:10:00 -04:00
has_many :group_deploy_keys_groups , inverse_of : :group
has_many :group_deploy_keys , through : :group_deploy_keys_groups
2020-02-28 13:09:07 -05:00
has_many :group_deploy_tokens
has_many :deploy_tokens , through : :group_deploy_tokens
2021-03-16 14:11:53 -04:00
has_many :oauth_applications , class_name : 'Doorkeeper::Application' , as : :owner , dependent : :destroy # rubocop:disable Cop/ActiveRecordDependent
2020-02-28 13:09:07 -05:00
2020-11-13 16:09:31 -05:00
has_one :dependency_proxy_setting , class_name : 'DependencyProxy::GroupSetting'
2021-08-25 20:09:31 -04:00
has_one :dependency_proxy_image_ttl_policy , class_name : 'DependencyProxy::ImageTtlGroupPolicy'
2020-11-13 16:09:31 -05:00
has_many :dependency_proxy_blobs , class_name : 'DependencyProxy::Blob'
2020-12-10 19:09:41 -05:00
has_many :dependency_proxy_manifests , class_name : 'DependencyProxy::Manifest'
2020-11-13 16:09:31 -05:00
2021-02-08 22:09:18 -05:00
# debian_distributions and associated component_files must be destroyed by ruby code in order to properly remove carrierwave uploads
2021-01-08 16:10:30 -05:00
has_many :debian_distributions , class_name : 'Packages::Debian::GroupDistribution' , dependent : :destroy # rubocop:disable Cop/ActiveRecordDependent
2021-09-14 20:09:30 -04:00
has_many :group_callouts , class_name : 'Users::GroupCallout' , foreign_key : :group_id
2021-08-26 08:10:28 -04:00
delegate :prevent_sharing_groups_outside_hierarchy , :new_user_signups_cap , :setup_for_company , :jobs_to_be_done , to : :namespace_settings
2022-01-19 07:17:41 -05:00
delegate :runner_token_expiration_interval , :runner_token_expiration_interval = , :runner_token_expiration_interval_human_readable , :runner_token_expiration_interval_human_readable = , to : :namespace_settings , allow_nil : true
delegate :subgroup_runner_token_expiration_interval , :subgroup_runner_token_expiration_interval = , :subgroup_runner_token_expiration_interval_human_readable , :subgroup_runner_token_expiration_interval_human_readable = , to : :namespace_settings , allow_nil : true
delegate :project_runner_token_expiration_interval , :project_runner_token_expiration_interval = , :project_runner_token_expiration_interval_human_readable , :project_runner_token_expiration_interval_human_readable = , to : :namespace_settings , allow_nil : true
2021-06-14 20:10:11 -04:00
2021-12-23 19:15:07 -05:00
has_one :crm_settings , class_name : 'Group::CrmSettings' , inverse_of : :group
2018-01-23 13:24:55 -05:00
accepts_nested_attributes_for :variables , allow_destroy : true
2016-03-18 08:28:16 -04:00
validate :visibility_level_allowed_by_projects
2017-08-29 01:49:01 -04:00
validate :visibility_level_allowed_by_sub_groups
2017-08-15 21:25:47 -04:00
validate :visibility_level_allowed_by_parent
2020-10-13 11:08:53 -04:00
validate :two_factor_authentication_allowed
2021-04-09 20:09:11 -04:00
validates :variables , nested_attributes_duplicates : { scope : :environment_scope }
2016-03-18 08:28:16 -04:00
2017-01-24 16:09:58 -05:00
validates :two_factor_grace_period , presence : true , numericality : { greater_than_or_equal_to : 0 }
2020-07-01 17:08:51 -04:00
2020-03-26 14:08:03 -04:00
validates :name ,
2020-07-01 17:08:51 -04:00
html_safety : true ,
format : { with : Gitlab :: Regex . group_name_regex ,
message : Gitlab :: Regex . group_name_regex_message } ,
if : :name_changed?
2017-01-24 16:09:58 -05:00
2022-02-25 19:14:54 -05:00
add_authentication_token_field :runners_token ,
encrypted : - > { Feature . enabled? ( :groups_tokens_optional_encryption , default_enabled : true ) ? :optional : :required } ,
prefix : - > ( instance ) { instance . runners_token_prefix }
2017-09-06 09:46:57 -04:00
2014-08-21 06:53:32 -04:00
after_create :post_create_hook
after_destroy :post_destroy_hook
2017-01-24 16:09:58 -05:00
after_save :update_two_factor_requirement
2019-01-15 16:05:36 -05:00
after_update :path_changed_hook , if : :saved_change_to_path?
2014-08-21 06:53:32 -04:00
2019-07-04 05:13:50 -04:00
scope :with_users , - > { includes ( :users ) }
2021-05-07 17:10:34 -04:00
scope :with_onboarding_progress , - > { joins ( :onboarding_progress ) }
2020-07-06 05:09:20 -04:00
scope :by_id , - > ( groups ) { where ( id : groups ) }
2022-01-06 19:13:12 -05:00
scope :by_ids_or_paths , - > ( ids , paths ) { by_id ( ids ) . or ( where ( path : paths ) ) }
2020-10-30 14:08:56 -04:00
scope :for_authorized_group_members , - > ( user_ids ) do
joins ( :group_members )
2021-04-26 08:09:44 -04:00
. where ( members : { user_id : user_ids } )
2020-10-30 14:08:56 -04:00
. where ( " access_level >= ? " , Gitlab :: Access :: GUEST )
end
scope :for_authorized_project_members , - > ( user_ids ) do
joins ( projects : :project_authorizations )
2021-04-26 08:09:44 -04:00
. where ( project_authorizations : { user_id : user_ids } )
2020-10-30 14:08:56 -04:00
end
2015-02-05 22:15:05 -05:00
class << self
2018-04-04 05:19:47 -04:00
def sort_by_attribute ( method )
2016-11-22 11:58:10 -05:00
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-02-05 22:15:05 -05:00
end
2015-05-02 23:11:21 -04:00
def reference_prefix
2015-05-14 16:59:39 -04:00
User . reference_prefix
end
def reference_pattern
User . reference_pattern
2015-05-02 23:11:21 -04:00
end
2015-11-18 06:27:21 -05:00
2018-09-07 08:29:19 -04:00
# 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-18 06:27:21 -05:00
end
2016-10-11 08:25:17 -04:00
def select_for_project_authorization
if current_scope . joins_values . include? ( :shared_projects )
2016-11-21 08:33:58 -05:00
joins ( 'INNER JOIN namespaces project_namespace ON project_namespace.id = projects.namespace_id' )
2021-04-26 08:09:44 -04:00
. where ( project_namespace : { share_with_group_lock : false } )
2021-07-26 20:08:48 -04:00
. select ( " projects.id AS project_id " , " LEAST(project_group_links.group_access, members.access_level) AS access_level " )
2016-10-11 08:25:17 -04:00
else
super
end
end
2018-09-07 08:29:19 -04:00
2020-09-29 11:10:08 -04:00
def without_integration ( integration )
2021-05-12 08:10:24 -04:00
integrations = Integration
2020-09-29 11:10:08 -04:00
. select ( '1' )
2021-07-07 11:07:24 -04:00
. where ( " #{ Integration . table_name } .group_id = namespaces.id " )
2020-09-29 11:10:08 -04:00
. where ( type : integration . type )
2021-05-12 08:10:24 -04:00
where ( 'NOT EXISTS (?)' , integrations )
2020-09-29 11:10:08 -04:00
end
2021-01-25 07:09:07 -05:00
# 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-08 11:09:06 -04:00
# 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-09-27 11:10:13 -04:00
inner_groups = Group . where ( 'id = namespaces_with_emails_disabled.id' )
2021-11-08 07:12:07 -05:00
inner_query = inner_groups
. self_and_ancestors
2021-04-08 11:09:06 -04:00
. 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-09-27 11:10:13 -04:00
. where ( 'EXISTS (?)' , inner_query )
2021-04-08 11:09:06 -04:00
. pluck ( :id )
Set . new ( group_ids )
end
2022-01-06 19:13:12 -05:00
def get_ids_by_ids_or_paths ( ids , paths )
by_ids_or_paths ( ids , paths ) . pluck ( :id )
end
2018-09-07 08:29:19 -04:00
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-11-12 10:06:26 -05:00
authorized_groups = user . authorized_groups . arel . as ( 'authorized' )
2018-09-07 08:29:19 -04:00
groups_table . project ( 1 )
. from ( authorized_groups )
. where ( authorized_groups [ :id ] . eq ( groups_table [ :id ] ) )
. exists
end
2015-05-02 23:11:21 -04:00
end
2018-05-28 14:43:46 -04:00
# Overrides notification_settings has_many association
# This allows to apply notification settings from parent groups
# to child groups and projects.
2019-04-26 11:17:06 -04:00
def notification_settings ( hierarchy_order : nil )
2018-05-28 14:43:46 -04:00
source_type = self . class . base_class . name
2019-04-26 11:17:06 -04:00
settings = NotificationSetting . where ( source_type : source_type , source_id : self_and_ancestors_ids )
2018-05-28 14:43:46 -04:00
2019-04-26 11:17:06 -04:00
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 } " )
2018-05-28 14:43:46 -04:00
end
2019-05-13 16:52:57 -04:00
def notification_settings_for ( user , hierarchy_order : nil )
notification_settings ( hierarchy_order : hierarchy_order ) . where ( user : user )
end
2020-07-19 23:09:39 -04:00
def packages_feature_enabled?
:: Gitlab . config . packages . enabled
end
2020-11-13 16:09:31 -05:00
def dependency_proxy_feature_available?
:: Gitlab . config . dependency_proxy . enabled
end
2019-07-18 13:04:43 -04:00
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-04-23 20:09:28 -04:00
def to_reference ( _from = nil , target_project : nil , full : nil )
2017-02-13 06:26:33 -05:00
" #{ self . class . reference_prefix } #{ full_path } "
2015-02-05 22:15:05 -05:00
end
2020-04-06 14:09:37 -04:00
def web_url ( only_path : nil )
Gitlab :: UrlBuilder . build ( self , only_path : only_path )
2016-06-02 10:14:02 -04:00
end
2021-09-08 11:09:10 -04:00
def dependency_proxy_image_prefix
# The namespace path can include uppercase letters, which
# Docker doesn't allow. The proxy expects it to be downcased.
2021-10-05 11:12:53 -04:00
url = " #{ Gitlab :: Routing . url_helpers . group_url ( self ) . downcase } #{ DependencyProxy :: URL_SUFFIX } "
2021-09-08 11:09:10 -04:00
# Docker images do not include the protocol
url . partition ( '//' ) . last
end
2012-11-22 23:11:09 -05:00
def human_name
2016-12-13 10:00:06 -05:00
full_name
2012-11-22 23:11:09 -05:00
end
2012-12-30 07:26:19 -05:00
2017-08-24 17:21:10 -04:00
def visibility_level_allowed_by_parent? ( level = self . visibility_level )
2017-08-25 16:03:16 -04:00
return true unless parent_id && parent_id . nonzero?
2016-03-18 08:28:16 -04:00
2017-08-24 17:21:10 -04:00
level < = parent . visibility_level
end
2017-08-23 12:51:11 -04:00
2017-08-24 17:21:10 -04:00
def visibility_level_allowed_by_projects? ( level = self . visibility_level )
2017-08-25 16:54:05 -04:00
! projects . where ( 'visibility_level > ?' , level ) . exists?
2017-08-24 17:21:10 -04:00
end
2016-03-18 08:28:16 -04:00
2017-08-24 17:21:10 -04:00
def visibility_level_allowed_by_sub_groups? ( level = self . visibility_level )
2017-08-25 16:54:05 -04:00
! children . where ( 'visibility_level > ?' , level ) . exists?
2016-03-18 08:28:16 -04:00
end
2017-08-24 18:19:49 -04:00
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 )
2016-03-18 08:28:16 -04:00
end
2016-09-01 19:49:48 -04:00
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-04-23 11:48:26 -04:00
def owned_by? ( user )
owners . include? ( user )
end
2021-10-25 08:10:19 -04:00
def add_users ( users , access_level , current_user : nil , expires_at : nil , tasks_to_be_done : [ ] , tasks_project_id : nil )
2021-09-01 05:10:58 -04:00
Members :: Groups :: BulkCreatorService . add_users ( # rubocop:disable CodeReuse/ServiceClass
2016-09-16 11:54:21 -04:00
self ,
users ,
access_level ,
current_user : current_user ,
2021-10-25 08:10:19 -04:00
expires_at : expires_at ,
tasks_to_be_done : tasks_to_be_done ,
tasks_project_id : tasks_project_id
2016-09-16 11:54:21 -04:00
)
2013-06-18 09:56:31 -04:00
end
2018-05-31 08:00:36 -04:00
def add_user ( user , access_level , current_user : nil , expires_at : nil , ldap : false )
2021-08-17 08:08:42 -04:00
Members :: Groups :: CreatorService . new ( self , # rubocop:disable CodeReuse/ServiceClass
2021-06-28 14:08:39 -04:00
user ,
access_level ,
current_user : current_user ,
expires_at : expires_at ,
ldap : ldap )
. execute
2013-09-25 07:05:35 -04:00
end
2015-08-07 00:20:02 -04:00
def add_guest ( user , current_user = nil )
2016-09-16 11:54:21 -04:00
add_user ( user , :guest , current_user : current_user )
2015-08-07 00:20:02 -04:00
end
def add_reporter ( user , current_user = nil )
2016-09-16 11:54:21 -04:00
add_user ( user , :reporter , current_user : current_user )
2015-08-07 00:20:02 -04:00
end
def add_developer ( user , current_user = nil )
2016-09-16 11:54:21 -04:00
add_user ( user , :developer , current_user : current_user )
2015-08-07 00:20:02 -04:00
end
2018-07-11 10:36:08 -04:00
def add_maintainer ( user , current_user = nil )
add_user ( user , :maintainer , current_user : current_user )
2015-08-07 00:20:02 -04:00
end
2015-11-17 09:49:37 -05:00
def add_owner ( user , current_user = nil )
2016-09-16 11:54:21 -04:00
add_user ( user , :owner , current_user : current_user )
2015-11-17 09:49:37 -05:00
end
2017-11-01 13:35:14 -04:00
def member? ( user , min_access_level = Gitlab :: Access :: GUEST )
return false unless user
max_member_access_for_user ( user ) > = min_access_level
end
2015-11-17 09:49:37 -05:00
def has_owner? ( user )
2017-07-24 06:35:54 -04:00
return false unless user
2019-04-04 10:19:57 -04:00
members_with_parents . owners . exists? ( user_id : user )
2015-11-17 09:49:37 -05:00
end
2021-04-15 05:09:03 -04:00
def blocked_owners
members . blocked . where ( access_level : Gitlab :: Access :: OWNER )
end
2018-07-11 10:36:08 -04:00
def has_maintainer? ( user )
2017-07-24 06:35:54 -04:00
return false unless user
2019-04-04 10:19:57 -04:00
members_with_parents . maintainers . exists? ( user_id : user )
2015-11-17 09:49:37 -05:00
end
2019-11-08 04:06:07 -05:00
def has_container_repository_including_subgroups?
:: ContainerRepository . for_group_and_its_subgroups ( self ) . exists?
2019-10-16 05:07:51 -04:00
end
2016-12-13 13:59:39 -05:00
# Check if user is a last owner of the group.
2015-11-17 09:49:37 -05:00
def last_owner? ( user )
2021-04-15 05:09:03 -04:00
has_owner? ( user ) && single_owner?
end
def member_last_owner? ( member )
return member . last_owner unless member . last_owner . nil?
last_owner? ( member . user )
2015-11-17 09:49:37 -05:00
end
2021-04-15 05:09:03 -04:00
def single_owner?
members_with_parents . owners . size == 1
end
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-03-15 20:09:44 -04:00
return false if members_with_parents . owners . any?
2021-04-15 05:09:03 -04:00
single_blocked_owner? && blocked_owners . exists? ( user_id : member . user )
2021-03-15 20:09:44 -04:00
end
2018-05-31 08:00:36 -04:00
def ldap_synced?
false
end
2014-08-21 06:53:32 -04:00
def post_create_hook
2015-06-03 10:15:58 -04:00
Gitlab :: AppLogger . info ( " Group \" #{ name } \" was created " )
2014-08-21 06:53:32 -04:00
system_hook_service . execute_hooks_for ( self , :create )
end
def post_destroy_hook
2015-06-03 10:15:58 -04:00
Gitlab :: AppLogger . info ( " Group \" #{ name } \" was removed " )
2014-08-21 06:53:32 -04:00
system_hook_service . execute_hooks_for ( self , :destroy )
end
2018-08-27 11:31:01 -04:00
# rubocop: disable CodeReuse/ServiceClass
2014-08-21 06:53:32 -04:00
def system_hook_service
SystemHooksService . new
end
2018-08-27 11:31:01 -04:00
# rubocop: enable CodeReuse/ServiceClass
2016-10-11 08:25:17 -04:00
2018-08-27 11:31:01 -04:00
# rubocop: disable CodeReuse/ServiceClass
2021-02-19 10:10:30 -05:00
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-15 08:08:28 -04:00
UserProjectAccessChangedService
2021-02-19 10:10:30 -05:00
. new ( user_ids )
2020-05-15 08:08:28 -04:00
. execute ( blocking : blocking , priority : priority )
2017-02-07 08:55:42 -05:00
end
2018-08-27 11:31:01 -04:00
# rubocop: enable CodeReuse/ServiceClass
2017-02-07 08:55:42 -05:00
2021-02-19 10:10:30 -05:00
def users_ids_of_direct_members
direct_members . pluck ( :user_id )
end
2017-02-07 08:55:42 -05:00
def user_ids_for_project_authorizations
2021-02-22 13:10:55 -05:00
members_with_parents . pluck ( Arel . sql ( 'DISTINCT members.user_id' ) )
2016-12-13 13:59:39 -05:00
end
2018-05-28 14:43:46 -04:00
def self_and_ancestors_ids
strong_memoize ( :self_and_ancestors_ids ) do
self_and_ancestors . pluck ( :id )
end
end
2021-06-09 05:10:18 -04:00
def self_and_descendants_ids
strong_memoize ( :self_and_descendants_ids ) do
self_and_descendants . pluck ( :id )
end
end
2021-02-19 10:10:30 -05:00
def direct_members
GroupMember . active_without_invites_and_requests
. non_minimal_access
. where ( source_id : id )
end
2021-05-12 14:10:35 -04:00
def authorizable_members_with_parents
source_ids =
if has_parent?
self_and_ancestors . reorder ( nil ) . select ( :id )
else
id
end
2021-07-26 20:08:48 -04:00
group_hierarchy_members = GroupMember . where ( source_id : source_ids ) . select ( * GroupMember . cached_column_list )
2021-05-12 14:10:35 -04:00
GroupMember . from_union ( [ group_hierarchy_members ,
members_from_self_and_ancestor_group_shares ] ) . authorizable
end
2016-12-13 13:59:39 -05:00
def members_with_parents
2017-08-11 10:19:11 -04:00
# Avoids an unnecessary SELECT when the group has no parents
source_ids =
2020-06-04 11:08:21 -04:00
if has_parent?
2017-08-11 10:19:11 -04:00
self_and_ancestors . reorder ( nil ) . select ( :id )
else
id
end
2020-06-04 11:08:21 -04:00
group_hierarchy_members = GroupMember . active_without_invites_and_requests
2020-10-01 05:09:54 -04:00
. non_minimal_access
2020-06-04 11:08:21 -04:00
. where ( source_id : source_ids )
2021-07-26 20:08:48 -04:00
. select ( * GroupMember . cached_column_list )
2020-06-04 11:08:21 -04:00
GroupMember . from_union ( [ group_hierarchy_members ,
members_from_self_and_ancestor_group_shares ] )
2017-08-11 10:19:11 -04:00
end
2020-05-12 08:09:47 -04:00
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-08-11 10:19:11 -04:00
def members_with_descendants
GroupMember
2018-03-21 12:14:32 -04:00
. active_without_invites_and_requests
2017-08-11 10:19:11 -04:00
. where ( source_id : self_and_descendants . reorder ( nil ) . select ( :id ) )
2016-12-13 13:59:39 -05:00
end
2018-04-26 15:53:13 -04:00
# 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
2020-11-26 19:09:42 -05:00
def direct_and_indirect_members_with_inactive
GroupMember
. non_request
. non_invite
. where ( source_id : self_and_hierarchy . reorder ( nil ) . select ( :id ) )
end
2016-12-13 13:59:39 -05:00
def users_with_parents
2017-08-11 10:19:11 -04:00
User
. where ( id : members_with_parents . select ( :user_id ) )
. reorder ( nil )
2016-10-11 08:25:17 -04:00
end
2017-03-01 14:34:29 -05:00
2017-06-27 16:35:35 -04:00
def users_with_descendants
2017-08-11 10:19:11 -04:00
User
. where ( id : members_with_descendants . select ( :user_id ) )
. reorder ( nil )
2017-06-27 16:35:35 -04:00
end
2018-04-26 15:53:13 -04:00
# 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-09-11 11:31:34 -04:00
User . from_union ( [
2018-04-26 15:53:13 -04:00
User
. where ( id : direct_and_indirect_members . select ( :user_id ) )
. reorder ( nil ) ,
project_users_with_descendants
] )
end
2020-11-26 19:09:42 -05:00
# 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-09-10 14:08:54 -04:00
def users_count
members . count
end
2018-04-26 15:53:13 -04:00
# 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-09-16 11:09:32 -04:00
# 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-08-28 06:09:45 -04:00
return GroupMember :: NO_ACCESS unless user
2021-03-09 13:09:41 -05:00
return GroupMember :: OWNER if user . can_admin_all_resources? && ! only_concrete_membership
2017-06-02 10:13:10 -04:00
2021-05-25 02:10:50 -04:00
max_member_access ( [ user . id ] ) [ user . id ]
2017-06-02 10:13:10 -04:00
end
2017-03-01 14:34:29 -05:00
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
2017-01-24 16:09:58 -05:00
2021-03-09 22:09:10 -05:00
def ci_variables_for ( ref , project , environment : nil )
cache_key = " ci_variables_for:group: #{ self & . id } :project: #{ project & . id } :ref: #{ ref } :environment: #{ environment } "
2020-02-28 16:09:15 -05:00
:: Gitlab :: SafeRequestStore . fetch ( cache_key ) do
2021-03-09 22:09:10 -05:00
uncached_ci_variables_for ( ref , project , environment : environment )
2020-02-28 16:09:15 -05:00
end
2017-05-03 14:51:55 -04:00
end
2022-01-12 10:13:54 -05:00
def member ( user )
2017-11-30 06:52:38 -05:00
if group_members . loaded?
group_members . find { | gm | gm . user_id == user . id }
else
group_members . find_by ( user_id : user )
end
end
2019-01-15 12:57:17 -05:00
def highest_group_member ( user )
GroupMember . where ( source_id : self_and_ancestors_ids , user_id : user . id ) . order ( :access_level ) . last
end
2022-01-07 19:14:32 -05:00
def bots
users . project_bot
end
2020-01-13 10:07:53 -05:00
def related_group_ids
[ id ,
* ancestors . pluck ( :id ) ,
* shared_with_group_links . pluck ( :shared_with_group_id ) ]
end
2017-12-06 06:36:11 -05:00
def hashed_storage? ( _feature )
false
end
2018-04-06 11:23:49 -04:00
def refresh_project_authorizations
refresh_members_authorized_projects ( blocking : false )
end
2017-10-04 07:57:50 -04:00
# 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
2022-02-25 19:14:54 -05:00
def runners_token_prefix
Feature . enabled? ( :groups_runners_token_prefix , self , default_enabled : :yaml ) ? RUNNERS_TOKEN_PREFIX : ''
end
override :format_runners_token
def format_runners_token ( token )
" #{ runners_token_prefix } #{ token } "
end
2019-04-05 14:49:46 -04:00
def project_creation_level
super || :: Gitlab :: CurrentSettings . default_project_creation
end
2019-07-01 16:41:32 -04:00
def subgroup_creation_level
2019-07-10 15:27:12 -04:00
super || :: Gitlab :: Access :: OWNER_SUBGROUP_ACCESS
2019-07-01 16:41:32 -04:00
end
2019-08-30 17:30:51 -04:00
def access_request_approvers_to_be_notified
2021-06-01 14:10:04 -04:00
members . owners . connected_to_user . order_recent_sign_in . limit ( Member :: ACCESS_REQUEST_APPROVERS_TO_BE_NOTIFIED_LIMIT )
2019-08-30 17:30:51 -04:00
end
2021-09-09 05:11:16 -04:00
def membership_locked?
false # to support project and group calling this as 'source'
end
2019-10-11 02:06:27 -04:00
def supports_events?
false
end
2019-11-14 07:06:30 -05:00
def export_file_exists?
2021-06-15 02:10:17 -04:00
import_export_upload & . export_file_exists?
2019-11-14 07:06:30 -05:00
end
def export_file
import_export_upload & . export_file
end
2021-06-15 02:10:17 -04:00
def export_archive_exists?
import_export_upload & . export_archive_exists?
end
2020-01-24 10:09:00 -05:00
def adjourned_deletion?
false
end
2020-05-13 08:07:54 -04:00
def execute_hooks ( data , hooks_scope )
# NOOP
# TODO: group hooks https://gitlab.com/gitlab-org/gitlab/-/issues/216904
end
2021-06-30 02:07:17 -04:00
def execute_integrations ( data , hooks_scope )
2020-05-13 08:07:54 -04:00
# NOOP
# TODO: group hooks https://gitlab.com/gitlab-org/gitlab/-/issues/216904
end
2020-06-09 14:08:28 -04:00
def preload_shared_group_links
preloader = ActiveRecord :: Associations :: Preloader . new
preloader . preload ( self , shared_with_group_links : [ shared_with_group : :route ] )
end
2020-09-30 11:09:46 -04:00
def update_shared_runners_setting! ( state )
raise ArgumentError unless SHARED_RUNNERS_SETTINGS . include? ( state )
2020-07-06 05:09:20 -04:00
2020-09-30 11:09:46 -04:00
case state
2021-10-13 11:12:51 -04:00
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
2020-09-30 11:09:46 -04:00
end
2020-07-06 05:09:20 -04:00
end
2022-01-18 22:14:09 -05:00
def first_owner
owners . first || parent & . first_owner || owner
2020-08-17 05:10:08 -04:00
end
2020-10-15 08:09:06 -04:00
def default_branch_name
namespace_settings & . default_branch_name
end
2020-10-01 05:09:54 -04:00
def access_level_roles
GroupMember . access_level_roles
end
def access_level_values
access_level_roles . values
end
2020-10-15 17:09:12 -04:00
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
2020-11-10 10:09:14 -05:00
def has_project_with_service_desk_enabled?
Gitlab :: ServiceDesk . supported? && all_projects . service_desk_enabled . exists?
end
2021-05-05 14:10:31 -04:00
def activity_path
Gitlab :: Routing . url_helpers . activity_group_path ( self )
end
2021-06-15 11:10:04 -04:00
# 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-08-10 08:11:00 -04:00
def timelogs
Timelog . in_group ( self )
end
2021-09-13 20:10:45 -04:00
def dependency_proxy_image_ttl_policy
super || build_dependency_proxy_image_ttl_policy
end
2022-01-18 13:11:20 -05:00
def dependency_proxy_setting
super || build_dependency_proxy_setting
end
2021-12-23 19:15:07 -05:00
def crm_enabled?
crm_settings & . enabled?
end
2022-01-16 19:14:49 -05:00
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
2022-01-19 07:17:41 -05:00
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
2017-08-29 01:49:01 -04:00
private
2017-01-24 16:09:58 -05:00
2021-05-25 02:10:50 -04:00
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-01-24 16:09:58 -05:00
def update_two_factor_requirement
2019-01-15 16:05:36 -05:00
return unless saved_change_to_require_two_factor_authentication? || saved_change_to_two_factor_grace_period?
2017-01-24 16:09:58 -05:00
2020-11-04 10:08:41 -05:00
direct_and_indirect_members . find_each ( & :update_two_factor_requirement )
2017-01-24 16:09:58 -05:00
end
2017-08-29 01:49:01 -04:00
2017-11-03 07:26:52 -04:00
def path_changed_hook
system_hook_service . execute_hooks_for ( self , :rename )
end
2017-08-29 01:49:01 -04:00
def visibility_level_allowed_by_parent
return if visibility_level_allowed_by_parent?
2017-08-30 15:37:08 -04:00
errors . add ( :visibility_level , " #{ visibility } is not allowed since the parent group has a #{ parent . visibility } visibility. " )
2017-08-29 01:49:01 -04:00
end
def visibility_level_allowed_by_projects
return if visibility_level_allowed_by_projects?
2017-08-30 15:37:08 -04:00
errors . add ( :visibility_level , " #{ visibility } is not allowed since this group contains projects with higher visibility. " )
2017-08-29 01:49:01 -04:00
end
def visibility_level_allowed_by_sub_groups
return if visibility_level_allowed_by_sub_groups?
2017-08-30 15:37:08 -04:00
errors . add ( :visibility_level , " #{ visibility } is not allowed since there are sub-groups with higher visibility. " )
2017-08-29 01:49:01 -04:00
end
2019-10-17 11:06:17 -04:00
2020-10-13 11:08:53 -04:00
def two_factor_authentication_allowed
return unless has_parent?
return unless require_two_factor_authentication
2020-10-15 17:09:12 -04:00
return if parent_allows_two_factor_authentication?
2020-10-13 11:08:53 -04:00
errors . add ( :require_two_factor_authentication , _ ( 'is forbidden by a top-level group' ) )
end
2020-06-04 11:08:21 -04:00
def members_from_self_and_ancestor_group_shares
2019-10-30 11:14:17 -04:00
group_group_link_table = GroupGroupLink . arel_table
group_member_table = GroupMember . arel_table
2020-06-04 11:08:21 -04:00
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-10-30 11:14:17 -04:00
cte = Gitlab :: SQL :: CTE . new ( :group_group_links_cte , group_group_links_query )
2020-03-04 16:07:54 -05:00
cte_alias = cte . table . alias ( GroupGroupLink . table_name )
2019-10-30 11:14:17 -04:00
2020-06-04 11:08:21 -04:00
# 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' ) )
2022-02-17 10:18:08 -05:00
. where ( group_member_table [ :state ] . eq ( :: Member :: STATE_ACTIVE ) )
2020-09-10 14:08:54 -04:00
. non_minimal_access
2019-10-30 11:14:17 -04:00
end
2020-03-04 16:07:54 -05:00
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-10-17 11:06:17 -04:00
def self . groups_including_descendants_by ( group_ids )
2021-11-22 22:12:38 -05:00
Group . where ( id : group_ids ) . self_and_descendants
2019-10-17 11:06:17 -04:00
end
2020-09-30 11:09:46 -04:00
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-03-09 22:09:10 -05:00
def uncached_ci_variables_for ( ref , project , environment : nil )
2021-05-06 05:18:56 -04:00
list_of_ids = if root_ancestor . use_traversal_ids?
[ self ] + ancestors ( hierarchy_order : :asc )
else
[ self ] + ancestors
end
2021-03-09 22:09:10 -05:00
variables = Ci :: GroupVariable . where ( group : list_of_ids )
variables = variables . unprotected unless project . protected_for? ( ref )
2021-04-15 23:09:00 -04:00
variables = if environment
variables . on_environment ( environment )
else
variables . where ( environment_scope : '*' )
end
2021-03-09 22:09:10 -05:00
variables = variables . group_by ( & :group_id )
list_of_ids . reverse . flat_map { | group | variables [ group . id ] } . compact
end
2012-10-02 11:17:12 -04:00
end
2019-09-13 09:26:31 -04:00
2021-05-11 17:10:21 -04:00
Group . prepend_mod_with ( 'Group' )