1# frozen_string_literal: true 2 3require 'carrierwave/orm/activerecord' 4 5class User < ApplicationRecord 6 extend Gitlab::ConfigHelper 7 8 include Gitlab::ConfigHelper 9 include Gitlab::SQL::Pattern 10 include AfterCommitQueue 11 include Avatarable 12 include Referable 13 include Sortable 14 include CaseSensitivity 15 include TokenAuthenticatable 16 include FeatureGate 17 include CreatedAtFilterable 18 include BulkMemberAccessLoad 19 include BlocksJsonSerialization 20 include WithUploads 21 include OptionallySearch 22 include FromUnion 23 include BatchDestroyDependentAssociations 24 include HasUniqueInternalUsers 25 include IgnorableColumns 26 include UpdateHighestRole 27 include HasUserType 28 include Gitlab::Auth::Otp::Fortinet 29 include RestrictedSignup 30 include StripAttribute 31 32 DEFAULT_NOTIFICATION_LEVEL = :participating 33 34 INSTANCE_ACCESS_REQUEST_APPROVERS_TO_BE_NOTIFIED_LIMIT = 10 35 36 BLOCKED_PENDING_APPROVAL_STATE = 'blocked_pending_approval' 37 38 COUNT_CACHE_VALIDITY_PERIOD = 24.hours 39 40 MAX_USERNAME_LENGTH = 255 41 MIN_USERNAME_LENGTH = 2 42 43 SECONDARY_EMAIL_ATTRIBUTES = [ 44 :commit_email, 45 :notification_email, 46 :public_email 47 ].freeze 48 49 add_authentication_token_field :incoming_email_token, token_generator: -> { SecureRandom.hex.to_i(16).to_s(36) } 50 add_authentication_token_field :feed_token 51 add_authentication_token_field :static_object_token 52 53 default_value_for :admin, false 54 default_value_for(:external) { Gitlab::CurrentSettings.user_default_external } 55 default_value_for :can_create_group, gitlab_config.default_can_create_group 56 default_value_for :can_create_team, false 57 default_value_for :hide_no_ssh_key, false 58 default_value_for :hide_no_password, false 59 default_value_for :project_view, :files 60 default_value_for :notified_of_own_activity, false 61 default_value_for :preferred_language, I18n.default_locale 62 default_value_for :theme_id, gitlab_config.default_theme 63 64 attr_encrypted :otp_secret, 65 key: Gitlab::Application.secrets.otp_key_base, 66 mode: :per_attribute_iv_and_salt, 67 insecure_mode: true, 68 algorithm: 'aes-256-cbc' 69 70 devise :two_factor_authenticatable, 71 otp_secret_encryption_key: Gitlab::Application.secrets.otp_key_base 72 73 devise :two_factor_backupable, otp_number_of_backup_codes: 10 74 serialize :otp_backup_codes, JSON # rubocop:disable Cop/ActiveRecordSerialize 75 76 devise :lockable, :recoverable, :rememberable, :trackable, 77 :validatable, :omniauthable, :confirmable, :registerable 78 79 include AdminChangedPasswordNotifier 80 81 # This module adds async behaviour to Devise emails 82 # and should be added after Devise modules are initialized. 83 include AsyncDeviseEmail 84 85 MINIMUM_INACTIVE_DAYS = 90 86 87 # Override Devise::Models::Trackable#update_tracked_fields! 88 # to limit database writes to at most once every hour 89 # rubocop: disable CodeReuse/ServiceClass 90 def update_tracked_fields!(request) 91 return if Gitlab::Database.read_only? 92 93 update_tracked_fields(request) 94 95 Gitlab::ExclusiveLease.throttle(id) do 96 ::Ability.forgetting(/admin/) do 97 Users::UpdateService.new(self, user: self).execute(validate: false) 98 end 99 end 100 end 101 # rubocop: enable CodeReuse/ServiceClass 102 103 attr_accessor :force_random_password 104 105 # Virtual attribute for authenticating by either username or email 106 attr_accessor :login 107 108 # Virtual attribute for impersonator 109 attr_accessor :impersonator 110 111 # 112 # Relations 113 # 114 115 # Namespace for personal projects 116 has_one :namespace, 117 -> { where(type: Namespaces::UserNamespace.sti_name) }, 118 dependent: :destroy, # rubocop:disable Cop/ActiveRecordDependent 119 foreign_key: :owner_id, 120 inverse_of: :owner, 121 autosave: true # rubocop:disable Cop/ActiveRecordDependent 122 123 # Profile 124 has_many :keys, -> { regular_keys }, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent 125 has_many :expired_today_and_unnotified_keys, -> { expired_today_and_not_notified }, class_name: 'Key' 126 has_many :expiring_soon_and_unnotified_keys, -> { expiring_soon_and_not_notified }, class_name: 'Key' 127 has_many :deploy_keys, -> { where(type: 'DeployKey') }, dependent: :nullify # rubocop:disable Cop/ActiveRecordDependent 128 has_many :group_deploy_keys 129 has_many :gpg_keys 130 131 has_many :emails 132 has_many :personal_access_tokens, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent 133 has_many :identities, dependent: :destroy, autosave: true # rubocop:disable Cop/ActiveRecordDependent 134 has_many :u2f_registrations, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent 135 has_many :webauthn_registrations 136 has_many :chat_names, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent 137 has_one :user_synced_attributes_metadata, autosave: true 138 has_one :aws_role, class_name: 'Aws::Role' 139 140 # Followers 141 has_many :followed_users, foreign_key: :follower_id, class_name: 'Users::UserFollowUser' 142 has_many :followees, through: :followed_users 143 144 has_many :following_users, foreign_key: :followee_id, class_name: 'Users::UserFollowUser' 145 has_many :followers, through: :following_users 146 147 # Groups 148 has_many :members 149 has_many :group_members, -> { where(requested_at: nil).where("access_level >= ?", Gitlab::Access::GUEST) }, class_name: 'GroupMember' 150 has_many :groups, through: :group_members 151 has_many :owned_groups, -> { where(members: { access_level: Gitlab::Access::OWNER }) }, through: :group_members, source: :group 152 has_many :maintainers_groups, -> { where(members: { access_level: Gitlab::Access::MAINTAINER }) }, through: :group_members, source: :group 153 has_many :developer_groups, -> { where(members: { access_level: ::Gitlab::Access::DEVELOPER }) }, through: :group_members, source: :group 154 has_many :owned_or_maintainers_groups, 155 -> { where(members: { access_level: [Gitlab::Access::MAINTAINER, Gitlab::Access::OWNER] }) }, 156 through: :group_members, 157 source: :group 158 alias_attribute :masters_groups, :maintainers_groups 159 has_many :reporter_developer_maintainer_owned_groups, 160 -> { where(members: { access_level: [Gitlab::Access::REPORTER, Gitlab::Access::DEVELOPER, Gitlab::Access::MAINTAINER, Gitlab::Access::OWNER] }) }, 161 through: :group_members, 162 source: :group 163 has_many :minimal_access_group_members, -> { where(access_level: [Gitlab::Access::MINIMAL_ACCESS]) }, class_name: 'GroupMember' 164 has_many :minimal_access_groups, through: :minimal_access_group_members, source: :group 165 166 # Projects 167 has_many :groups_projects, through: :groups, source: :projects 168 has_many :personal_projects, through: :namespace, source: :projects 169 has_many :project_members, -> { where(requested_at: nil) } 170 has_many :projects, through: :project_members 171 has_many :created_projects, foreign_key: :creator_id, class_name: 'Project' 172 has_many :users_star_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent 173 has_many :starred_projects, through: :users_star_projects, source: :project 174 has_many :project_authorizations, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent 175 has_many :authorized_projects, through: :project_authorizations, source: :project 176 177 has_many :user_interacted_projects 178 has_many :project_interactions, through: :user_interacted_projects, source: :project, class_name: 'Project' 179 180 has_many :snippets, dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent 181 has_many :notes, dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent 182 has_many :issues, dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent 183 has_many :merge_requests, dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent 184 has_many :events, dependent: :delete_all, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent 185 has_many :releases, dependent: :nullify, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent 186 has_many :subscriptions, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent 187 has_many :oauth_applications, class_name: 'Doorkeeper::Application', as: :owner, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent 188 has_one :abuse_report, dependent: :destroy, foreign_key: :user_id # rubocop:disable Cop/ActiveRecordDependent 189 has_many :reported_abuse_reports, dependent: :destroy, foreign_key: :reporter_id, class_name: "AbuseReport" # rubocop:disable Cop/ActiveRecordDependent 190 has_many :spam_logs, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent 191 has_many :builds, class_name: 'Ci::Build' 192 has_many :pipelines, class_name: 'Ci::Pipeline' 193 has_many :todos 194 has_many :notification_settings 195 has_many :award_emoji, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent 196 has_many :triggers, class_name: 'Ci::Trigger', foreign_key: :owner_id 197 198 has_many :issue_assignees, inverse_of: :assignee 199 has_many :merge_request_assignees, inverse_of: :assignee 200 has_many :merge_request_reviewers, inverse_of: :reviewer 201 has_many :assigned_issues, class_name: "Issue", through: :issue_assignees, source: :issue 202 has_many :assigned_merge_requests, class_name: "MergeRequest", through: :merge_request_assignees, source: :merge_request 203 has_many :created_custom_emoji, class_name: 'CustomEmoji', inverse_of: :creator 204 205 has_many :bulk_imports 206 207 has_many :custom_attributes, class_name: 'UserCustomAttribute' 208 has_many :callouts, class_name: 'Users::Callout' 209 has_many :group_callouts, class_name: 'Users::GroupCallout' 210 has_many :term_agreements 211 belongs_to :accepted_term, class_name: 'ApplicationSetting::Term' 212 213 has_many :metrics_users_starred_dashboards, class_name: 'Metrics::UsersStarredDashboard', inverse_of: :user 214 215 has_one :status, class_name: 'UserStatus' 216 has_one :user_preference 217 has_one :user_detail 218 has_one :user_highest_role 219 has_one :user_canonical_email 220 has_one :credit_card_validation, class_name: '::Users::CreditCardValidation' 221 has_one :atlassian_identity, class_name: 'Atlassian::Identity' 222 has_one :banned_user, class_name: '::Users::BannedUser' 223 224 has_many :reviews, foreign_key: :author_id, inverse_of: :author 225 226 has_many :in_product_marketing_emails, class_name: '::Users::InProductMarketingEmail' 227 228 has_many :timelogs 229 230 # 231 # Validations 232 # 233 # Note: devise :validatable above adds validations for :email and :password 234 validates :name, presence: true, length: { maximum: 255 } 235 validates :first_name, length: { maximum: 127 } 236 validates :last_name, length: { maximum: 127 } 237 validates :email, confirmation: true 238 validates :notification_email, devise_email: true, allow_blank: true 239 validates :public_email, uniqueness: true, devise_email: true, allow_blank: true 240 validates :commit_email, devise_email: true, allow_blank: true, unless: ->(user) { user.commit_email == Gitlab::PrivateCommitEmail::TOKEN } 241 validates :projects_limit, 242 presence: true, 243 numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: Gitlab::Database::MAX_INT_VALUE } 244 validates :username, presence: true 245 246 validates :namespace, presence: true 247 validate :namespace_move_dir_allowed, if: :username_changed? 248 249 validate :unique_email, if: :email_changed? 250 validate :notification_email_verified, if: :notification_email_changed? 251 validate :public_email_verified, if: :public_email_changed? 252 validate :commit_email_verified, if: :commit_email_changed? 253 validate :signup_email_valid?, on: :create, if: ->(user) { !user.created_by_id } 254 validate :check_username_format, if: :username_changed? 255 256 validates :theme_id, allow_nil: true, inclusion: { in: Gitlab::Themes.valid_ids, 257 message: _("%{placeholder} is not a valid theme") % { placeholder: '%{value}' } } 258 259 validates :color_scheme_id, allow_nil: true, inclusion: { in: Gitlab::ColorSchemes.valid_ids, 260 message: _("%{placeholder} is not a valid color scheme") % { placeholder: '%{value}' } } 261 262 validates :website_url, allow_blank: true, url: true, if: :website_url_changed? 263 264 before_validation :sanitize_attrs 265 before_save :default_private_profile_to_false 266 before_save :ensure_incoming_email_token 267 before_save :ensure_user_rights_and_limits, if: ->(user) { user.new_record? || user.external_changed? } 268 before_save :skip_reconfirmation!, if: ->(user) { user.email_changed? && user.read_only_attribute?(:email) } 269 before_save :check_for_verified_email, if: ->(user) { user.email_changed? && !user.new_record? } 270 before_validation :ensure_namespace_correct 271 before_save :ensure_namespace_correct # in case validation is skipped 272 after_validation :set_username_errors 273 after_update :username_changed_hook, if: :saved_change_to_username? 274 after_destroy :post_destroy_hook 275 after_destroy :remove_key_cache 276 after_create :add_primary_email_to_emails!, if: :confirmed? 277 after_commit(on: :update) do 278 if previous_changes.key?('email') 279 # Add the old primary email to Emails if not added already - this should be removed 280 # after the background migration for MR https://gitlab.com/gitlab-org/gitlab/-/merge_requests/70872/ has completed, 281 # as the primary email is now added to Emails upon confirmation 282 # Issue to remove that: https://gitlab.com/gitlab-org/gitlab/-/issues/344134 283 previous_confirmed_at = previous_changes.key?('confirmed_at') ? previous_changes['confirmed_at'][0] : confirmed_at 284 previous_email = previous_changes[:email][0] 285 if previous_confirmed_at && !emails.exists?(email: previous_email) 286 # rubocop: disable CodeReuse/ServiceClass 287 Emails::CreateService.new(self, user: self, email: previous_email).execute(confirmed_at: previous_confirmed_at) 288 # rubocop: enable CodeReuse/ServiceClass 289 end 290 291 update_invalid_gpg_signatures 292 end 293 end 294 295 after_initialize :set_projects_limit 296 297 # User's Layout preference 298 enum layout: { fixed: 0, fluid: 1 } 299 300 # User's Dashboard preference 301 enum dashboard: { projects: 0, stars: 1, project_activity: 2, starred_project_activity: 3, groups: 4, todos: 5, issues: 6, merge_requests: 7, operations: 8, followed_user_activity: 9 } 302 303 # User's Project preference 304 enum project_view: { readme: 0, activity: 1, files: 2 } 305 306 # User's role 307 enum role: { software_developer: 0, development_team_lead: 1, devops_engineer: 2, systems_administrator: 3, security_analyst: 4, data_analyst: 5, product_manager: 6, product_designer: 7, other: 8 }, _suffix: true 308 309 delegate :notes_filter_for, 310 :set_notes_filter, 311 :first_day_of_week, :first_day_of_week=, 312 :timezone, :timezone=, 313 :time_display_relative, :time_display_relative=, 314 :time_format_in_24h, :time_format_in_24h=, 315 :show_whitespace_in_diffs, :show_whitespace_in_diffs=, 316 :view_diffs_file_by_file, :view_diffs_file_by_file=, 317 :tab_width, :tab_width=, 318 :sourcegraph_enabled, :sourcegraph_enabled=, 319 :gitpod_enabled, :gitpod_enabled=, 320 :setup_for_company, :setup_for_company=, 321 :render_whitespace_in_code, :render_whitespace_in_code=, 322 :markdown_surround_selection, :markdown_surround_selection=, 323 to: :user_preference 324 325 delegate :path, to: :namespace, allow_nil: true, prefix: true 326 delegate :job_title, :job_title=, to: :user_detail, allow_nil: true 327 delegate :other_role, :other_role=, to: :user_detail, allow_nil: true 328 delegate :bio, :bio=, to: :user_detail, allow_nil: true 329 delegate :webauthn_xid, :webauthn_xid=, to: :user_detail, allow_nil: true 330 delegate :pronouns, :pronouns=, to: :user_detail, allow_nil: true 331 delegate :pronunciation, :pronunciation=, to: :user_detail, allow_nil: true 332 delegate :registration_objective, :registration_objective=, to: :user_detail, allow_nil: true 333 334 accepts_nested_attributes_for :user_preference, update_only: true 335 accepts_nested_attributes_for :user_detail, update_only: true 336 accepts_nested_attributes_for :credit_card_validation, update_only: true, allow_destroy: true 337 338 state_machine :state, initial: :active do 339 event :block do 340 transition active: :blocked 341 transition deactivated: :blocked 342 transition ldap_blocked: :blocked 343 transition blocked_pending_approval: :blocked 344 end 345 346 event :ldap_block do 347 transition active: :ldap_blocked 348 transition deactivated: :ldap_blocked 349 end 350 351 event :activate do 352 transition deactivated: :active 353 transition blocked: :active 354 transition ldap_blocked: :active 355 transition blocked_pending_approval: :active 356 transition banned: :active 357 end 358 359 event :block_pending_approval do 360 transition active: :blocked_pending_approval 361 end 362 363 event :ban do 364 transition active: :banned 365 end 366 367 event :unban do 368 transition banned: :active 369 end 370 371 event :deactivate do 372 # Any additional changes to this event should be also 373 # reflected in app/workers/users/deactivate_dormant_users_worker.rb 374 transition active: :deactivated 375 end 376 377 state :blocked, :ldap_blocked, :blocked_pending_approval, :banned do 378 def blocked? 379 true 380 end 381 end 382 383 before_transition do 384 !Gitlab::Database.read_only? 385 end 386 387 # rubocop: disable CodeReuse/ServiceClass 388 # Ideally we should not call a service object here but user.block 389 # is also bcalled by Users::MigrateToGhostUserService which references 390 # this state transition object in order to do a rollback. 391 # For this reason the tradeoff is to disable this cop. 392 after_transition any => :blocked do |user| 393 user.run_after_commit do 394 Ci::DropPipelineService.new.execute_async_for_all(user.pipelines, :user_blocked, user) 395 Ci::DisableUserPipelineSchedulesService.new.execute(user) 396 end 397 end 398 399 after_transition any => :deactivated do |user| 400 next unless Gitlab::CurrentSettings.user_deactivation_emails_enabled 401 402 NotificationService.new.user_deactivated(user.name, user.notification_email_or_default) 403 end 404 # rubocop: enable CodeReuse/ServiceClass 405 406 after_transition active: :banned do |user| 407 user.create_banned_user 408 end 409 410 after_transition banned: :active do |user| 411 user.banned_user&.destroy 412 end 413 end 414 415 # Scopes 416 scope :admins, -> { where(admin: true) } 417 scope :instance_access_request_approvers_to_be_notified, -> { admins.active.order_recent_sign_in.limit(INSTANCE_ACCESS_REQUEST_APPROVERS_TO_BE_NOTIFIED_LIMIT) } 418 scope :blocked, -> { with_states(:blocked, :ldap_blocked) } 419 scope :blocked_pending_approval, -> { with_states(:blocked_pending_approval) } 420 scope :banned, -> { with_states(:banned) } 421 scope :external, -> { where(external: true) } 422 scope :non_external, -> { where(external: false) } 423 scope :confirmed, -> { where.not(confirmed_at: nil) } 424 scope :active, -> { with_state(:active).non_internal } 425 scope :active_without_ghosts, -> { with_state(:active).without_ghosts } 426 scope :deactivated, -> { with_state(:deactivated).non_internal } 427 scope :without_projects, -> { joins('LEFT JOIN project_authorizations ON users.id = project_authorizations.user_id').where(project_authorizations: { user_id: nil }) } 428 scope :by_username, -> (usernames) { iwhere(username: Array(usernames).map(&:to_s)) } 429 scope :by_name, -> (names) { iwhere(name: Array(names)) } 430 scope :by_user_email, -> (emails) { iwhere(email: Array(emails)) } 431 scope :by_emails, -> (emails) { joins(:emails).where(emails: { email: Array(emails).map(&:downcase) }) } 432 scope :for_todos, -> (todos) { where(id: todos.select(:user_id).distinct) } 433 scope :with_emails, -> { preload(:emails) } 434 scope :with_dashboard, -> (dashboard) { where(dashboard: dashboard) } 435 scope :with_public_profile, -> { where(private_profile: false) } 436 scope :with_expiring_and_not_notified_personal_access_tokens, ->(at) do 437 where('EXISTS (?)', 438 ::PersonalAccessToken 439 .where('personal_access_tokens.user_id = users.id') 440 .without_impersonation 441 .expiring_and_not_notified(at).select(1)) 442 end 443 scope :with_personal_access_tokens_expired_today, -> do 444 where('EXISTS (?)', 445 ::PersonalAccessToken 446 .select(1) 447 .where('personal_access_tokens.user_id = users.id') 448 .without_impersonation 449 .expired_today_and_not_notified) 450 end 451 452 scope :with_ssh_key_expiring_soon, -> do 453 includes(:expiring_soon_and_unnotified_keys) 454 .where('EXISTS (?)', 455 ::Key 456 .select(1) 457 .where('keys.user_id = users.id') 458 .expiring_soon_and_not_notified) 459 end 460 scope :order_recent_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('current_sign_in_at', 'DESC')) } 461 scope :order_oldest_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('current_sign_in_at', 'ASC')) } 462 scope :order_recent_last_activity, -> { reorder(Gitlab::Database.nulls_last_order('last_activity_on', 'DESC')) } 463 scope :order_oldest_last_activity, -> { reorder(Gitlab::Database.nulls_first_order('last_activity_on', 'ASC')) } 464 scope :by_id_and_login, ->(id, login) { where(id: id).where('username = LOWER(:login) OR email = LOWER(:login)', login: login) } 465 scope :dormant, -> { with_state(:active).human_or_service_user.where('last_activity_on <= ?', MINIMUM_INACTIVE_DAYS.day.ago.to_date) } 466 scope :with_no_activity, -> { with_state(:active).human_or_service_user.where(last_activity_on: nil) } 467 scope :by_provider_and_extern_uid, ->(provider, extern_uid) { joins(:identities).merge(Identity.with_extern_uid(provider, extern_uid)) } 468 scope :get_ids_by_username, -> (username) { where(username: username).pluck(:id) } 469 470 strip_attributes! :name 471 472 def preferred_language 473 read_attribute('preferred_language') || 474 I18n.default_locale.to_s.presence_in(Gitlab::I18n.available_locales) || 475 default_preferred_language 476 end 477 478 def active_for_authentication? 479 return false unless super 480 481 check_ldap_if_ldap_blocked! 482 483 can?(:log_in) 484 end 485 486 # The messages for these keys are defined in `devise.en.yml` 487 def inactive_message 488 if blocked_pending_approval? 489 :blocked_pending_approval 490 elsif blocked? 491 :blocked 492 elsif internal? 493 :forbidden 494 else 495 super 496 end 497 end 498 499 def self.with_visible_profile(user) 500 return with_public_profile if user.nil? 501 502 if user.admin? 503 all 504 else 505 with_public_profile.or(where(id: user.id)) 506 end 507 end 508 509 # Limits the users to those that have TODOs, optionally in the given state. 510 # 511 # user - The user to get the todos for. 512 # 513 # with_todos - If we should limit the result set to users that are the 514 # authors of todos. 515 # 516 # todo_state - An optional state to require the todos to be in. 517 def self.limit_to_todo_authors(user: nil, with_todos: false, todo_state: nil) 518 if user && with_todos 519 where(id: Todo.where(user: user, state: todo_state).select(:author_id)) 520 else 521 all 522 end 523 end 524 525 # Returns a relation that optionally includes the given user. 526 # 527 # user_id - The ID of the user to include. 528 def self.union_with_user(user_id = nil) 529 if user_id.present? 530 # We use "unscoped" here so that any inner conditions are not repeated for 531 # the outer query, which would be redundant. 532 User.unscoped.from_union([all, User.unscoped.where(id: user_id)]) 533 else 534 all 535 end 536 end 537 538 def self.with_two_factor 539 with_u2f_registrations = <<-SQL 540 EXISTS ( 541 SELECT * 542 FROM u2f_registrations AS u2f 543 WHERE u2f.user_id = users.id 544 ) OR users.otp_required_for_login = ? 545 OR 546 EXISTS ( 547 SELECT * 548 FROM webauthn_registrations AS webauthn 549 WHERE webauthn.user_id = users.id 550 ) 551 SQL 552 553 where(with_u2f_registrations, true) 554 end 555 556 def self.without_two_factor 557 joins("LEFT OUTER JOIN u2f_registrations AS u2f ON u2f.user_id = users.id 558 LEFT OUTER JOIN webauthn_registrations AS webauthn ON webauthn.user_id = users.id") 559 .where("u2f.id IS NULL AND webauthn.id IS NULL AND users.otp_required_for_login = ?", false) 560 end 561 562 # 563 # Class methods 564 # 565 class << self 566 # Devise method overridden to allow support for dynamic password lengths 567 def password_length 568 Gitlab::CurrentSettings.minimum_password_length..Devise.password_length.max 569 end 570 571 # Generate a random password that conforms to the current password length settings 572 def random_password 573 Devise.friendly_token(password_length.max) 574 end 575 576 # Devise method overridden to allow sign in with email or username 577 def find_for_database_authentication(warden_conditions) 578 conditions = warden_conditions.dup 579 if login = conditions.delete(:login) 580 where(conditions).find_by("lower(username) = :value OR lower(email) = :value", value: login.downcase.strip) 581 else 582 find_by(conditions) 583 end 584 end 585 586 def sort_by_attribute(method) 587 order_method = method || 'id_desc' 588 589 case order_method.to_s 590 when 'recent_sign_in' then order_recent_sign_in 591 when 'oldest_sign_in' then order_oldest_sign_in 592 when 'last_activity_on_desc' then order_recent_last_activity 593 when 'last_activity_on_asc' then order_oldest_last_activity 594 else 595 order_by(order_method) 596 end 597 end 598 599 # Find a User by their primary email or any associated secondary email 600 def find_by_any_email(email, confirmed: false) 601 return unless email 602 603 by_any_email(email, confirmed: confirmed).take 604 end 605 606 # Returns a relation containing all the users for the given email addresses 607 # 608 # @param emails [String, Array<String>] email addresses to check 609 # @param confirmed [Boolean] Only return users where the email is confirmed 610 def by_any_email(emails, confirmed: false) 611 from_users = by_user_email(emails) 612 from_users = from_users.confirmed if confirmed 613 614 from_emails = by_emails(emails) 615 from_emails = from_emails.confirmed.merge(Email.confirmed) if confirmed 616 617 items = [from_users, from_emails] 618 619 user_ids = Gitlab::PrivateCommitEmail.user_ids_for_emails(Array(emails).map(&:downcase)) 620 items << where(id: user_ids) if user_ids.present? 621 622 from_union(items) 623 end 624 625 def find_by_private_commit_email(email) 626 user_id = Gitlab::PrivateCommitEmail.user_id_for_email(email) 627 628 find_by(id: user_id) 629 end 630 631 def filter_items(filter_name) 632 case filter_name 633 when 'admins' 634 admins 635 when 'blocked' 636 blocked 637 when 'blocked_pending_approval' 638 blocked_pending_approval 639 when 'banned' 640 banned 641 when 'two_factor_disabled' 642 without_two_factor 643 when 'two_factor_enabled' 644 with_two_factor 645 when 'wop' 646 without_projects 647 when 'external' 648 external 649 when 'deactivated' 650 deactivated 651 else 652 active_without_ghosts 653 end 654 end 655 656 # Searches users matching the given query. 657 # 658 # This method uses ILIKE on PostgreSQL. 659 # 660 # query - The search query as a String 661 # 662 # Returns an ActiveRecord::Relation. 663 def search(query, **options) 664 query = query&.delete_prefix('@') 665 return none if query.blank? 666 667 query = query.downcase 668 669 order = <<~SQL 670 CASE 671 WHEN users.name = :query THEN 0 672 WHEN users.username = :query THEN 1 673 WHEN users.email = :query THEN 2 674 ELSE 3 675 END 676 SQL 677 678 sanitized_order_sql = Arel.sql(sanitize_sql_array([order, query: query])) 679 680 search_with_secondary_emails(query).reorder(sanitized_order_sql, :name) 681 end 682 683 # Limits the result set to users _not_ in the given query/list of IDs. 684 # 685 # users - The list of users to ignore. This can be an 686 # `ActiveRecord::Relation`, or an Array. 687 def where_not_in(users = nil) 688 users ? where.not(id: users) : all 689 end 690 691 def reorder_by_name 692 reorder(:name) 693 end 694 695 def search_without_secondary_emails(query) 696 return none if query.blank? 697 698 query = query.downcase 699 700 where( 701 fuzzy_arel_match(:name, query, lower_exact_match: true) 702 .or(fuzzy_arel_match(:username, query, lower_exact_match: true)) 703 .or(arel_table[:email].eq(query)) 704 ) 705 end 706 707 # searches user by given pattern 708 # it compares name, email, username fields and user's secondary emails with given pattern 709 # This method uses ILIKE on PostgreSQL. 710 711 def search_with_secondary_emails(query) 712 return none if query.blank? 713 714 query = query.downcase 715 716 email_table = Email.arel_table 717 matched_by_email_user_id = email_table 718 .project(email_table[:user_id]) 719 .where(email_table[:email].eq(query)) 720 .take(1) # at most 1 record as there is a unique constraint 721 722 where( 723 fuzzy_arel_match(:name, query) 724 .or(fuzzy_arel_match(:username, query)) 725 .or(arel_table[:email].eq(query)) 726 .or(arel_table[:id].eq(matched_by_email_user_id)) 727 ) 728 end 729 730 def by_login(login) 731 return unless login 732 733 if login.include?('@') 734 unscoped.iwhere(email: login).take 735 else 736 unscoped.iwhere(username: login).take 737 end 738 end 739 740 def find_by_username(username) 741 by_username(username).take 742 end 743 744 def find_by_username!(username) 745 by_username(username).take! 746 end 747 748 # Returns a user for the given SSH key. 749 def find_by_ssh_key_id(key_id) 750 find_by('EXISTS (?)', Key.select(1).where('keys.user_id = users.id').where(id: key_id)) 751 end 752 753 def find_by_full_path(path, follow_redirects: false) 754 namespace = Namespace.user_namespaces.find_by_full_path(path, follow_redirects: follow_redirects) 755 namespace&.owner 756 end 757 758 def reference_prefix 759 '@' 760 end 761 762 # Pattern used to extract `@user` user references from text 763 def reference_pattern 764 @reference_pattern ||= 765 %r{ 766 (?<!\w) 767 #{Regexp.escape(reference_prefix)} 768 (?<user>#{Gitlab::PathRegex::FULL_NAMESPACE_FORMAT_REGEX}) 769 }x 770 end 771 772 # Return (create if necessary) the ghost user. The ghost user 773 # owns records previously belonging to deleted users. 774 def ghost 775 email = 'ghost%s@example.com' 776 unique_internal(where(user_type: :ghost), 'ghost', email) do |u| 777 u.bio = _('This is a "Ghost User", created to hold all issues authored by users that have since been deleted. This user cannot be removed.') 778 u.name = 'Ghost User' 779 end 780 end 781 782 def alert_bot 783 email_pattern = "alert%s@#{Settings.gitlab.host}" 784 785 unique_internal(where(user_type: :alert_bot), 'alert-bot', email_pattern) do |u| 786 u.bio = 'The GitLab alert bot' 787 u.name = 'GitLab Alert Bot' 788 u.avatar = bot_avatar(image: 'alert-bot.png') 789 end 790 end 791 792 def migration_bot 793 email_pattern = "noreply+gitlab-migration-bot%s@#{Settings.gitlab.host}" 794 795 unique_internal(where(user_type: :migration_bot), 'migration-bot', email_pattern) do |u| 796 u.bio = 'The GitLab migration bot' 797 u.name = 'GitLab Migration Bot' 798 u.confirmed_at = Time.zone.now 799 end 800 end 801 802 def security_bot 803 email_pattern = "security-bot%s@#{Settings.gitlab.host}" 804 805 unique_internal(where(user_type: :security_bot), 'GitLab-Security-Bot', email_pattern) do |u| 806 u.bio = 'System bot that monitors detected vulnerabilities for solutions and creates merge requests with the fixes.' 807 u.name = 'GitLab Security Bot' 808 u.website_url = Gitlab::Routing.url_helpers.help_page_url('user/application_security/security_bot/index.md') 809 u.avatar = bot_avatar(image: 'security-bot.png') 810 u.confirmed_at = Time.zone.now 811 end 812 end 813 814 def support_bot 815 email_pattern = "support%s@#{Settings.gitlab.host}" 816 817 unique_internal(where(user_type: :support_bot), 'support-bot', email_pattern) do |u| 818 u.bio = 'The GitLab support bot used for Service Desk' 819 u.name = 'GitLab Support Bot' 820 u.avatar = bot_avatar(image: 'support-bot.png') 821 u.confirmed_at = Time.zone.now 822 end 823 end 824 825 def automation_bot 826 email_pattern = "automation%s@#{Settings.gitlab.host}" 827 828 unique_internal(where(user_type: :automation_bot), 'automation-bot', email_pattern) do |u| 829 u.bio = 'The GitLab automation bot used for automated workflows and tasks' 830 u.name = 'GitLab Automation Bot' 831 u.avatar = bot_avatar(image: 'support-bot.png') # todo: add an avatar for automation-bot 832 end 833 end 834 835 # Return true if there is only single non-internal user in the deployment, 836 # ghost user is ignored. 837 def single_user? 838 User.non_internal.limit(2).count == 1 839 end 840 841 def single_user 842 User.non_internal.first if single_user? 843 end 844 end 845 846 # 847 # Instance methods 848 # 849 850 def full_path 851 username 852 end 853 854 def to_param 855 username 856 end 857 858 def to_reference(_from = nil, target_project: nil, full: nil) 859 "#{self.class.reference_prefix}#{username}" 860 end 861 862 def skip_confirmation=(bool) 863 skip_confirmation! if bool 864 end 865 866 def skip_reconfirmation=(bool) 867 skip_reconfirmation! if bool 868 end 869 870 def generate_reset_token 871 @reset_token, enc = Devise.token_generator.generate(self.class, :reset_password_token) 872 873 self.reset_password_token = enc 874 self.reset_password_sent_at = Time.current.utc 875 876 @reset_token 877 end 878 879 def recently_sent_password_reset? 880 reset_password_sent_at.present? && reset_password_sent_at >= 1.minute.ago 881 end 882 883 def remember_me! 884 super if ::Gitlab::Database.read_write? 885 end 886 887 def forget_me! 888 super if ::Gitlab::Database.read_write? 889 end 890 891 def disable_two_factor! 892 transaction do 893 update( 894 otp_required_for_login: false, 895 encrypted_otp_secret: nil, 896 encrypted_otp_secret_iv: nil, 897 encrypted_otp_secret_salt: nil, 898 otp_grace_period_started_at: nil, 899 otp_backup_codes: nil 900 ) 901 self.u2f_registrations.destroy_all # rubocop: disable Cop/DestroyAll 902 self.webauthn_registrations.destroy_all # rubocop: disable Cop/DestroyAll 903 end 904 end 905 906 def two_factor_enabled? 907 two_factor_otp_enabled? || two_factor_webauthn_u2f_enabled? 908 end 909 910 def two_factor_otp_enabled? 911 otp_required_for_login? || 912 forti_authenticator_enabled?(self) || 913 forti_token_cloud_enabled?(self) 914 end 915 916 def two_factor_u2f_enabled? 917 return false if Feature.enabled?(:webauthn, default_enabled: :yaml) 918 919 if u2f_registrations.loaded? 920 u2f_registrations.any? 921 else 922 u2f_registrations.exists? 923 end 924 end 925 926 def two_factor_webauthn_u2f_enabled? 927 two_factor_u2f_enabled? || two_factor_webauthn_enabled? 928 end 929 930 def two_factor_webauthn_enabled? 931 return false unless Feature.enabled?(:webauthn, default_enabled: :yaml) 932 933 (webauthn_registrations.loaded? && webauthn_registrations.any?) || (!webauthn_registrations.loaded? && webauthn_registrations.exists?) 934 end 935 936 def namespace_move_dir_allowed 937 if namespace&.any_project_has_container_registry_tags? 938 errors.add(:username, _('cannot be changed if a personal project has container registry tags.')) 939 end 940 end 941 942 # will_save_change_to_attribute? is used by Devise to check if it is necessary 943 # to clear any existing reset_password_tokens before updating an authentication_key 944 # and login in our case is a virtual attribute to allow login by username or email. 945 def will_save_change_to_login? 946 will_save_change_to_username? || will_save_change_to_email? 947 end 948 949 def unique_email 950 return if errors.added?(:email, _('has already been taken')) 951 952 if !emails.exists?(email: email) && Email.exists?(email: email) 953 errors.add(:email, _('has already been taken')) 954 end 955 end 956 957 def commit_email_or_default 958 if self.commit_email == Gitlab::PrivateCommitEmail::TOKEN 959 return private_commit_email 960 end 961 962 # The commit email is the same as the primary email if undefined 963 self.commit_email.presence || self.email 964 end 965 966 def notification_email_or_default 967 # The notification email is the same as the primary email if undefined 968 self.notification_email.presence || self.email 969 end 970 971 def private_commit_email 972 Gitlab::PrivateCommitEmail.for_user(self) 973 end 974 975 # see if the new email is already a verified secondary email 976 def check_for_verified_email 977 skip_reconfirmation! if emails.confirmed.where(email: self.email).any? 978 end 979 980 def update_invalid_gpg_signatures 981 gpg_keys.each(&:update_invalid_gpg_signatures) 982 end 983 984 # Returns the groups a user has access to, either through a membership or a project authorization 985 def authorized_groups 986 Group.unscoped do 987 authorized_groups_with_shared_membership 988 end 989 end 990 991 # Returns the groups a user is a member of, either directly or through a parent group 992 def membership_groups 993 groups.self_and_descendants 994 end 995 996 # Returns a relation of groups the user has access to, including their parent 997 # and child groups (recursively). 998 def all_expanded_groups 999 return groups if groups.empty? 1000 1001 Gitlab::ObjectHierarchy.new(groups).all_objects 1002 end 1003 1004 def expanded_groups_requiring_two_factor_authentication 1005 all_expanded_groups.where(require_two_factor_authentication: true) 1006 end 1007 1008 def source_groups_of_two_factor_authentication_requirement 1009 Gitlab::ObjectHierarchy.new(expanded_groups_requiring_two_factor_authentication) 1010 .all_objects 1011 .where(id: groups) 1012 end 1013 1014 # rubocop: disable CodeReuse/ServiceClass 1015 def refresh_authorized_projects(source: nil) 1016 Users::RefreshAuthorizedProjectsService.new(self, source: source).execute 1017 end 1018 # rubocop: enable CodeReuse/ServiceClass 1019 1020 def remove_project_authorizations(project_ids, per_batch = 1000) 1021 project_ids.each_slice(per_batch) do |project_ids_batch| 1022 project_authorizations.where(project_id: project_ids_batch).delete_all 1023 end 1024 end 1025 1026 def authorized_projects(min_access_level = nil) 1027 # We're overriding an association, so explicitly call super with no 1028 # arguments or it would be passed as `force_reload` to the association 1029 projects = super() 1030 1031 if min_access_level 1032 projects = projects 1033 .where('project_authorizations.access_level >= ?', min_access_level) 1034 end 1035 1036 projects 1037 end 1038 1039 def authorized_project?(project, min_access_level = nil) 1040 authorized_projects(min_access_level).exists?({ id: project.id }) 1041 end 1042 1043 # Typically used in conjunction with projects table to get projects 1044 # a user has been given access to. 1045 # The param `related_project_column` is the column to compare to the 1046 # project_authorizations. By default is projects.id 1047 # 1048 # Example use: 1049 # `Project.where('EXISTS(?)', user.authorizations_for_projects)` 1050 def authorizations_for_projects(min_access_level: nil, related_project_column: 'projects.id') 1051 authorizations = project_authorizations 1052 .select(1) 1053 .where("project_authorizations.project_id = #{related_project_column}") 1054 1055 return authorizations unless min_access_level.present? 1056 1057 authorizations.where('project_authorizations.access_level >= ?', min_access_level) 1058 end 1059 1060 # Returns the projects this user has reporter (or greater) access to, limited 1061 # to at most the given projects. 1062 # 1063 # This method is useful when you have a list of projects and want to 1064 # efficiently check to which of these projects the user has at least reporter 1065 # access. 1066 def projects_with_reporter_access_limited_to(projects) 1067 authorized_projects(Gitlab::Access::REPORTER).where(id: projects) 1068 end 1069 1070 def owned_projects 1071 @owned_projects ||= Project.from_union( 1072 [ 1073 Project.where(namespace: namespace), 1074 Project.joins(:project_authorizations) 1075 .where.not('projects.namespace_id' => namespace.id) 1076 .where(project_authorizations: { user_id: id, access_level: Gitlab::Access::OWNER }) 1077 ], 1078 remove_duplicates: false 1079 ) 1080 end 1081 1082 # Returns projects which user can admin issues on (for example to move an issue to that project). 1083 # 1084 # This logic is duplicated from `Ability#project_abilities` into a SQL form. 1085 def projects_where_can_admin_issues 1086 authorized_projects(Gitlab::Access::REPORTER).non_archived.with_issues_enabled 1087 end 1088 1089 # rubocop: disable CodeReuse/ServiceClass 1090 def require_ssh_key? 1091 count = Users::KeysCountService.new(self).count 1092 1093 count == 0 && Gitlab::ProtocolAccess.allowed?('ssh') 1094 end 1095 # rubocop: enable CodeReuse/ServiceClass 1096 1097 def require_password_creation_for_web? 1098 allow_password_authentication_for_web? && password_automatically_set? 1099 end 1100 1101 def require_password_creation_for_git? 1102 allow_password_authentication_for_git? && password_automatically_set? 1103 end 1104 1105 def require_personal_access_token_creation_for_git_auth? 1106 return false if allow_password_authentication_for_git? || password_based_omniauth_user? 1107 1108 PersonalAccessTokensFinder.new(user: self, impersonation: false, state: 'active').execute.none? 1109 end 1110 1111 def require_extra_setup_for_git_auth? 1112 require_password_creation_for_git? || require_personal_access_token_creation_for_git_auth? 1113 end 1114 1115 def allow_password_authentication? 1116 allow_password_authentication_for_web? || allow_password_authentication_for_git? 1117 end 1118 1119 def allow_password_authentication_for_web? 1120 Gitlab::CurrentSettings.password_authentication_enabled_for_web? && !ldap_user? 1121 end 1122 1123 def allow_password_authentication_for_git? 1124 Gitlab::CurrentSettings.password_authentication_enabled_for_git? && !password_based_omniauth_user? 1125 end 1126 1127 # method overriden in EE 1128 def password_based_login_forbidden? 1129 false 1130 end 1131 1132 def can_change_username? 1133 gitlab_config.username_changing_enabled 1134 end 1135 1136 def can_create_project? 1137 projects_limit_left > 0 1138 end 1139 1140 def can_create_group? 1141 can?(:create_group) 1142 end 1143 1144 def can_select_namespace? 1145 several_namespaces? || admin 1146 end 1147 1148 def can?(action, subject = :global) 1149 Ability.allowed?(self, action, subject) 1150 end 1151 1152 def confirm_deletion_with_password? 1153 !password_automatically_set? && allow_password_authentication? 1154 end 1155 1156 def first_name 1157 read_attribute(:first_name) || begin 1158 name.split(' ').first unless name.blank? 1159 end 1160 end 1161 1162 def last_name 1163 read_attribute(:last_name) || begin 1164 name.split(' ').drop(1).join(' ') unless name.blank? 1165 end 1166 end 1167 1168 def projects_limit_left 1169 projects_limit - personal_projects_count 1170 end 1171 1172 # rubocop: disable CodeReuse/ServiceClass 1173 def recent_push(project = nil) 1174 service = Users::LastPushEventService.new(self) 1175 1176 if project 1177 service.last_event_for_project(project) 1178 else 1179 service.last_event_for_user 1180 end 1181 end 1182 # rubocop: enable CodeReuse/ServiceClass 1183 1184 def several_namespaces? 1185 union_sql = ::Gitlab::SQL::Union.new( 1186 [owned_groups, 1187 maintainers_groups, 1188 groups_with_developer_maintainer_project_access]).to_sql 1189 1190 ::Group.from("(#{union_sql}) #{::Group.table_name}").any? 1191 end 1192 1193 def namespace_id 1194 namespace.try :id 1195 end 1196 1197 def name_with_username 1198 "#{name} (#{username})" 1199 end 1200 1201 def already_forked?(project) 1202 !!fork_of(project) 1203 end 1204 1205 def fork_of(project) 1206 namespace.find_fork_of(project) 1207 end 1208 1209 def password_based_omniauth_user? 1210 ldap_user? || crowd_user? 1211 end 1212 1213 def crowd_user? 1214 if identities.loaded? 1215 identities.find { |identity| identity.provider == 'crowd' && identity.extern_uid.present? } 1216 else 1217 identities.with_any_extern_uid('crowd').exists? 1218 end 1219 end 1220 1221 def ldap_user? 1222 if identities.loaded? 1223 identities.find { |identity| Gitlab::Auth::OAuth::Provider.ldap_provider?(identity.provider) && !identity.extern_uid.nil? } 1224 else 1225 identities.exists?(["provider LIKE ? AND extern_uid IS NOT NULL", "ldap%"]) 1226 end 1227 end 1228 1229 def ldap_identity 1230 @ldap_identity ||= identities.find_by(["provider LIKE ?", "ldap%"]) 1231 end 1232 1233 def matches_identity?(provider, extern_uid) 1234 identities.with_extern_uid(provider, extern_uid).exists? 1235 end 1236 1237 def project_deploy_keys 1238 @project_deploy_keys ||= DeployKey.in_projects(authorized_projects.select(:id)).distinct(:id) 1239 end 1240 1241 def highest_role 1242 user_highest_role&.highest_access_level || Gitlab::Access::NO_ACCESS 1243 end 1244 1245 def credit_card_validated_at 1246 credit_card_validation&.credit_card_validated_at 1247 end 1248 1249 def accessible_deploy_keys 1250 DeployKey.from_union([ 1251 DeployKey.where(id: project_deploy_keys.select(:deploy_key_id)), 1252 DeployKey.are_public 1253 ]) 1254 end 1255 1256 def created_by 1257 User.find_by(id: created_by_id) if created_by_id 1258 end 1259 1260 def sanitize_attrs 1261 sanitize_links 1262 sanitize_name 1263 end 1264 1265 def sanitize_links 1266 %i[skype linkedin twitter].each do |attr| 1267 value = self[attr] 1268 self[attr] = Sanitize.clean(value) if value.present? 1269 end 1270 end 1271 1272 def sanitize_name 1273 return unless self.name 1274 1275 self.name = self.name.gsub(%r{</?[^>]*>}, '') 1276 end 1277 1278 def unset_secondary_emails_matching_deleted_email!(deleted_email) 1279 secondary_email_attribute_changed = false 1280 SECONDARY_EMAIL_ATTRIBUTES.each do |attribute| 1281 if read_attribute(attribute) == deleted_email 1282 self.write_attribute(attribute, nil) 1283 secondary_email_attribute_changed = true 1284 end 1285 end 1286 save if secondary_email_attribute_changed 1287 end 1288 1289 def admin_unsubscribe! 1290 update_column :admin_email_unsubscribed_at, Time.current 1291 end 1292 1293 def set_projects_limit 1294 # `User.select(:id)` raises 1295 # `ActiveModel::MissingAttributeError: missing attribute: projects_limit` 1296 # without this safeguard! 1297 return unless has_attribute?(:projects_limit) && projects_limit.nil? 1298 1299 self.projects_limit = Gitlab::CurrentSettings.default_projects_limit 1300 end 1301 1302 def requires_ldap_check? 1303 if !Gitlab.config.ldap.enabled 1304 false 1305 elsif ldap_user? 1306 !last_credential_check_at || (last_credential_check_at + ldap_sync_time) < Time.current 1307 else 1308 false 1309 end 1310 end 1311 1312 def ldap_sync_time 1313 # This number resides in this method so it can be redefined in EE. 1314 1.hour 1315 end 1316 1317 def try_obtain_ldap_lease 1318 # After obtaining this lease LDAP checks will be blocked for 600 seconds 1319 # (10 minutes) for this user. 1320 lease = Gitlab::ExclusiveLease.new("user_ldap_check:#{id}", timeout: 600) 1321 lease.try_obtain 1322 end 1323 1324 def solo_owned_groups 1325 @solo_owned_groups ||= owned_groups.includes(:owners).select do |group| 1326 group.owners == [self] 1327 end 1328 end 1329 1330 def with_defaults 1331 User.defaults.each do |k, v| 1332 public_send("#{k}=", v) # rubocop:disable GitlabSecurity/PublicSend 1333 end 1334 1335 self 1336 end 1337 1338 def can_leave_project?(project) 1339 project.namespace != namespace && 1340 project.project_member(self) 1341 end 1342 1343 def full_website_url 1344 return "http://#{website_url}" if website_url !~ %r{\Ahttps?://} 1345 1346 website_url 1347 end 1348 1349 def short_website_url 1350 website_url.sub(%r{\Ahttps?://}, '') 1351 end 1352 1353 def all_ssh_keys 1354 keys.map(&:publishable_key) 1355 end 1356 1357 def temp_oauth_email? 1358 email.start_with?('temp-email-for-oauth') 1359 end 1360 1361 # rubocop: disable CodeReuse/ServiceClass 1362 def avatar_url(size: nil, scale: 2, **args) 1363 GravatarService.new.execute(email, size, scale, username: username) 1364 end 1365 # rubocop: enable CodeReuse/ServiceClass 1366 1367 def primary_email_verified? 1368 confirmed? && !temp_oauth_email? 1369 end 1370 1371 def accept_pending_invitations! 1372 pending_invitations.select do |member| 1373 member.accept_invite!(self) 1374 end 1375 end 1376 1377 def pending_invitations 1378 Member.where(invite_email: verified_emails).invite 1379 end 1380 1381 def all_emails(include_private_email: true) 1382 all_emails = [] 1383 all_emails << email unless temp_oauth_email? 1384 all_emails << private_commit_email if include_private_email 1385 all_emails.concat(emails.map(&:email)) 1386 all_emails.uniq 1387 end 1388 1389 def verified_emails(include_private_email: true) 1390 verified_emails = [] 1391 verified_emails << email if primary_email_verified? 1392 verified_emails << private_commit_email if include_private_email 1393 verified_emails.concat(emails.confirmed.pluck(:email)) 1394 verified_emails.uniq 1395 end 1396 1397 def public_verified_emails 1398 strong_memoize(:public_verified_emails) do 1399 emails = verified_emails(include_private_email: false) 1400 emails << email unless temp_oauth_email? 1401 emails.uniq 1402 end 1403 end 1404 1405 def any_email?(check_email) 1406 downcased = check_email.downcase 1407 1408 # handle the outdated private commit email case 1409 return true if persisted? && 1410 id == Gitlab::PrivateCommitEmail.user_id_for_email(downcased) 1411 1412 all_emails.include?(check_email.downcase) 1413 end 1414 1415 def verified_email?(check_email) 1416 downcased = check_email.downcase 1417 1418 # handle the outdated private commit email case 1419 return true if persisted? && 1420 id == Gitlab::PrivateCommitEmail.user_id_for_email(downcased) 1421 1422 verified_emails.include?(check_email.downcase) 1423 end 1424 1425 def hook_attrs 1426 { 1427 id: id, 1428 name: name, 1429 username: username, 1430 avatar_url: avatar_url(only_path: false), 1431 email: public_email.presence || _('[REDACTED]') 1432 } 1433 end 1434 1435 def ensure_namespace_correct 1436 if namespace 1437 namespace.path = username if username_changed? 1438 namespace.name = name if name_changed? 1439 else 1440 # TODO: we should no longer need the `type` parameter once we can make the 1441 # the `has_one :namespace` association use the correct class. 1442 # issue https://gitlab.com/gitlab-org/gitlab/-/issues/341070 1443 namespace = build_namespace(path: username, name: name, type: ::Namespaces::UserNamespace.sti_name) 1444 namespace.build_namespace_settings 1445 end 1446 end 1447 1448 def set_username_errors 1449 namespace_path_errors = self.errors.delete(:"namespace.path") 1450 1451 return unless namespace_path_errors&.any? 1452 1453 if namespace_path_errors.include?('has already been taken') && !User.exists?(username: username) 1454 self.errors.add(:base, :username_exists_as_a_different_namespace) 1455 else 1456 namespace_path_errors.each do |msg| 1457 self.errors.add(:username, msg) 1458 end 1459 end 1460 end 1461 1462 def username_changed_hook 1463 system_hook_service.execute_hooks_for(self, :rename) 1464 end 1465 1466 def post_destroy_hook 1467 log_info("User \"#{name}\" (#{email}) was removed") 1468 1469 system_hook_service.execute_hooks_for(self, :destroy) 1470 end 1471 1472 # rubocop: disable CodeReuse/ServiceClass 1473 def remove_key_cache 1474 Users::KeysCountService.new(self).delete_cache 1475 end 1476 # rubocop: enable CodeReuse/ServiceClass 1477 1478 def delete_async(deleted_by:, params: {}) 1479 block if params[:hard_delete] 1480 DeleteUserWorker.perform_async(deleted_by.id, id, params.to_h) 1481 end 1482 1483 # rubocop: disable CodeReuse/ServiceClass 1484 def notification_service 1485 NotificationService.new 1486 end 1487 # rubocop: enable CodeReuse/ServiceClass 1488 1489 def log_info(message) 1490 Gitlab::AppLogger.info message 1491 end 1492 1493 # rubocop: disable CodeReuse/ServiceClass 1494 def system_hook_service 1495 SystemHooksService.new 1496 end 1497 # rubocop: enable CodeReuse/ServiceClass 1498 1499 def starred?(project) 1500 starred_projects.exists?(project.id) 1501 end 1502 1503 def toggle_star(project) 1504 UsersStarProject.transaction do 1505 user_star_project = users_star_projects 1506 .where(project: project, user: self).lock(true).first 1507 1508 if user_star_project 1509 user_star_project.destroy 1510 else 1511 UsersStarProject.create!(project: project, user: self) 1512 end 1513 end 1514 end 1515 1516 def following?(user) 1517 self.followees.exists?(user.id) 1518 end 1519 1520 def follow(user) 1521 return false if self.id == user.id 1522 1523 begin 1524 followee = Users::UserFollowUser.create(follower_id: self.id, followee_id: user.id) 1525 self.followees.reset if followee.persisted? 1526 rescue ActiveRecord::RecordNotUnique 1527 false 1528 end 1529 end 1530 1531 def unfollow(user) 1532 if Users::UserFollowUser.where(follower_id: self.id, followee_id: user.id).delete_all > 0 1533 self.followees.reset 1534 else 1535 false 1536 end 1537 end 1538 1539 def manageable_namespaces 1540 @manageable_namespaces ||= [namespace] + manageable_groups 1541 end 1542 1543 def manageable_groups(include_groups_with_developer_maintainer_access: false) 1544 owned_and_maintainer_group_hierarchy = if Feature.enabled?(:linear_user_manageable_groups, self, default_enabled: :yaml) 1545 owned_or_maintainers_groups.self_and_descendants 1546 else 1547 Gitlab::ObjectHierarchy.new(owned_or_maintainers_groups).base_and_descendants 1548 end 1549 1550 if include_groups_with_developer_maintainer_access 1551 union_sql = ::Gitlab::SQL::Union.new( 1552 [owned_and_maintainer_group_hierarchy, 1553 groups_with_developer_maintainer_project_access]).to_sql 1554 1555 ::Group.from("(#{union_sql}) #{::Group.table_name}") 1556 else 1557 owned_and_maintainer_group_hierarchy 1558 end 1559 end 1560 1561 def manageable_groups_with_routes(include_groups_with_developer_maintainer_access: false) 1562 manageable_groups(include_groups_with_developer_maintainer_access: include_groups_with_developer_maintainer_access) 1563 .eager_load(:route) 1564 .order('routes.path') 1565 end 1566 1567 def namespaces(owned_only: false) 1568 user_groups = owned_only ? owned_groups : groups 1569 personal_namespace = Namespace.where(id: namespace.id) 1570 1571 Namespace.from_union([user_groups, personal_namespace]) 1572 end 1573 1574 def oauth_authorized_tokens 1575 Doorkeeper::AccessToken.where(resource_owner_id: id, revoked_at: nil) 1576 end 1577 1578 # Returns the projects a user contributed to in the last year. 1579 # 1580 # This method relies on a subquery as this performs significantly better 1581 # compared to a JOIN when coupled with, for example, 1582 # `Project.visible_to_user`. That is, consider the following code: 1583 # 1584 # some_user.contributed_projects.visible_to_user(other_user) 1585 # 1586 # If this method were to use a JOIN the resulting query would take roughly 200 1587 # ms on a database with a similar size to GitLab.com's database. On the other 1588 # hand, using a subquery means we can get the exact same data in about 40 ms. 1589 def contributed_projects 1590 events = Event.select(:project_id) 1591 .contributions.where(author_id: self) 1592 .where("created_at > ?", Time.current - 1.year) 1593 .distinct 1594 .reorder(nil) 1595 1596 Project.where(id: events) 1597 end 1598 1599 def can_be_removed? 1600 !solo_owned_groups.present? 1601 end 1602 1603 def can_remove_self? 1604 true 1605 end 1606 1607 def ci_owned_runners 1608 @ci_owned_runners ||= begin 1609 project_runners = Ci::RunnerProject 1610 .where(project: authorized_projects(Gitlab::Access::MAINTAINER)) 1611 .joins(:runner) 1612 .select('ci_runners.*') 1613 1614 group_runners = Ci::RunnerNamespace 1615 .where(namespace_id: owned_groups.self_and_descendant_ids) 1616 .joins(:runner) 1617 .select('ci_runners.*') 1618 1619 Ci::Runner.from_union([project_runners, group_runners]).allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/336436') 1620 end 1621 end 1622 1623 def owns_runner?(runner) 1624 ::Gitlab::Database.allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/336436') do 1625 ci_owned_runners.exists?(runner.id) 1626 end 1627 end 1628 1629 def notification_email_for(notification_group) 1630 # Return group-specific email address if present, otherwise return global notification email address 1631 notification_group&.notification_email_for(self) || notification_email_or_default 1632 end 1633 1634 def notification_settings_for(source, inherit: false) 1635 if notification_settings.loaded? 1636 notification_settings.find do |notification| 1637 notification.source_type == source.class.base_class.name && 1638 notification.source_id == source.id 1639 end 1640 else 1641 notification_settings.find_or_initialize_by(source: source) do |ns| 1642 next unless source.is_a?(Group) && inherit 1643 1644 # If we're here it means we're trying to create a NotificationSetting for a group that doesn't have one. 1645 # Find the closest parent with a notification_setting that's not Global level, or that has an email set. 1646 ancestor_ns = source 1647 .notification_settings(hierarchy_order: :asc) 1648 .where(user: self) 1649 .find_by('level != ? OR notification_email IS NOT NULL', NotificationSetting.levels[:global]) 1650 # Use it to seed the settings 1651 ns.assign_attributes(ancestor_ns&.slice(*NotificationSetting.allowed_fields)) 1652 ns.source = source 1653 ns.user = self 1654 end 1655 end 1656 end 1657 1658 def notification_settings_for_groups(groups) 1659 ids = groups.is_a?(ActiveRecord::Relation) ? groups.select(:id) : groups.map(&:id) 1660 notification_settings.for_groups.where(source_id: ids) 1661 end 1662 1663 # Lazy load global notification setting 1664 # Initializes User setting with Participating level if setting not persisted 1665 def global_notification_setting 1666 return @global_notification_setting if defined?(@global_notification_setting) 1667 1668 @global_notification_setting = notification_settings.find_or_initialize_by(source: nil) 1669 @global_notification_setting.update(level: NotificationSetting.levels[DEFAULT_NOTIFICATION_LEVEL]) unless @global_notification_setting.persisted? 1670 1671 @global_notification_setting 1672 end 1673 1674 def assigned_open_merge_requests_count(force: false) 1675 Rails.cache.fetch(['users', id, 'assigned_open_merge_requests_count'], force: force, expires_in: COUNT_CACHE_VALIDITY_PERIOD) do 1676 MergeRequestsFinder.new(self, assignee_id: self.id, state: 'opened', non_archived: true).execute.count 1677 end 1678 end 1679 1680 def review_requested_open_merge_requests_count(force: false) 1681 Rails.cache.fetch(['users', id, 'review_requested_open_merge_requests_count'], force: force, expires_in: COUNT_CACHE_VALIDITY_PERIOD) do 1682 MergeRequestsFinder.new(self, reviewer_id: id, state: 'opened', non_archived: true).execute.count 1683 end 1684 end 1685 1686 def assigned_open_issues_count(force: false) 1687 Rails.cache.fetch(['users', id, 'assigned_open_issues_count'], force: force, expires_in: COUNT_CACHE_VALIDITY_PERIOD) do 1688 IssuesFinder.new(self, assignee_id: self.id, state: 'opened', non_archived: true).execute.count 1689 end 1690 end 1691 1692 def todos_done_count(force: false) 1693 Rails.cache.fetch(['users', id, 'todos_done_count'], force: force, expires_in: COUNT_CACHE_VALIDITY_PERIOD) do 1694 TodosFinder.new(self, state: :done).execute.count 1695 end 1696 end 1697 1698 def todos_pending_count(force: false) 1699 Rails.cache.fetch(['users', id, 'todos_pending_count'], force: force, expires_in: COUNT_CACHE_VALIDITY_PERIOD) do 1700 TodosFinder.new(self, state: :pending).execute.count 1701 end 1702 end 1703 1704 def personal_projects_count(force: false) 1705 Rails.cache.fetch(['users', id, 'personal_projects_count'], force: force, expires_in: 24.hours, raw: true) do 1706 personal_projects.count 1707 end.to_i 1708 end 1709 1710 def update_todos_count_cache 1711 todos_done_count(force: true) 1712 todos_pending_count(force: true) 1713 end 1714 1715 def invalidate_cache_counts 1716 invalidate_issue_cache_counts 1717 invalidate_merge_request_cache_counts 1718 invalidate_todos_cache_counts 1719 invalidate_personal_projects_count 1720 end 1721 1722 def invalidate_issue_cache_counts 1723 Rails.cache.delete(['users', id, 'assigned_open_issues_count']) 1724 end 1725 1726 def invalidate_merge_request_cache_counts 1727 Rails.cache.delete(['users', id, 'assigned_open_merge_requests_count']) 1728 Rails.cache.delete(['users', id, 'review_requested_open_merge_requests_count']) 1729 end 1730 1731 def invalidate_todos_cache_counts 1732 Rails.cache.delete(['users', id, 'todos_done_count']) 1733 Rails.cache.delete(['users', id, 'todos_pending_count']) 1734 end 1735 1736 def invalidate_personal_projects_count 1737 Rails.cache.delete(['users', id, 'personal_projects_count']) 1738 end 1739 1740 # This is copied from Devise::Models::Lockable#valid_for_authentication?, as our auth 1741 # flow means we don't call that automatically (and can't conveniently do so). 1742 # 1743 # See: 1744 # <https://github.com/plataformatec/devise/blob/v4.7.1/lib/devise/models/lockable.rb#L104> 1745 # 1746 # rubocop: disable CodeReuse/ServiceClass 1747 def increment_failed_attempts! 1748 return if ::Gitlab::Database.read_only? 1749 1750 increment_failed_attempts 1751 1752 if attempts_exceeded? 1753 lock_access! unless access_locked? 1754 else 1755 Users::UpdateService.new(self, user: self).execute(validate: false) 1756 end 1757 end 1758 # rubocop: enable CodeReuse/ServiceClass 1759 1760 def access_level 1761 if admin? 1762 :admin 1763 else 1764 :regular 1765 end 1766 end 1767 1768 def access_level=(new_level) 1769 new_level = new_level.to_s 1770 return unless %w(admin regular).include?(new_level) 1771 1772 self.admin = (new_level == 'admin') 1773 end 1774 1775 def can_read_all_resources? 1776 can?(:read_all_resources) 1777 end 1778 1779 def can_admin_all_resources? 1780 can?(:admin_all_resources) 1781 end 1782 1783 def update_two_factor_requirement 1784 periods = expanded_groups_requiring_two_factor_authentication.pluck(:two_factor_grace_period) 1785 1786 self.require_two_factor_authentication_from_group = periods.any? 1787 self.two_factor_grace_period = periods.min || User.column_defaults['two_factor_grace_period'] 1788 1789 save 1790 end 1791 1792 # each existing user needs to have a `feed_token`. 1793 # we do this on read since migrating all existing users is not a feasible 1794 # solution. 1795 def feed_token 1796 ensure_feed_token! unless Gitlab::CurrentSettings.disable_feed_token 1797 end 1798 1799 # Each existing user needs to have a `static_object_token`. 1800 # We do this on read since migrating all existing users is not a feasible 1801 # solution. 1802 def static_object_token 1803 ensure_static_object_token! 1804 end 1805 1806 def enabled_static_object_token 1807 static_object_token if Gitlab::CurrentSettings.static_objects_external_storage_enabled? 1808 end 1809 1810 def enabled_incoming_email_token 1811 incoming_email_token if Gitlab::IncomingEmail.supports_issue_creation? 1812 end 1813 1814 def sync_attribute?(attribute) 1815 return true if ldap_user? && attribute == :email 1816 1817 attributes = Gitlab.config.omniauth.sync_profile_attributes 1818 1819 if attributes.is_a?(Array) 1820 attributes.include?(attribute.to_s) 1821 else 1822 attributes 1823 end 1824 end 1825 1826 def read_only_attribute?(attribute) 1827 user_synced_attributes_metadata&.read_only?(attribute) 1828 end 1829 1830 # override, from Devise 1831 def lock_access! 1832 Gitlab::AppLogger.info("Account Locked: username=#{username}") 1833 super 1834 end 1835 1836 # Determine the maximum access level for a group of projects in bulk. 1837 # 1838 # Returns a Hash mapping project ID -> maximum access level. 1839 def max_member_access_for_project_ids(project_ids) 1840 max_member_access_for_resource_ids(Project, project_ids) do |project_ids| 1841 project_authorizations.where(project: project_ids) 1842 .group(:project_id) 1843 .maximum(:access_level) 1844 end 1845 end 1846 1847 def max_member_access_for_project(project_id) 1848 max_member_access_for_project_ids([project_id])[project_id] 1849 end 1850 1851 # Determine the maximum access level for a group of groups in bulk. 1852 # 1853 # Returns a Hash mapping project ID -> maximum access level. 1854 def max_member_access_for_group_ids(group_ids) 1855 max_member_access_for_resource_ids(Group, group_ids) do |group_ids| 1856 group_members.where(source: group_ids).group(:source_id).maximum(:access_level) 1857 end 1858 end 1859 1860 def max_member_access_for_group(group_id) 1861 max_member_access_for_group_ids([group_id])[group_id] 1862 end 1863 1864 def terms_accepted? 1865 return true if project_bot? 1866 1867 accepted_term_id.present? 1868 end 1869 1870 def required_terms_not_accepted? 1871 Gitlab::CurrentSettings.current_application_settings.enforce_terms? && 1872 !terms_accepted? 1873 end 1874 1875 def requires_usage_stats_consent? 1876 self.admin? && 7.days.ago > self.created_at && !has_current_license? && User.single_user? && !consented_usage_stats? 1877 end 1878 1879 # Avoid migrations only building user preference object when needed. 1880 def user_preference 1881 super.presence || build_user_preference 1882 end 1883 1884 def user_detail 1885 super.presence || build_user_detail 1886 end 1887 1888 def pending_todo_for(target) 1889 todos.find_by(target: target, state: :pending) 1890 end 1891 1892 def password_expired? 1893 !!(password_expires_at && password_expires_at < Time.current) 1894 end 1895 1896 def password_expired_if_applicable? 1897 return false if bot? 1898 return false unless password_expired? 1899 return false if password_automatically_set? 1900 return false unless allow_password_authentication? 1901 1902 true 1903 end 1904 1905 def can_be_deactivated? 1906 active? && no_recent_activity? && !internal? 1907 end 1908 1909 def last_active_at 1910 last_activity = last_activity_on&.to_time&.in_time_zone 1911 last_sign_in = current_sign_in_at 1912 1913 [last_activity, last_sign_in].compact.max 1914 end 1915 1916 REQUIRES_ROLE_VALUE = 99 1917 1918 def role_required? 1919 role_before_type_cast == REQUIRES_ROLE_VALUE 1920 end 1921 1922 def set_role_required! 1923 update_column(:role, REQUIRES_ROLE_VALUE) 1924 end 1925 1926 def dismissed_callout?(feature_name:, ignore_dismissal_earlier_than: nil) 1927 callout = callouts_by_feature_name[feature_name] 1928 1929 callout_dismissed?(callout, ignore_dismissal_earlier_than) 1930 end 1931 1932 def dismissed_callout_for_group?(feature_name:, group:, ignore_dismissal_earlier_than: nil) 1933 source_feature_name = "#{feature_name}_#{group.id}" 1934 callout = group_callouts_by_feature_name[source_feature_name] 1935 1936 callout_dismissed?(callout, ignore_dismissal_earlier_than) 1937 end 1938 1939 # Load the current highest access by looking directly at the user's memberships 1940 def current_highest_access_level 1941 members.non_request.maximum(:access_level) 1942 end 1943 1944 def confirmation_required_on_sign_in? 1945 !confirmed? && !confirmation_period_valid? 1946 end 1947 1948 def impersonated? 1949 impersonator.present? 1950 end 1951 1952 def created_recently? 1953 created_at > Devise.confirm_within.ago 1954 end 1955 1956 def find_or_initialize_callout(feature_name) 1957 callouts.find_or_initialize_by(feature_name: ::Users::Callout.feature_names[feature_name]) 1958 end 1959 1960 def find_or_initialize_group_callout(feature_name, group_id) 1961 group_callouts 1962 .find_or_initialize_by(feature_name: ::Users::GroupCallout.feature_names[feature_name], group_id: group_id) 1963 end 1964 1965 def can_trigger_notifications? 1966 confirmed? && !blocked? && !ghost? 1967 end 1968 1969 # This attribute hosts a Ci::JobToken::Scope object which is set when 1970 # the user is authenticated successfully via CI_JOB_TOKEN. 1971 def ci_job_token_scope 1972 Gitlab::SafeRequestStore[ci_job_token_scope_cache_key] 1973 end 1974 1975 def set_ci_job_token_scope!(job) 1976 Gitlab::SafeRequestStore[ci_job_token_scope_cache_key] = Ci::JobToken::Scope.new(job.project) 1977 end 1978 1979 def from_ci_job_token? 1980 ci_job_token_scope.present? 1981 end 1982 1983 # override from Devise::Confirmable 1984 # 1985 # Add the primary email to user.emails (or confirm it if it was already 1986 # present) when the primary email is confirmed. 1987 def confirm(*args) 1988 saved = super(*args) 1989 return false unless saved 1990 1991 email_to_confirm = self.emails.find_by(email: self.email) 1992 1993 if email_to_confirm.present? 1994 email_to_confirm.confirm(*args) 1995 else 1996 add_primary_email_to_emails! 1997 end 1998 1999 saved 2000 end 2001 2002 def user_project 2003 strong_memoize(:user_project) do 2004 personal_projects.find_by(path: username, visibility_level: Gitlab::VisibilityLevel::PUBLIC) 2005 end 2006 end 2007 2008 def user_readme 2009 strong_memoize(:user_readme) do 2010 user_project&.repository&.readme 2011 end 2012 end 2013 2014 protected 2015 2016 # override, from Devise::Validatable 2017 def password_required? 2018 return false if internal? || project_bot? 2019 2020 super 2021 end 2022 2023 # override from Devise::Confirmable 2024 def confirmation_period_valid? 2025 return false if Feature.disabled?(:soft_email_confirmation) 2026 2027 super 2028 end 2029 2030 # This is copied from Devise::Models::TwoFactorAuthenticatable#consume_otp! 2031 # 2032 # An OTP cannot be used more than once in a given timestep 2033 # Storing timestep of last valid OTP is sufficient to satisfy this requirement 2034 # 2035 # See: 2036 # <https://github.com/tinfoil/devise-two-factor/blob/master/lib/devise_two_factor/models/two_factor_authenticatable.rb#L66> 2037 # 2038 def consume_otp! 2039 if self.consumed_timestep != current_otp_timestep 2040 self.consumed_timestep = current_otp_timestep 2041 return Gitlab::Database.read_only? ? true : save(validate: false) 2042 end 2043 2044 false 2045 end 2046 2047 private 2048 2049 # To enable JiHu repository to modify the default language options 2050 def default_preferred_language 2051 'en' 2052 end 2053 2054 # rubocop: disable CodeReuse/ServiceClass 2055 def add_primary_email_to_emails! 2056 Emails::CreateService.new(self, user: self, email: self.email).execute(confirmed_at: self.confirmed_at) 2057 end 2058 # rubocop: enable CodeReuse/ServiceClass 2059 2060 def notification_email_verified 2061 return if notification_email.blank? || temp_oauth_email? 2062 2063 errors.add(:notification_email, _("must be an email you have verified")) unless verified_emails.include?(notification_email_or_default) 2064 end 2065 2066 def public_email_verified 2067 return if public_email.blank? 2068 2069 errors.add(:public_email, _("must be an email you have verified")) unless verified_emails.include?(public_email) 2070 end 2071 2072 def commit_email_verified 2073 return if commit_email.blank? 2074 2075 errors.add(:commit_email, _("must be an email you have verified")) unless verified_emails.include?(commit_email_or_default) 2076 end 2077 2078 def callout_dismissed?(callout, ignore_dismissal_earlier_than) 2079 return false unless callout 2080 return callout.dismissed_after?(ignore_dismissal_earlier_than) if ignore_dismissal_earlier_than 2081 2082 true 2083 end 2084 2085 def callouts_by_feature_name 2086 @callouts_by_feature_name ||= callouts.index_by(&:feature_name) 2087 end 2088 2089 def group_callouts_by_feature_name 2090 @group_callouts_by_feature_name ||= group_callouts.index_by(&:source_feature_name) 2091 end 2092 2093 def authorized_groups_without_shared_membership 2094 Group.from_union([ 2095 groups.select(Namespace.arel_table[Arel.star]), 2096 authorized_projects.joins(:namespace).select(Namespace.arel_table[Arel.star]) 2097 ]) 2098 end 2099 2100 def authorized_groups_with_shared_membership 2101 cte = Gitlab::SQL::CTE.new(:direct_groups, authorized_groups_without_shared_membership) 2102 cte_alias = cte.table.alias(Group.table_name) 2103 2104 Group 2105 .with(cte.to_arel) 2106 .from_union([ 2107 Group.from(cte_alias), 2108 Group.joins(:shared_with_group_links) 2109 .where(group_group_links: { shared_with_group_id: Group.from(cte_alias) }) 2110 ]) 2111 end 2112 2113 def default_private_profile_to_false 2114 return unless private_profile_changed? && private_profile.nil? 2115 2116 self.private_profile = false 2117 end 2118 2119 def has_current_license? 2120 false 2121 end 2122 2123 def consented_usage_stats? 2124 # Bypass the cache here because it's possible the admin enabled the 2125 # usage ping, and we don't want to annoy the user again if they 2126 # already set the value. This is a bit of hack, but the alternative 2127 # would be to put in a more complex cache invalidation step. Since 2128 # this call only gets called in the uncommon situation where the 2129 # user is an admin and the only user in the instance, this shouldn't 2130 # cause too much load on the system. 2131 ApplicationSetting.current_without_cache&.usage_stats_set_by_user_id == self.id 2132 end 2133 2134 def ensure_user_rights_and_limits 2135 if external? 2136 self.can_create_group = false 2137 self.projects_limit = 0 2138 else 2139 # Only revert these back to the default if they weren't specifically changed in this update. 2140 self.can_create_group = gitlab_config.default_can_create_group unless can_create_group_changed? 2141 self.projects_limit = Gitlab::CurrentSettings.default_projects_limit unless projects_limit_changed? 2142 end 2143 end 2144 2145 def signup_email_valid? 2146 error = validate_admin_signup_restrictions(email) 2147 2148 errors.add(:email, error) if error 2149 end 2150 2151 def signup_email_invalid_message 2152 _('is not allowed for sign-up.') 2153 end 2154 2155 def check_username_format 2156 return if username.blank? || Mime::EXTENSION_LOOKUP.keys.none? { |type| username.end_with?(".#{type}") } 2157 2158 errors.add(:username, _('ending with a reserved file extension is not allowed.')) 2159 end 2160 2161 def groups_with_developer_maintainer_project_access 2162 project_creation_levels = [::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS] 2163 2164 if ::Gitlab::CurrentSettings.default_project_creation == ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS 2165 project_creation_levels << nil 2166 end 2167 2168 developer_groups.self_and_descendants.where(project_creation_level: project_creation_levels) 2169 end 2170 2171 def no_recent_activity? 2172 last_active_at.to_i <= MINIMUM_INACTIVE_DAYS.days.ago.to_i 2173 end 2174 2175 def update_highest_role? 2176 return false unless persisted? 2177 2178 (previous_changes.keys & %w(state user_type)).any? 2179 end 2180 2181 def update_highest_role_attribute 2182 id 2183 end 2184 2185 def ci_job_token_scope_cache_key 2186 "users:#{id}:ci:job_token_scope" 2187 end 2188 2189 # An `ldap_blocked` user will be unblocked if LDAP indicates they are allowed. 2190 def check_ldap_if_ldap_blocked! 2191 return unless ::Gitlab::Auth::Ldap::Config.enabled? && ldap_blocked? 2192 2193 ::Gitlab::Auth::Ldap::Access.allowed?(self) 2194 end 2195end 2196 2197User.prepend_mod_with('User') 2198