Class: User
- Inherits:
-
ApplicationRecord
show all
- Extended by:
- Gitlab::ConfigHelper
- Includes:
- AdminChangedPasswordNotifier, AfterCommitQueue, AsyncDeviseEmail, Avatarable, BatchDestroyDependentAssociations, BlocksJsonSerialization, BulkMemberAccessLoad, CaseSensitivity, CreatedAtFilterable, FeatureGate, FromUnion, Gitlab::ConfigHelper, Gitlab::SQL::Pattern, HasUniqueInternalUsers, HasUserType, IgnorableColumns, OptionallySearch, Referable, Sortable, TokenAuthenticatable, UpdateHighestRole, WithUploads
- Defined in:
- app/models/user.rb
Constant Summary
collapse
- DEFAULT_NOTIFICATION_LEVEL =
:participating
- BLOCKED_MESSAGE =
"Your account has been blocked. Please contact your GitLab " \
"administrator if you think this is an error."
- LOGIN_FORBIDDEN =
"Your account does not have the required permission to login. Please contact your GitLab " \
"administrator if you think this is an error."
- MINIMUM_INACTIVE_DAYS =
180
- REQUIRES_ROLE_VALUE =
99
Constants included
from HasUserType
HasUserType::BOT_USER_TYPES, HasUserType::INTERNAL_USER_TYPES, HasUserType::NON_INTERNAL_USER_TYPES, HasUserType::USER_TYPES
UpdateHighestRole::HIGHEST_ROLE_JOB_DELAY, UpdateHighestRole::HIGHEST_ROLE_LEASE_TIMEOUT
BatchDestroyDependentAssociations::DEPENDENT_ASSOCIATIONS_BATCH_SIZE
Constants included
from WithUploads
WithUploads::FILE_UPLOADERS
BlocksJsonSerialization::JsonSerializationError
Constants included
from Avatarable
Avatarable::ALLOWED_IMAGE_SCALER_WIDTHS
Gitlab::SQL::Pattern::MIN_CHARS_FOR_PARTIAL_MATCHING, Gitlab::SQL::Pattern::REGEX_QUOTED_WORD
Instance Attribute Summary collapse
Class Method Summary
collapse
-
.alert_bot ⇒ Object
-
.by_any_email(emails, confirmed: false) ⇒ Object
Returns a relation containing all the users for the given email addresses.
-
.by_login(login) ⇒ Object
-
.filter_items(filter_name) ⇒ Object
-
.find_by_any_email(email, confirmed: false) ⇒ Object
Find a User by their primary email or any associated secondary email.
-
.find_by_full_path(path, follow_redirects: false) ⇒ Object
-
.find_by_private_commit_email(email) ⇒ Object
-
.find_by_ssh_key_id(key_id) ⇒ Object
Returns a user for the given SSH key.
-
.find_by_username(username) ⇒ Object
-
.find_by_username!(username) ⇒ Object
-
.find_for_database_authentication(warden_conditions) ⇒ Object
Devise method overridden to allow sign in with email or username.
-
.for_github_id(id) ⇒ Object
-
.ghost ⇒ Object
Return (create if necessary) the ghost user.
-
.limit_to_todo_authors(user: nil, with_todos: false, todo_state: nil) ⇒ Object
Limits the users to those that have TODOs, optionally in the given state.
-
.migration_bot ⇒ Object
-
.password_length ⇒ Object
Devise method overridden to allow support for dynamic password lengths.
-
.random_password ⇒ Object
Generate a random password that conforms to the current password length settings.
-
.reference_pattern ⇒ Object
Pattern used to extract `@user` user references from text.
-
.reference_prefix ⇒ Object
-
.reorder_by_name ⇒ Object
-
.search(query, **options) ⇒ Object
Searches users matching the given query.
-
.search_with_secondary_emails(query) ⇒ Object
searches user by given pattern it compares name, email, username fields and user's secondary emails with given pattern This method uses ILIKE on PostgreSQL.
-
.single_user ⇒ Object
-
.single_user? ⇒ Boolean
Return true if there is only single non-internal user in the deployment, ghost user is ignored.
-
.sort_by_attribute(method) ⇒ Object
-
.support_bot ⇒ Object
-
.union_with_user(user_id = nil) ⇒ Object
Returns a relation that optionally includes the given user.
-
.where_not_in(users = nil) ⇒ Object
Limits the result set to users not in the given query/list of IDs.
-
.with_two_factor ⇒ Object
-
.with_visible_profile(user) ⇒ Object
-
.without_two_factor ⇒ Object
Instance Method Summary
collapse
-
#accept_pending_invitations! ⇒ Object
-
#access_level ⇒ Object
rubocop: enable CodeReuse/ServiceClass.
-
#access_level=(new_level) ⇒ Object
-
#accessible_deploy_keys ⇒ Object
-
#active_for_authentication? ⇒ Boolean
-
#all_emails(include_private_email: true) ⇒ Object
-
#all_expanded_groups ⇒ Object
Returns a relation of groups the user has access to, including their parent and child groups (recursively).
-
#all_ssh_keys ⇒ Object
-
#allow_password_authentication? ⇒ Boolean
-
#allow_password_authentication_for_git? ⇒ Boolean
-
#allow_password_authentication_for_web? ⇒ Boolean
-
#already_forked?(project) ⇒ Boolean
-
#any_email?(check_email) ⇒ Boolean
-
#assigned_open_issues_count(force: false) ⇒ Object
-
#assigned_open_merge_requests_count(force: false) ⇒ Object
-
#authorizations_for_projects(min_access_level: nil, related_project_column: 'projects.id') ⇒ Object
Typically used in conjunction with projects table to get projects a user has been given access to.
-
#authorized_groups ⇒ Object
Returns the groups a user has access to, either through a membership or a project authorization.
-
#authorized_project?(project, min_access_level = nil) ⇒ Boolean
-
#authorized_projects(min_access_level = nil) ⇒ Object
-
#avatar_url(size: nil, scale: 2, **args) ⇒ Object
rubocop: disable CodeReuse/ServiceClass.
-
#can?(action, subject = :global) ⇒ Boolean
-
#can_be_deactivated? ⇒ Boolean
-
#can_be_removed? ⇒ Boolean
-
#can_change_username? ⇒ Boolean
-
#can_create_group? ⇒ Boolean
-
#can_create_project? ⇒ Boolean
-
#can_leave_project?(project) ⇒ Boolean
-
#can_read_all_resources? ⇒ Boolean
-
#can_select_namespace? ⇒ Boolean
-
#check_for_verified_email ⇒ Object
see if the new email is already a verified secondary email.
-
#ci_owned_runners ⇒ Object
-
#commit_email ⇒ Object
Define commit_email-related attribute methods explicitly instead of relying on ActiveRecord to provide them.
-
#commit_email=(email) ⇒ Object
-
#commit_email_changed? ⇒ Boolean
-
#confirm_deletion_with_password? ⇒ Boolean
-
#confirmation_required_on_sign_in? ⇒ Boolean
-
#contributed_projects ⇒ Object
Returns the projects a user contributed to in the last year.
-
#created_by ⇒ Object
-
#created_recently? ⇒ Boolean
-
#current_highest_access_level ⇒ Object
Load the current highest access by looking directly at the user's memberships.
-
#delete_async(deleted_by:, params: {}) ⇒ Object
rubocop: enable CodeReuse/ServiceClass.
-
#disable_two_factor! ⇒ Object
-
#dismissed_callout?(feature_name:, ignore_dismissal_earlier_than: nil) ⇒ Boolean
-
#ensure_namespace_correct ⇒ Object
-
#expanded_groups_requiring_two_factor_authentication ⇒ Object
-
#feed_token ⇒ Object
each existing user needs to have an `feed_token`.
-
#first_name ⇒ Object
-
#forget_me! ⇒ Object
-
#fork_of(project) ⇒ Object
-
#full_path ⇒ Object
-
#full_website_url ⇒ Object
-
#generate_reset_token ⇒ Object
-
#global_notification_setting ⇒ Object
Lazy load global notification setting Initializes User setting with Participating level if setting not persisted.
-
#highest_role ⇒ Object
-
#hook_attrs ⇒ Object
-
#impersonated? ⇒ Boolean
-
#inactive_message ⇒ Object
-
#increment_failed_attempts! ⇒ Object
This is copied from Devise::Models::Lockable#valid_for_authentication?, as our auth flow means we don't call that automatically (and can't conveniently do so).
-
#invalidate_cache_counts ⇒ Object
-
#invalidate_issue_cache_counts ⇒ Object
-
#invalidate_merge_request_cache_counts ⇒ Object
-
#invalidate_personal_projects_count ⇒ Object
-
#invalidate_todos_done_count ⇒ Object
-
#invalidate_todos_pending_count ⇒ Object
-
#last_active_at ⇒ Object
-
#last_name ⇒ Object
-
#ldap_identity ⇒ Object
-
#ldap_sync_time ⇒ Object
-
#ldap_user? ⇒ Boolean
-
#lock_access! ⇒ Object
-
#log_info(message) ⇒ Object
rubocop: enable CodeReuse/ServiceClass.
-
#manageable_groups(include_groups_with_developer_maintainer_access: false) ⇒ Object
-
#manageable_groups_with_routes(include_groups_with_developer_maintainer_access: false) ⇒ Object
-
#manageable_namespaces ⇒ Object
-
#matches_identity?(provider, extern_uid) ⇒ Boolean
-
#max_member_access_for_group(group_id) ⇒ Object
-
#max_member_access_for_group_ids(group_ids) ⇒ Object
Determine the maximum access level for a group of groups in bulk.
-
#max_member_access_for_project(project_id) ⇒ Object
-
#max_member_access_for_project_ids(project_ids) ⇒ Object
Determine the maximum access level for a group of projects in bulk.
-
#membership_groups ⇒ Object
Returns the groups a user is a member of, either directly or through a parent group.
-
#name_with_username ⇒ Object
-
#namespace_id ⇒ Object
-
#namespace_move_dir_allowed ⇒ Object
-
#namespaces ⇒ Object
-
#notification_email_for(notification_group) ⇒ Object
-
#notification_service ⇒ Object
rubocop: disable CodeReuse/ServiceClass.
-
#notification_settings_for(source, inherit: false) ⇒ Object
-
#notification_settings_for_groups(groups) ⇒ Object
-
#oauth_authorized_tokens ⇒ Object
-
#owned_projects ⇒ Object
-
#owns_commit_email ⇒ Object
-
#owns_notification_email ⇒ Object
-
#owns_public_email ⇒ Object
-
#password_expired? ⇒ Boolean
-
#pending_invitations ⇒ Object
-
#pending_todo_for(target) ⇒ Object
-
#personal_projects_count(force: false) ⇒ Object
-
#post_destroy_hook ⇒ Object
-
#preferred_language ⇒ Object
-
#primary_email_verified? ⇒ Boolean
rubocop: enable CodeReuse/ServiceClass.
-
#private_commit_email ⇒ Object
-
#project_deploy_keys ⇒ Object
-
#projects_limit_left ⇒ Object
-
#projects_where_can_admin_issues ⇒ Object
Returns projects which user can admin issues on (for example to move an issue to that project).
-
#projects_with_reporter_access_limited_to(projects) ⇒ Object
Returns the projects this user has reporter (or greater) access to, limited to at most the given projects.
-
#public_verified_emails ⇒ Object
-
#read_only_attribute?(attribute) ⇒ Boolean
-
#recent_push(project = nil) ⇒ Object
rubocop: disable CodeReuse/ServiceClass.
-
#recently_sent_password_reset? ⇒ Boolean
-
#refresh_authorized_projects ⇒ Object
rubocop: disable CodeReuse/ServiceClass.
-
#remember_me! ⇒ Object
-
#remove_key_cache ⇒ Object
rubocop: disable CodeReuse/ServiceClass.
-
#remove_project_authorizations(project_ids) ⇒ Object
rubocop: enable CodeReuse/ServiceClass.
-
#require_extra_setup_for_git_auth? ⇒ Boolean
-
#require_password_creation_for_git? ⇒ Boolean
-
#require_password_creation_for_web? ⇒ Boolean
rubocop: enable CodeReuse/ServiceClass.
-
#require_personal_access_token_creation_for_git_auth? ⇒ Boolean
-
#require_ssh_key? ⇒ Boolean
rubocop: disable CodeReuse/ServiceClass.
-
#required_terms_not_accepted? ⇒ Boolean
-
#requires_ldap_check? ⇒ Boolean
-
#requires_usage_stats_consent? ⇒ Boolean
-
#role_required? ⇒ Boolean
-
#sanitize_attrs ⇒ Object
-
#set_commit_email ⇒ Object
-
#set_notification_email ⇒ Object
-
#set_projects_limit ⇒ Object
-
#set_public_email ⇒ Object
-
#set_role_required! ⇒ Object
-
#set_username_errors ⇒ Object
-
#several_namespaces? ⇒ Boolean
rubocop: enable CodeReuse/ServiceClass.
-
#short_website_url ⇒ Object
-
#skip_confirmation=(bool) ⇒ Object
-
#skip_reconfirmation=(bool) ⇒ Object
-
#solo_owned_groups ⇒ Object
-
#source_groups_of_two_factor_authentication_requirement ⇒ Object
-
#starred?(project) ⇒ Boolean
rubocop: enable CodeReuse/ServiceClass.
-
#static_object_token ⇒ Object
Each existing user needs to have a `static_object_token`.
-
#sync_attribute?(attribute) ⇒ Boolean
-
#system_hook_service ⇒ Object
rubocop: disable CodeReuse/ServiceClass.
-
#temp_oauth_email? ⇒ Boolean
-
#terms_accepted? ⇒ Boolean
-
#to_param ⇒ Object
-
#to_reference(_from = nil, target_project: nil, full: nil) ⇒ Object
-
#todos_done_count(force: false) ⇒ Object
-
#todos_pending_count(force: false) ⇒ Object
-
#toggle_star(project) ⇒ Object
-
#try_obtain_ldap_lease ⇒ Object
-
#two_factor_enabled? ⇒ Boolean
-
#two_factor_otp_enabled? ⇒ Boolean
-
#two_factor_u2f_enabled? ⇒ Boolean
-
#two_factor_webauthn_enabled? ⇒ Boolean
-
#two_factor_webauthn_u2f_enabled? ⇒ Boolean
-
#unique_email ⇒ Object
-
#update_emails_with_primary_email(previous_confirmed_at, previous_email) ⇒ Object
Note: the use of the Emails services will cause `saves` on the user object, running through the callbacks again and can have side effects, such as the `previous_changes` hash and `_was` variables getting munged.
-
#update_invalid_gpg_signatures ⇒ Object
rubocop: enable CodeReuse/ServiceClass.
-
#update_secondary_emails! ⇒ Object
-
#update_todos_count_cache ⇒ Object
-
#update_tracked_fields!(request) ⇒ Object
Override Devise::Models::Trackable#update_tracked_fields! to limit database writes to at most once every hour rubocop: disable CodeReuse/ServiceClass.
-
#update_two_factor_requirement ⇒ Object
-
#user_detail ⇒ Object
-
#user_preference ⇒ Object
Avoid migrations only building user preference object when needed.
-
#username_changed_hook ⇒ Object
-
#verified_email?(check_email) ⇒ Boolean
-
#verified_emails(include_private_email: true) ⇒ Object
-
#will_save_change_to_login? ⇒ Boolean
will_save_change_to_attribute? is used by Devise to check if it is necessary to clear any existing reset_password_tokens before updating an authentication_key and login in our case is a virtual attribute to allow login by username or email.
-
#with_defaults ⇒ Object
gitlab_config, gitlab_config_features
#initialize, #send_only_admin_changed_your_password_notification!
#bot?, #internal?
#dependent_associations_to_destroy, #destroy_dependent_associations_in_batches
#retrieve_upload
#perform_fast_destroy
#run_after_commit, #run_after_commit_or_now
#to_json
#flipper_id
Methods included from Referable
#referable_inspect, #reference_link_text, #to_reference_base
Methods included from Avatarable
#avatar_path, #avatar_type, #uncached_avatar_path, #upload_paths
at_most, id_in, id_not_in, iid_in, pluck_primary_key, primary_key_in, safe_ensure_unique, safe_find_or_create_by, safe_find_or_create_by!, underscore, without_order
Instance Attribute Details
#force_random_password ⇒ Object
rubocop: enable CodeReuse/ServiceClass
91
92
93
|
# File 'app/models/user.rb', line 91
def force_random_password
@force_random_password
end
|
#impersonator ⇒ Object
Virtual attribute for impersonator
97
98
99
|
# File 'app/models/user.rb', line 97
def impersonator
@impersonator
end
|
#login ⇒ Object
Virtual attribute for authenticating by either username or email
94
95
96
|
# File 'app/models/user.rb', line 94
def login
@login
end
|
Class Method Details
.alert_bot ⇒ Object
669
670
671
672
673
674
675
676
677
|
# File 'app/models/user.rb', line 669
def alert_bot
email_pattern = "alert%[email protected]#{Settings.gitlab.host}"
unique_internal(where(user_type: :alert_bot), 'alert-bot', email_pattern) do |u|
u.bio = 'The GitLab alert bot'
u.name = 'GitLab Alert Bot'
u.avatar = bot_avatar(image: 'alert-bot.png')
end
end
|
.by_any_email(emails, confirmed: false) ⇒ Object
Returns a relation containing all the users for the given email addresses
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
|
# File 'app/models/user.rb', line 508
def by_any_email(emails, confirmed: false)
emails = Array(emails).map(&:downcase)
from_users = where(email: emails)
from_users = from_users.confirmed if confirmed
from_emails = joins(:emails).where(emails: { email: emails })
from_emails = from_emails.confirmed.merge(Email.confirmed) if confirmed
items = [from_users, from_emails]
user_ids = Gitlab::PrivateCommitEmail.user_ids_for_emails(emails)
items << where(id: user_ids) if user_ids.present?
from_union(items)
end
|
.by_login(login) ⇒ Object
617
618
619
620
621
622
623
624
625
|
# File 'app/models/user.rb', line 617
def by_login(login)
return unless login
if login.include?('@')
unscoped.iwhere(email: login).take
else
unscoped.iwhere(username: login).take
end
end
|
.filter_items(filter_name) ⇒ Object
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
|
# File 'app/models/user.rb', line 531
def filter_items(filter_name)
case filter_name
when 'admins'
admins
when 'blocked'
blocked
when 'two_factor_disabled'
without_two_factor
when 'two_factor_enabled'
with_two_factor
when 'wop'
without_projects
when 'external'
external
when 'deactivated'
deactivated
else
active_without_ghosts
end
end
|
.find_by_any_email(email, confirmed: false) ⇒ Object
Find a User by their primary email or any associated secondary email
498
499
500
501
502
|
# File 'app/models/user.rb', line 498
def find_by_any_email(email, confirmed: false)
return unless email
by_any_email(email, confirmed: confirmed).take
end
|
.find_by_full_path(path, follow_redirects: false) ⇒ Object
640
641
642
643
|
# File 'app/models/user.rb', line 640
def find_by_full_path(path, follow_redirects: false)
namespace = Namespace.for_user.find_by_full_path(path, follow_redirects: follow_redirects)
namespace&.owner
end
|
.find_by_private_commit_email(email) ⇒ Object
.find_by_ssh_key_id(key_id) ⇒ Object
Returns a user for the given SSH key.
636
637
638
|
# File 'app/models/user.rb', line 636
def find_by_ssh_key_id(key_id)
find_by('EXISTS (?)', Key.select(1).where('keys.user_id = users.id').where(id: key_id))
end
|
.find_by_username(username) ⇒ Object
627
628
629
|
# File 'app/models/user.rb', line 627
def find_by_username(username)
by_username(username).take
end
|
.find_by_username!(username) ⇒ Object
631
632
633
|
# File 'app/models/user.rb', line 631
def find_by_username!(username)
by_username(username).take!
end
|
.find_for_database_authentication(warden_conditions) ⇒ Object
Devise method overridden to allow sign in with email or username
471
472
473
474
475
476
477
478
|
# File 'app/models/user.rb', line 471
def find_for_database_authentication(warden_conditions)
conditions = warden_conditions.dup
if login = conditions.delete(:login)
where(conditions).find_by("lower(username) = :value OR lower(email) = :value", value: login.downcase.strip)
else
find_by(conditions)
end
end
|
.for_github_id(id) ⇒ Object
493
494
495
|
# File 'app/models/user.rb', line 493
def for_github_id(id)
joins(:identities).merge(Identity.with_extern_uid(:github, id))
end
|
.ghost ⇒ Object
Return (create if necessary) the ghost user. The ghost user owns records previously belonging to deleted users.
661
662
663
664
665
666
667
|
# File 'app/models/user.rb', line 661
def ghost
email = 'ghost%[email protected]'
unique_internal(where(user_type: :ghost), 'ghost', email) do |u|
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.')
u.name = 'Ghost User'
end
end
|
.limit_to_todo_authors(user: nil, with_todos: false, todo_state: nil) ⇒ Object
Limits the users to those that have TODOs, optionally in the given state.
user - The user to get the todos for.
with_todos - If we should limit the result set to users that are the
authors of todos.
todo_state - An optional state to require the todos to be in.
411
412
413
414
415
416
417
|
# File 'app/models/user.rb', line 411
def self.limit_to_todo_authors(user: nil, with_todos: false, todo_state: nil)
if user && with_todos
where(id: Todo.where(user: user, state: todo_state).select(:author_id))
else
all
end
end
|
.migration_bot ⇒ Object
679
680
681
682
683
684
685
686
687
|
# File 'app/models/user.rb', line 679
def migration_bot
email_pattern = "noreply+gitlab-migration-bot%[email protected]#{Settings.gitlab.host}"
unique_internal(where(user_type: :migration_bot), 'migration-bot', email_pattern) do |u|
u.bio = 'The GitLab migration bot'
u.name = 'GitLab Migration Bot'
u.confirmed_at = Time.zone.now
end
end
|
.password_length ⇒ Object
Devise method overridden to allow support for dynamic password lengths
461
462
463
|
# File 'app/models/user.rb', line 461
def password_length
Gitlab::CurrentSettings.minimum_password_length..Devise.password_length.max
end
|
.random_password ⇒ Object
Generate a random password that conforms to the current password length settings
466
467
468
|
# File 'app/models/user.rb', line 466
def random_password
Devise.friendly_token(password_length.max)
end
|
.reference_pattern ⇒ Object
Pattern used to extract `@user` user references from text
650
651
652
653
654
655
656
657
|
# File 'app/models/user.rb', line 650
def reference_pattern
@reference_pattern ||=
%r{
(?<!\w)
#{Regexp.escape(reference_prefix)}
(?<user>#{Gitlab::PathRegex::FULL_NAMESPACE_FORMAT_REGEX})
}x
end
|
.reference_prefix ⇒ Object
645
646
647
|
# File 'app/models/user.rb', line 645
def reference_prefix
'@'
end
|
.reorder_by_name ⇒ Object
591
592
593
|
# File 'app/models/user.rb', line 591
def reorder_by_name
reorder(:name)
end
|
.search(query, **options) ⇒ Object
Searches users matching the given query.
This method uses ILIKE on PostgreSQL.
query - The search query as a String
Returns an ActiveRecord::Relation.
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
|
# File 'app/models/user.rb', line 559
def search(query, **options)
query = query&.delete_prefix('@')
return none if query.blank?
query = query.downcase
order = <<~SQL
CASE
WHEN users.name = :query THEN 0
WHEN users.username = :query THEN 1
WHEN users.email = :query THEN 2
ELSE 3
END
SQL
sanitized_order_sql = Arel.sql(sanitize_sql_array([order, query: query]))
where(
fuzzy_arel_match(:name, query, lower_exact_match: true)
.or(fuzzy_arel_match(:username, query, lower_exact_match: true))
.or(arel_table[:email].eq(query))
).reorder(sanitized_order_sql, :name)
end
|
.search_with_secondary_emails(query) ⇒ Object
searches user by given pattern it compares name, email, username fields and user's secondary emails with given pattern This method uses ILIKE on PostgreSQL.
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
|
# File 'app/models/user.rb', line 599
def search_with_secondary_emails(query)
return none if query.blank?
query = query.downcase
email_table = Email.arel_table
matched_by_emails_user_ids = email_table
.project(email_table[:user_id])
.where(email_table[:email].eq(query))
where(
fuzzy_arel_match(:name, query)
.or(fuzzy_arel_match(:username, query))
.or(arel_table[:email].eq(query))
.or(arel_table[:id].in(matched_by_emails_user_ids))
)
end
|
.single_user ⇒ Object
705
706
707
|
# File 'app/models/user.rb', line 705
def single_user
User.non_internal.first if single_user?
end
|
.single_user? ⇒ Boolean
Return true if there is only single non-internal user in the deployment, ghost user is ignored.
701
702
703
|
# File 'app/models/user.rb', line 701
def single_user?
User.non_internal.limit(2).count == 1
end
|
.sort_by_attribute(method) ⇒ Object
480
481
482
483
484
485
486
487
488
489
490
491
|
# File 'app/models/user.rb', line 480
def sort_by_attribute(method)
order_method = method || 'id_desc'
case order_method.to_s
when 'recent_sign_in' then order_recent_sign_in
when 'oldest_sign_in' then order_oldest_sign_in
when 'last_activity_on_desc' then order_recent_last_activity
when 'last_activity_on_asc' then order_oldest_last_activity
else
order_by(order_method)
end
end
|
.support_bot ⇒ Object
689
690
691
692
693
694
695
696
697
|
# File 'app/models/user.rb', line 689
def support_bot
email_pattern = "support%[email protected]#{Settings.gitlab.host}"
unique_internal(where(user_type: :support_bot), 'support-bot', email_pattern) do |u|
u.bio = 'The GitLab support bot used for Service Desk'
u.name = 'GitLab Support Bot'
u.avatar = bot_avatar(image: 'support-bot.png')
end
end
|
.union_with_user(user_id = nil) ⇒ Object
Returns a relation that optionally includes the given user.
user_id - The ID of the user to include.
422
423
424
425
426
427
428
429
430
|
# File 'app/models/user.rb', line 422
def self.union_with_user(user_id = nil)
if user_id.present?
User.unscoped.from_union([all, User.unscoped.where(id: user_id)])
else
all
end
end
|
.where_not_in(users = nil) ⇒ Object
Limits the result set to users not in the given query/list of IDs.
users - The list of users to ignore. This can be an
`ActiveRecord::Relation`, or an Array.
587
588
589
|
# File 'app/models/user.rb', line 587
def where_not_in(users = nil)
users ? where.not(id: users) : all
end
|
.with_two_factor ⇒ Object
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
|
# File 'app/models/user.rb', line 432
def self.with_two_factor
with_u2f_registrations = <<-SQL
EXISTS (
SELECT *
FROM u2f_registrations AS u2f
WHERE u2f.user_id = users.id
) OR users.otp_required_for_login = ?
OR
EXISTS (
SELECT *
FROM webauthn_registrations AS webauthn
WHERE webauthn.user_id = users.id
)
SQL
where(with_u2f_registrations, true)
end
|
.with_visible_profile(user) ⇒ Object
393
394
395
396
397
398
399
400
401
|
# File 'app/models/user.rb', line 393
def self.with_visible_profile(user)
return with_public_profile if user.nil?
if user.admin?
all
else
with_public_profile.or(where(id: user.id))
end
end
|
.without_two_factor ⇒ Object
450
451
452
453
454
|
# File 'app/models/user.rb', line 450
def self.without_two_factor
joins("LEFT OUTER JOIN u2f_registrations AS u2f ON u2f.user_id = users.id
LEFT OUTER JOIN webauthn_registrations AS webauthn ON webauthn.user_id = users.id")
.where("u2f.id IS NULL AND webauthn.id IS NULL AND users.otp_required_for_login = ?", false)
end
|
Instance Method Details
#accept_pending_invitations! ⇒ Object
1252
1253
1254
1255
1256
|
# File 'app/models/user.rb', line 1252
def accept_pending_invitations!
pending_invitations.select do |member|
member.accept_invite!(self)
end
end
|
#access_level ⇒ Object
rubocop: enable CodeReuse/ServiceClass
1585
1586
1587
1588
1589
1590
1591
|
# File 'app/models/user.rb', line 1585
def access_level
if admin?
:admin
else
:regular
end
end
|
#access_level=(new_level) ⇒ Object
1593
1594
1595
1596
1597
1598
|
# File 'app/models/user.rb', line 1593
def access_level=(new_level)
new_level = new_level.to_s
return unless %w(admin regular).include?(new_level)
self.admin = (new_level == 'admin')
end
|
#accessible_deploy_keys ⇒ Object
1131
1132
1133
1134
1135
1136
|
# File 'app/models/user.rb', line 1131
def accessible_deploy_keys
DeployKey.from_union([
DeployKey.where(id: project_deploy_keys.select(:deploy_key_id)),
DeployKey.are_public
])
end
|
#active_for_authentication? ⇒ Boolean
379
380
381
|
# File 'app/models/user.rb', line 379
def active_for_authentication?
super && can?(:log_in)
end
|
#all_emails(include_private_email: true) ⇒ Object
1262
1263
1264
1265
1266
1267
1268
|
# File 'app/models/user.rb', line 1262
def all_emails(include_private_email: true)
all_emails = []
all_emails << email unless temp_oauth_email?
all_emails << private_commit_email if include_private_email
all_emails.concat(emails.map(&:email))
all_emails
end
|
#all_expanded_groups ⇒ Object
Returns a relation of groups the user has access to, including their parent and child groups (recursively).
#all_ssh_keys ⇒ Object
1234
1235
1236
|
# File 'app/models/user.rb', line 1234
def all_ssh_keys
keys.map(&:publishable_key)
end
|
#allow_password_authentication? ⇒ Boolean
1018
1019
1020
|
# File 'app/models/user.rb', line 1018
def allow_password_authentication?
allow_password_authentication_for_web? || allow_password_authentication_for_git?
end
|
#allow_password_authentication_for_git? ⇒ Boolean
1026
1027
1028
|
# File 'app/models/user.rb', line 1026
def allow_password_authentication_for_git?
Gitlab::CurrentSettings.password_authentication_enabled_for_git? && !ldap_user?
end
|
#allow_password_authentication_for_web? ⇒ Boolean
1022
1023
1024
|
# File 'app/models/user.rb', line 1022
def allow_password_authentication_for_web?
Gitlab::CurrentSettings.password_authentication_enabled_for_web? && !ldap_user?
end
|
#already_forked?(project) ⇒ Boolean
1099
1100
1101
|
# File 'app/models/user.rb', line 1099
def already_forked?(project)
!!fork_of(project)
end
|
#any_email?(check_email) ⇒ Boolean
1284
1285
1286
1287
1288
1289
1290
1291
1292
|
# File 'app/models/user.rb', line 1284
def any_email?(check_email)
downcased = check_email.downcase
return true if persisted? &&
id == Gitlab::PrivateCommitEmail.user_id_for_email(downcased)
all_emails.include?(check_email.downcase)
end
|
#assigned_open_issues_count(force: false) ⇒ Object
1508
1509
1510
1511
1512
|
# File 'app/models/user.rb', line 1508
def assigned_open_issues_count(force: false)
Rails.cache.fetch(['users', id, 'assigned_open_issues_count'], force: force, expires_in: 20.minutes) do
IssuesFinder.new(self, assignee_id: self.id, state: 'opened', non_archived: true).execute.count
end
end
|
#assigned_open_merge_requests_count(force: false) ⇒ Object
1502
1503
1504
1505
1506
|
# File 'app/models/user.rb', line 1502
def assigned_open_merge_requests_count(force: false)
Rails.cache.fetch(['users', id, 'assigned_open_merge_requests_count'], force: force, expires_in: 20.minutes) do
MergeRequestsFinder.new(self, assignee_id: self.id, state: 'opened', non_archived: true).execute.count
end
end
|
#authorizations_for_projects(min_access_level: nil, related_project_column: 'projects.id') ⇒ Object
Typically used in conjunction with projects table to get projects a user has been given access to. The param `related_project_column` is the column to compare to the project_authorizations. By default is projects.id
Example use: `Project.where('EXISTS(?)', user.authorizations_for_projects)`
953
954
955
956
957
958
959
960
961
|
# File 'app/models/user.rb', line 953
def authorizations_for_projects(min_access_level: nil, related_project_column: 'projects.id')
authorizations = project_authorizations
.select(1)
.where("project_authorizations.project_id = #{related_project_column}")
return authorizations unless min_access_level.present?
authorizations.where('project_authorizations.access_level >= ?', min_access_level)
end
|
#authorized_groups ⇒ Object
Returns the groups a user has access to, either through a membership or a project authorization
889
890
891
892
893
894
895
896
|
# File 'app/models/user.rb', line 889
def authorized_groups
Group.unscoped do
Group.from_union([
groups,
authorized_projects.joins(:namespace).select('namespaces.*')
])
end
end
|
#authorized_project?(project, min_access_level = nil) ⇒ Boolean
942
943
944
|
# File 'app/models/user.rb', line 942
def authorized_project?(project, min_access_level = nil)
authorized_projects(min_access_level).exists?({ id: project.id })
end
|
#authorized_projects(min_access_level = nil) ⇒ Object
929
930
931
932
933
934
935
936
937
938
939
940
|
# File 'app/models/user.rb', line 929
def authorized_projects(min_access_level = nil)
projects = super()
if min_access_level
projects = projects
.where('project_authorizations.access_level >= ?', min_access_level)
end
projects
end
|
#avatar_url(size: nil, scale: 2, **args) ⇒ Object
rubocop: disable CodeReuse/ServiceClass
1243
1244
1245
|
# File 'app/models/user.rb', line 1243
def avatar_url(size: nil, scale: 2, **args)
GravatarService.new.execute(email, size, scale, username: username)
end
|
#can?(action, subject = :global) ⇒ Boolean
1046
1047
1048
|
# File 'app/models/user.rb', line 1046
def can?(action, subject = :global)
Ability.allowed?(self, action, subject)
end
|
#can_be_deactivated? ⇒ Boolean
1707
1708
1709
|
# File 'app/models/user.rb', line 1707
def can_be_deactivated?
active? && no_recent_activity?
end
|
#can_be_removed? ⇒ Boolean
1437
1438
1439
|
# File 'app/models/user.rb', line 1437
def can_be_removed?
!solo_owned_groups.present?
end
|
#can_change_username? ⇒ Boolean
1030
1031
1032
|
# File 'app/models/user.rb', line 1030
def can_change_username?
gitlab_config.username_changing_enabled
end
|
#can_create_group? ⇒ Boolean
1038
1039
1040
|
# File 'app/models/user.rb', line 1038
def can_create_group?
can?(:create_group)
end
|
#can_create_project? ⇒ Boolean
1034
1035
1036
|
# File 'app/models/user.rb', line 1034
def can_create_project?
projects_limit_left > 0
end
|
#can_leave_project?(project) ⇒ Boolean
1219
1220
1221
1222
|
# File 'app/models/user.rb', line 1219
def can_leave_project?(project)
project.namespace != namespace &&
project.project_member(self)
end
|
#can_read_all_resources? ⇒ Boolean
1600
1601
1602
|
# File 'app/models/user.rb', line 1600
def can_read_all_resources?
can?(:read_all_resources)
end
|
#can_select_namespace? ⇒ Boolean
1042
1043
1044
|
# File 'app/models/user.rb', line 1042
def can_select_namespace?
several_namespaces? || admin
end
|
#check_for_verified_email ⇒ Object
see if the new email is already a verified secondary email
862
863
864
|
# File 'app/models/user.rb', line 862
def check_for_verified_email
skip_reconfirmation! if emails.confirmed.where(email: self.email).any?
end
|
#ci_owned_runners ⇒ Object
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
|
# File 'app/models/user.rb', line 1441
def ci_owned_runners
@ci_owned_runners ||= begin
project_runners = Ci::RunnerProject
.where(project: authorized_projects(Gitlab::Access::MAINTAINER))
.joins(:runner)
.select('ci_runners.*')
group_runners = Ci::RunnerNamespace
.where(namespace_id: Gitlab::ObjectHierarchy.new(owned_groups).base_and_descendants.select(:id))
.joins(:runner)
.select('ci_runners.*')
Ci::Runner.from_union([project_runners, group_runners])
end
end
|
#commit_email ⇒ Object
Define commit_email-related attribute methods explicitly instead of relying on ActiveRecord to provide them. Some of the specs use the current state of the model code but an older database schema, so we need to guard against the possibility of the commit_email column not existing.
838
839
840
841
842
843
844
845
846
847
|
# File 'app/models/user.rb', line 838
def commit_email
return self.email unless has_attribute?(:commit_email)
if super == Gitlab::PrivateCommitEmail::TOKEN
return private_commit_email
end
super.presence || self.email
end
|
#commit_email=(email) ⇒ Object
849
850
851
|
# File 'app/models/user.rb', line 849
def commit_email=(email)
super if has_attribute?(:commit_email)
end
|
#commit_email_changed? ⇒ Boolean
853
854
855
|
# File 'app/models/user.rb', line 853
def commit_email_changed?
has_attribute?(:commit_email) && super
end
|
#confirm_deletion_with_password? ⇒ Boolean
1050
1051
1052
|
# File 'app/models/user.rb', line 1050
def confirm_deletion_with_password?
!password_automatically_set? && allow_password_authentication?
end
|
#confirmation_required_on_sign_in? ⇒ Boolean
1740
1741
1742
|
# File 'app/models/user.rb', line 1740
def confirmation_required_on_sign_in?
!confirmed? && !confirmation_period_valid?
end
|
#contributed_projects ⇒ Object
Returns the projects a user contributed to in the last year.
This method relies on a subquery as this performs significantly better compared to a JOIN when coupled with, for example, `Project.visible_to_user`. That is, consider the following code:
some_user.contributed_projects.visible_to_user(other_user)
If this method were to use a JOIN the resulting query would take roughly 200 ms on a database with a similar size to GitLab.com's database. On the other hand, using a subquery means we can get the exact same data in about 40 ms.
1427
1428
1429
1430
1431
1432
1433
1434
1435
|
# File 'app/models/user.rb', line 1427
def contributed_projects
events = Event.select(:project_id)
.contributions.where(author_id: self)
.where("created_at > ?", Time.current - 1.year)
.distinct
.reorder(nil)
Project.where(id: events)
end
|
#created_by ⇒ Object
1138
1139
1140
|
# File 'app/models/user.rb', line 1138
def created_by
User.find_by(id: created_by_id) if created_by_id
end
|
#created_recently? ⇒ Boolean
1748
1749
1750
|
# File 'app/models/user.rb', line 1748
def created_recently?
created_at > Devise.confirm_within.ago
end
|
#current_highest_access_level ⇒ Object
Load the current highest access by looking directly at the user's memberships
1736
1737
1738
|
# File 'app/models/user.rb', line 1736
def current_highest_access_level
members.non_request.maximum(:access_level)
end
|
#delete_async(deleted_by:, params: {}) ⇒ Object
rubocop: enable CodeReuse/ServiceClass
1344
1345
1346
1347
|
# File 'app/models/user.rb', line 1344
def delete_async(deleted_by:, params: {})
block if params[:hard_delete]
DeleteUserWorker.perform_async(deleted_by.id, id, params.to_h)
end
|
#disable_two_factor! ⇒ Object
755
756
757
758
759
760
761
762
763
764
765
766
767
768
|
# File 'app/models/user.rb', line 755
def disable_two_factor!
transaction do
update(
otp_required_for_login: false,
encrypted_otp_secret: nil,
encrypted_otp_secret_iv: nil,
encrypted_otp_secret_salt: nil,
otp_grace_period_started_at: nil,
otp_backup_codes: nil
)
self.u2f_registrations.destroy_all self.webauthn_registrations.destroy_all end
end
|
#dismissed_callout?(feature_name:, ignore_dismissal_earlier_than: nil) ⇒ Boolean
1728
1729
1730
1731
1732
1733
|
# File 'app/models/user.rb', line 1728
def dismissed_callout?(feature_name:, ignore_dismissal_earlier_than: nil)
callouts = self.callouts.with_feature_name(feature_name)
callouts = callouts.with_dismissed_after(ignore_dismissal_earlier_than) if ignore_dismissal_earlier_than
callouts.any?
end
|
#ensure_namespace_correct ⇒ Object
1313
1314
1315
1316
1317
1318
1319
1320
1321
|
# File 'app/models/user.rb', line 1313
def ensure_namespace_correct
if namespace
namespace.path = username if username_changed?
namespace.name = name if name_changed?
else
namespace = build_namespace(path: username, name: name)
namespace.build_namespace_settings
end
end
|
#expanded_groups_requiring_two_factor_authentication ⇒ Object
909
910
911
|
# File 'app/models/user.rb', line 909
def expanded_groups_requiring_two_factor_authentication
all_expanded_groups.where(require_two_factor_authentication: true)
end
|
#feed_token ⇒ Object
each existing user needs to have an `feed_token`. we do this on read since migrating all existing users is not a feasible solution.
1616
1617
1618
|
# File 'app/models/user.rb', line 1616
def feed_token
ensure_feed_token!
end
|
#first_name ⇒ Object
1054
1055
1056
1057
1058
|
# File 'app/models/user.rb', line 1054
def first_name
read_attribute(:first_name) || begin
name.split(' ').first unless name.blank?
end
end
|
#fork_of(project) ⇒ Object
1103
1104
1105
|
# File 'app/models/user.rb', line 1103
def fork_of(project)
namespace.find_fork_of(project)
end
|
#full_path ⇒ Object
714
715
716
|
# File 'app/models/user.rb', line 714
def full_path
username
end
|
#full_website_url ⇒ Object
1224
1225
1226
1227
1228
|
# File 'app/models/user.rb', line 1224
def full_website_url
return "http://#{website_url}" if website_url !~ %r{\Ahttps?://}
website_url
end
|
#generate_reset_token ⇒ Object
734
735
736
737
738
739
740
741
|
# File 'app/models/user.rb', line 734
def generate_reset_token
@reset_token, enc = Devise.token_generator.generate(self.class, :reset_password_token)
self.reset_password_token = enc
self.reset_password_sent_at = Time.current.utc
@reset_token
end
|
#global_notification_setting ⇒ Object
Lazy load global notification setting Initializes User setting with Participating level if setting not persisted
1493
1494
1495
1496
1497
1498
1499
1500
|
# File 'app/models/user.rb', line 1493
def global_notification_setting
return @global_notification_setting if defined?(@global_notification_setting)
@global_notification_setting = notification_settings.find_or_initialize_by(source: nil)
@global_notification_setting.update(level: NotificationSetting.levels[DEFAULT_NOTIFICATION_LEVEL]) unless @global_notification_setting.persisted?
@global_notification_setting
end
|
#highest_role ⇒ Object
1127
1128
1129
|
# File 'app/models/user.rb', line 1127
def highest_role
user_highest_role&.highest_access_level || Gitlab::Access::NO_ACCESS
end
|
#hook_attrs ⇒ Object
1304
1305
1306
1307
1308
1309
1310
1311
|
# File 'app/models/user.rb', line 1304
def hook_attrs
{
name: name,
username: username,
avatar_url: avatar_url(only_path: false),
email: email
}
end
|
#impersonated? ⇒ Boolean
1744
1745
1746
|
# File 'app/models/user.rb', line 1744
def impersonated?
impersonator.present?
end
|
#inactive_message ⇒ Object
383
384
385
386
387
388
389
390
391
|
# File 'app/models/user.rb', line 383
def inactive_message
if blocked?
BLOCKED_MESSAGE
elsif internal?
LOGIN_FORBIDDEN
else
super
end
end
|
#increment_failed_attempts! ⇒ Object
This is copied from Devise::Models::Lockable#valid_for_authentication?, as our auth flow means we don't call that automatically (and can't conveniently do so).
See:
<https://github.com/plataformatec/devise/blob/v4.7.1/lib/devise/models/lockable.rb#L104>
rubocop: disable CodeReuse/ServiceClass
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
|
# File 'app/models/user.rb', line 1572
def increment_failed_attempts!
return if ::Gitlab::Database.read_only?
increment_failed_attempts
if attempts_exceeded?
lock_access! unless access_locked?
else
Users::UpdateService.new(self, user: self).execute(validate: false)
end
end
|
#invalidate_cache_counts ⇒ Object
1537
1538
1539
1540
1541
1542
1543
|
# File 'app/models/user.rb', line 1537
def invalidate_cache_counts
invalidate_issue_cache_counts
invalidate_merge_request_cache_counts
invalidate_todos_done_count
invalidate_todos_pending_count
invalidate_personal_projects_count
end
|
#invalidate_issue_cache_counts ⇒ Object
1545
1546
1547
|
# File 'app/models/user.rb', line 1545
def invalidate_issue_cache_counts
Rails.cache.delete(['users', id, 'assigned_open_issues_count'])
end
|
#invalidate_merge_request_cache_counts ⇒ Object
1549
1550
1551
|
# File 'app/models/user.rb', line 1549
def invalidate_merge_request_cache_counts
Rails.cache.delete(['users', id, 'assigned_open_merge_requests_count'])
end
|
#invalidate_personal_projects_count ⇒ Object
1561
1562
1563
|
# File 'app/models/user.rb', line 1561
def invalidate_personal_projects_count
Rails.cache.delete(['users', id, 'personal_projects_count'])
end
|
#invalidate_todos_done_count ⇒ Object
1553
1554
1555
|
# File 'app/models/user.rb', line 1553
def invalidate_todos_done_count
Rails.cache.delete(['users', id, 'todos_done_count'])
end
|
#invalidate_todos_pending_count ⇒ Object
1557
1558
1559
|
# File 'app/models/user.rb', line 1557
def invalidate_todos_pending_count
Rails.cache.delete(['users', id, 'todos_pending_count'])
end
|
#last_active_at ⇒ Object
1711
1712
1713
1714
1715
1716
|
# File 'app/models/user.rb', line 1711
def last_active_at
last_activity = last_activity_on&.to_time&.in_time_zone
last_sign_in = current_sign_in_at
[last_activity, last_sign_in].compact.max
end
|
#last_name ⇒ Object
1060
1061
1062
1063
1064
|
# File 'app/models/user.rb', line 1060
def last_name
read_attribute(:last_name) || begin
name.split(' ').drop(1).join(' ') unless name.blank?
end
end
|
#ldap_identity ⇒ Object
1115
1116
1117
|
# File 'app/models/user.rb', line 1115
def ldap_identity
@ldap_identity ||= identities.find_by(["provider LIKE ?", "ldap%"])
end
|
#ldap_sync_time ⇒ Object
1193
1194
1195
1196
|
# File 'app/models/user.rb', line 1193
def ldap_sync_time
1.hour
end
|
#ldap_user? ⇒ Boolean
1107
1108
1109
1110
1111
1112
1113
|
# File 'app/models/user.rb', line 1107
def ldap_user?
if identities.loaded?
identities.find { |identity| Gitlab::Auth::OAuth::Provider.ldap_provider?(identity.provider) && !identity.extern_uid.nil? }
else
identities.exists?(["provider LIKE ? AND extern_uid IS NOT NULL", "ldap%"])
end
end
|
#lock_access! ⇒ Object
1644
1645
1646
1647
|
# File 'app/models/user.rb', line 1644
def lock_access!
Gitlab::AppLogger.info("Account Locked: username=#{username}")
super
end
|
#log_info(message) ⇒ Object
rubocop: enable CodeReuse/ServiceClass
1355
1356
1357
|
# File 'app/models/user.rb', line 1355
def log_info(message)
Gitlab::AppLogger.info message
end
|
#manageable_groups(include_groups_with_developer_maintainer_access: false) ⇒ Object
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
|
# File 'app/models/user.rb', line 1386
def manageable_groups(include_groups_with_developer_maintainer_access: false)
owned_and_maintainer_group_hierarchy = Gitlab::ObjectHierarchy.new(owned_or_maintainers_groups).base_and_descendants
if include_groups_with_developer_maintainer_access
union_sql = ::Gitlab::SQL::Union.new(
[owned_and_maintainer_group_hierarchy,
groups_with_developer_maintainer_project_access]).to_sql
::Group.from("(#{union_sql}) #{::Group.table_name}")
else
owned_and_maintainer_group_hierarchy
end
end
|
#manageable_groups_with_routes(include_groups_with_developer_maintainer_access: false) ⇒ Object
1400
1401
1402
1403
1404
|
# File 'app/models/user.rb', line 1400
def manageable_groups_with_routes(include_groups_with_developer_maintainer_access: false)
manageable_groups(include_groups_with_developer_maintainer_access: include_groups_with_developer_maintainer_access)
.eager_load(:route)
.order('routes.path')
end
|
#manageable_namespaces ⇒ Object
1382
1383
1384
|
# File 'app/models/user.rb', line 1382
def manageable_namespaces
@manageable_namespaces ||= [namespace] + manageable_groups
end
|
#matches_identity?(provider, extern_uid) ⇒ Boolean
1119
1120
1121
|
# File 'app/models/user.rb', line 1119
def matches_identity?(provider, extern_uid)
identities.where(provider: provider, extern_uid: extern_uid).exists?
end
|
#max_member_access_for_group(group_id) ⇒ Object
1673
1674
1675
|
# File 'app/models/user.rb', line 1673
def max_member_access_for_group(group_id)
max_member_access_for_group_ids([group_id])[group_id]
end
|
#max_member_access_for_group_ids(group_ids) ⇒ Object
Determine the maximum access level for a group of groups in bulk.
Returns a Hash mapping project ID -> maximum access level.
1667
1668
1669
1670
1671
|
# File 'app/models/user.rb', line 1667
def max_member_access_for_group_ids(group_ids)
max_member_access_for_resource_ids(Group, group_ids) do |group_ids|
group_members.where(source: group_ids).group(:source_id).maximum(:access_level)
end
end
|
#max_member_access_for_project(project_id) ⇒ Object
1660
1661
1662
|
# File 'app/models/user.rb', line 1660
def max_member_access_for_project(project_id)
max_member_access_for_project_ids([project_id])[project_id]
end
|
#max_member_access_for_project_ids(project_ids) ⇒ Object
Determine the maximum access level for a group of projects in bulk.
Returns a Hash mapping project ID -> maximum access level.
1652
1653
1654
1655
1656
1657
1658
|
# File 'app/models/user.rb', line 1652
def max_member_access_for_project_ids(project_ids)
max_member_access_for_resource_ids(Project, project_ids) do |project_ids|
project_authorizations.where(project: project_ids)
.group(:project_id)
.maximum(:access_level)
end
end
|
#membership_groups ⇒ Object
Returns the groups a user is a member of, either directly or through a parent group
#name_with_username ⇒ Object
1095
1096
1097
|
# File 'app/models/user.rb', line 1095
def name_with_username
"#{name} (#{username})"
end
|
#namespace_id ⇒ Object
1091
1092
1093
|
# File 'app/models/user.rb', line 1091
def namespace_id
namespace.try :id
end
|
#namespace_move_dir_allowed ⇒ Object
796
797
798
799
800
|
# File 'app/models/user.rb', line 796
def namespace_move_dir_allowed
if namespace&.any_project_has_container_registry_tags?
errors.add(:username, _('cannot be changed if a personal project has container registry tags.'))
end
end
|
#namespaces ⇒ Object
1406
1407
1408
1409
1410
|
# File 'app/models/user.rb', line 1406
def namespaces
namespace_ids = groups.pluck(:id)
namespace_ids.push(namespace.id)
Namespace.where(id: namespace_ids)
end
|
#notification_email_for(notification_group) ⇒ Object
1457
1458
1459
1460
|
# File 'app/models/user.rb', line 1457
def notification_email_for(notification_group)
notification_group&.notification_email_for(self) || notification_email
end
|
#notification_service ⇒ Object
rubocop: disable CodeReuse/ServiceClass
1350
1351
1352
|
# File 'app/models/user.rb', line 1350
def notification_service
NotificationService.new
end
|
#notification_settings_for(source, inherit: false) ⇒ Object
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
|
# File 'app/models/user.rb', line 1462
def notification_settings_for(source, inherit: false)
if notification_settings.loaded?
notification_settings.find do |notification|
notification.source_type == source.class.base_class.name &&
notification.source_id == source.id
end
else
notification_settings.find_or_initialize_by(source: source) do |ns|
next unless source.is_a?(Group) && inherit
ancestor_ns = source
.notification_settings(hierarchy_order: :asc)
.where(user: self)
.find_by('level != ? OR notification_email IS NOT NULL', NotificationSetting.levels[:global])
ns.assign_attributes(ancestor_ns&.slice(*NotificationSetting.allowed_fields))
ns.source = source
ns.user = self
end
end
end
|
#notification_settings_for_groups(groups) ⇒ Object
1486
1487
1488
1489
|
# File 'app/models/user.rb', line 1486
def notification_settings_for_groups(groups)
ids = groups.is_a?(ActiveRecord::Relation) ? groups.select(:id) : groups.map(&:id)
notification_settings.for_groups.where(source_id: ids)
end
|
#oauth_authorized_tokens ⇒ Object
1412
1413
1414
|
# File 'app/models/user.rb', line 1412
def oauth_authorized_tokens
Doorkeeper::AccessToken.where(resource_owner_id: id, revoked_at: nil)
end
|
#owned_projects ⇒ Object
973
974
975
976
977
978
979
980
981
982
983
|
# File 'app/models/user.rb', line 973
def owned_projects
@owned_projects ||= Project.from_union(
[
Project.where(namespace: namespace),
Project.joins(:project_authorizations)
.where("projects.namespace_id <> ?", namespace.id)
.where(project_authorizations: { user_id: id, access_level: Gitlab::Access::OWNER })
],
remove_duplicates: false
)
end
|
#owns_commit_email ⇒ Object
827
828
829
830
831
|
# File 'app/models/user.rb', line 827
def owns_commit_email
return if read_attribute(:commit_email).blank?
errors.add(:commit_email, _("is not an email you own")) unless verified_emails.include?(commit_email)
end
|
#owns_notification_email ⇒ Object
815
816
817
818
819
|
# File 'app/models/user.rb', line 815
def owns_notification_email
return if new_record? || temp_oauth_email?
errors.add(:notification_email, _("is not an email you own")) unless verified_emails.include?(notification_email)
end
|
#owns_public_email ⇒ Object
821
822
823
824
825
|
# File 'app/models/user.rb', line 821
def owns_public_email
return if public_email.blank?
errors.add(:public_email, _("is not an email you own")) unless verified_emails.include?(public_email)
end
|
#password_expired? ⇒ Boolean
1703
1704
1705
|
# File 'app/models/user.rb', line 1703
def password_expired?
!!(password_expires_at && password_expires_at < Time.current)
end
|
#pending_invitations ⇒ Object
1258
1259
1260
|
# File 'app/models/user.rb', line 1258
def pending_invitations
Member.where(invite_email: verified_emails).invite
end
|
#pending_todo_for(target) ⇒ Object
1699
1700
1701
|
# File 'app/models/user.rb', line 1699
def pending_todo_for(target)
todos.find_by(target: target, state: :pending)
end
|
#personal_projects_count(force: false) ⇒ Object
1526
1527
1528
1529
1530
|
# File 'app/models/user.rb', line 1526
def personal_projects_count(force: false)
Rails.cache.fetch(['users', id, 'personal_projects_count'], force: force, expires_in: 24.hours, raw: true) do
personal_projects.count
end.to_i
end
|
#post_destroy_hook ⇒ Object
1332
1333
1334
1335
1336
|
# File 'app/models/user.rb', line 1332
def post_destroy_hook
log_info("User \"#{name}\" (#{email}) was removed")
system_hook_service.execute_hooks_for(self, :destroy)
end
|
#preferred_language ⇒ Object
373
374
375
376
377
|
# File 'app/models/user.rb', line 373
def preferred_language
read_attribute('preferred_language') ||
I18n.default_locale.to_s.presence_in(Gitlab::I18n::AVAILABLE_LANGUAGES.keys) ||
'en'
end
|
#primary_email_verified? ⇒ Boolean
rubocop: enable CodeReuse/ServiceClass
1248
1249
1250
|
# File 'app/models/user.rb', line 1248
def primary_email_verified?
confirmed? && !temp_oauth_email?
end
|
#private_commit_email ⇒ Object
#project_deploy_keys ⇒ Object
1123
1124
1125
|
# File 'app/models/user.rb', line 1123
def project_deploy_keys
@project_deploy_keys ||= DeployKey.in_projects(authorized_projects.select(:id)).distinct(:id)
end
|
#projects_limit_left ⇒ Object
1066
1067
1068
|
# File 'app/models/user.rb', line 1066
def projects_limit_left
projects_limit - personal_projects_count
end
|
#projects_where_can_admin_issues ⇒ Object
Returns projects which user can admin issues on (for example to move an issue to that project).
This logic is duplicated from `Ability#project_abilities` into a SQL form.
988
989
990
|
# File 'app/models/user.rb', line 988
def projects_where_can_admin_issues
authorized_projects(Gitlab::Access::REPORTER).non_archived.with_issues_enabled
end
|
#projects_with_reporter_access_limited_to(projects) ⇒ Object
Returns the projects this user has reporter (or greater) access to, limited to at most the given projects.
This method is useful when you have a list of projects and want to efficiently check to which of these projects the user has at least reporter access.
969
970
971
|
# File 'app/models/user.rb', line 969
def projects_with_reporter_access_limited_to(projects)
authorized_projects(Gitlab::Access::REPORTER).where(id: projects)
end
|
#public_verified_emails ⇒ Object
1278
1279
1280
1281
1282
|
# File 'app/models/user.rb', line 1278
def public_verified_emails
emails = verified_emails(include_private_email: false)
emails << email unless temp_oauth_email?
emails.uniq
end
|
#read_only_attribute?(attribute) ⇒ Boolean
1639
1640
1641
|
# File 'app/models/user.rb', line 1639
def read_only_attribute?(attribute)
user_synced_attributes_metadata&.read_only?(attribute)
end
|
#recent_push(project = nil) ⇒ Object
rubocop: disable CodeReuse/ServiceClass
1071
1072
1073
1074
1075
1076
1077
1078
1079
|
# File 'app/models/user.rb', line 1071
def recent_push(project = nil)
service = Users::LastPushEventService.new(self)
if project
service.last_event_for_project(project)
else
service.last_event_for_user
end
end
|
#recently_sent_password_reset? ⇒ Boolean
743
744
745
|
# File 'app/models/user.rb', line 743
def recently_sent_password_reset?
reset_password_sent_at.present? && reset_password_sent_at >= 1.minute.ago
end
|
#refresh_authorized_projects ⇒ Object
rubocop: disable CodeReuse/ServiceClass
#remove_key_cache ⇒ Object
rubocop: disable CodeReuse/ServiceClass
#remove_project_authorizations(project_ids) ⇒ Object
rubocop: enable CodeReuse/ServiceClass
925
926
927
|
# File 'app/models/user.rb', line 925
def remove_project_authorizations(project_ids)
project_authorizations.where(project_id: project_ids).delete_all
end
|
1014
1015
1016
|
# File 'app/models/user.rb', line 1014
def
require_password_creation_for_git? || require_personal_access_token_creation_for_git_auth?
end
|
#require_password_creation_for_git? ⇒ Boolean
1004
1005
1006
|
# File 'app/models/user.rb', line 1004
def require_password_creation_for_git?
allow_password_authentication_for_git? && password_automatically_set?
end
|
#require_password_creation_for_web? ⇒ Boolean
rubocop: enable CodeReuse/ServiceClass
1000
1001
1002
|
# File 'app/models/user.rb', line 1000
def require_password_creation_for_web?
allow_password_authentication_for_web? && password_automatically_set?
end
|
#require_personal_access_token_creation_for_git_auth? ⇒ Boolean
1008
1009
1010
1011
1012
|
# File 'app/models/user.rb', line 1008
def require_personal_access_token_creation_for_git_auth?
return false if allow_password_authentication_for_git? || ldap_user?
PersonalAccessTokensFinder.new(user: self, impersonation: false, state: 'active').execute.none?
end
|
#require_ssh_key? ⇒ Boolean
rubocop: disable CodeReuse/ServiceClass
#required_terms_not_accepted? ⇒ Boolean
#requires_ldap_check? ⇒ Boolean
1183
1184
1185
1186
1187
1188
1189
1190
1191
|
# File 'app/models/user.rb', line 1183
def requires_ldap_check?
if !Gitlab.config.ldap.enabled
false
elsif ldap_user?
!last_credential_check_at || (last_credential_check_at + ldap_sync_time) < Time.current
else
false
end
end
|
#requires_usage_stats_consent? ⇒ Boolean
1686
1687
1688
|
# File 'app/models/user.rb', line 1686
def requires_usage_stats_consent?
self.admin? && 7.days.ago > self.created_at && !has_current_license? && User.single_user? && !consented_usage_stats?
end
|
#role_required? ⇒ Boolean
1720
1721
1722
|
# File 'app/models/user.rb', line 1720
def role_required?
role_before_type_cast == REQUIRES_ROLE_VALUE
end
|
#sanitize_attrs ⇒ Object
1142
1143
1144
1145
1146
1147
|
# File 'app/models/user.rb', line 1142
def sanitize_attrs
%i[skype linkedin twitter].each do |attr|
value = self[attr]
self[attr] = Sanitize.clean(value) if value.present?
end
end
|
#set_commit_email ⇒ Object
1161
1162
1163
1164
1165
|
# File 'app/models/user.rb', line 1161
def set_commit_email
if commit_email.blank? || verified_emails.exclude?(commit_email)
self.commit_email = nil
end
end
|
#set_notification_email ⇒ Object
1149
1150
1151
1152
1153
|
# File 'app/models/user.rb', line 1149
def set_notification_email
if notification_email.blank? || all_emails.exclude?(notification_email)
self.notification_email = email
end
end
|
#set_projects_limit ⇒ Object
1174
1175
1176
1177
1178
1179
1180
1181
|
# File 'app/models/user.rb', line 1174
def set_projects_limit
return unless has_attribute?(:projects_limit) && projects_limit.nil?
self.projects_limit = Gitlab::CurrentSettings.default_projects_limit
end
|
#set_public_email ⇒ Object
1155
1156
1157
1158
1159
|
# File 'app/models/user.rb', line 1155
def set_public_email
if public_email.blank? || all_emails.exclude?(public_email)
self.public_email = ''
end
end
|
#set_role_required! ⇒ Object
1724
1725
1726
|
# File 'app/models/user.rb', line 1724
def set_role_required!
update_column(:role, REQUIRES_ROLE_VALUE)
end
|
#set_username_errors ⇒ Object
1323
1324
1325
1326
|
# File 'app/models/user.rb', line 1323
def set_username_errors
namespace_path_errors = self.errors.delete(:"namespace.path")
self.errors[:username].concat(namespace_path_errors) if namespace_path_errors
end
|
#several_namespaces? ⇒ Boolean
rubocop: enable CodeReuse/ServiceClass
1082
1083
1084
1085
1086
1087
1088
1089
|
# File 'app/models/user.rb', line 1082
def several_namespaces?
union_sql = ::Gitlab::SQL::Union.new(
[owned_groups,
maintainers_groups,
groups_with_developer_maintainer_project_access]).to_sql
::Group.from("(#{union_sql}) #{::Group.table_name}").any?
end
|
#short_website_url ⇒ Object
1230
1231
1232
|
# File 'app/models/user.rb', line 1230
def short_website_url
website_url.sub(%r{\Ahttps?://}, '')
end
|
#skip_confirmation=(bool) ⇒ Object
726
727
728
|
# File 'app/models/user.rb', line 726
def skip_confirmation=(bool)
skip_confirmation! if bool
end
|
#skip_reconfirmation=(bool) ⇒ Object
730
731
732
|
# File 'app/models/user.rb', line 730
def skip_reconfirmation=(bool)
skip_reconfirmation! if bool
end
|
#solo_owned_groups ⇒ Object
1205
1206
1207
1208
1209
|
# File 'app/models/user.rb', line 1205
def solo_owned_groups
@solo_owned_groups ||= owned_groups.select do |group|
group.owners == [self]
end
end
|
#source_groups_of_two_factor_authentication_requirement ⇒ Object
913
914
915
916
917
|
# File 'app/models/user.rb', line 913
def source_groups_of_two_factor_authentication_requirement
Gitlab::ObjectHierarchy.new(expanded_groups_requiring_two_factor_authentication)
.all_objects
.where(id: groups)
end
|
#starred?(project) ⇒ Boolean
rubocop: enable CodeReuse/ServiceClass
1365
1366
1367
|
# File 'app/models/user.rb', line 1365
def starred?(project)
starred_projects.exists?(project.id)
end
|
#static_object_token ⇒ Object
Each existing user needs to have a `static_object_token`. We do this on read since migrating all existing users is not a feasible solution.
1623
1624
1625
|
# File 'app/models/user.rb', line 1623
def static_object_token
ensure_static_object_token!
end
|
#sync_attribute?(attribute) ⇒ Boolean
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
|
# File 'app/models/user.rb', line 1627
def sync_attribute?(attribute)
return true if ldap_user? && attribute == :email
attributes = Gitlab.config.omniauth.sync_profile_attributes
if attributes.is_a?(Array)
attributes.include?(attribute.to_s)
else
attributes
end
end
|
#system_hook_service ⇒ Object
rubocop: disable CodeReuse/ServiceClass
1360
1361
1362
|
# File 'app/models/user.rb', line 1360
def system_hook_service
SystemHooksService.new
end
|
#temp_oauth_email? ⇒ Boolean
1238
1239
1240
|
# File 'app/models/user.rb', line 1238
def temp_oauth_email?
email.start_with?('temp-email-for-oauth')
end
|
#terms_accepted? ⇒ Boolean
1677
1678
1679
|
# File 'app/models/user.rb', line 1677
def terms_accepted?
accepted_term_id.present?
end
|
#to_param ⇒ Object
718
719
720
|
# File 'app/models/user.rb', line 718
def to_param
username
end
|
#to_reference(_from = nil, target_project: nil, full: nil) ⇒ Object
722
723
724
|
# File 'app/models/user.rb', line 722
def to_reference(_from = nil, target_project: nil, full: nil)
"#{self.class.reference_prefix}#{username}"
end
|
#todos_done_count(force: false) ⇒ Object
1514
1515
1516
1517
1518
|
# File 'app/models/user.rb', line 1514
def todos_done_count(force: false)
Rails.cache.fetch(['users', id, 'todos_done_count'], force: force, expires_in: 20.minutes) do
TodosFinder.new(self, state: :done).execute.count
end
end
|
#todos_pending_count(force: false) ⇒ Object
1520
1521
1522
1523
1524
|
# File 'app/models/user.rb', line 1520
def todos_pending_count(force: false)
Rails.cache.fetch(['users', id, 'todos_pending_count'], force: force, expires_in: 20.minutes) do
TodosFinder.new(self, state: :pending).execute.count
end
end
|
#toggle_star(project) ⇒ Object
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
|
# File 'app/models/user.rb', line 1369
def toggle_star(project)
UsersStarProject.transaction do
user_star_project = users_star_projects
.where(project: project, user: self).lock(true).first
if user_star_project
user_star_project.destroy
else
UsersStarProject.create!(project: project, user: self)
end
end
end
|
#try_obtain_ldap_lease ⇒ Object
1198
1199
1200
1201
1202
1203
|
# File 'app/models/user.rb', line 1198
def try_obtain_ldap_lease
lease = Gitlab::ExclusiveLease.new("user_ldap_check:#{id}", timeout: 600)
lease.try_obtain
end
|
#two_factor_enabled? ⇒ Boolean
770
771
772
|
# File 'app/models/user.rb', line 770
def two_factor_enabled?
two_factor_otp_enabled? || two_factor_webauthn_u2f_enabled?
end
|
#two_factor_otp_enabled? ⇒ Boolean
774
775
776
|
# File 'app/models/user.rb', line 774
def two_factor_otp_enabled?
otp_required_for_login?
end
|
#two_factor_u2f_enabled? ⇒ Boolean
778
779
780
781
782
783
784
|
# File 'app/models/user.rb', line 778
def two_factor_u2f_enabled?
if u2f_registrations.loaded?
u2f_registrations.any?
else
u2f_registrations.exists?
end
end
|
#two_factor_webauthn_enabled? ⇒ Boolean
790
791
792
793
794
|
# File 'app/models/user.rb', line 790
def two_factor_webauthn_enabled?
return false unless Feature.enabled?(:webauthn)
(webauthn_registrations.loaded? && webauthn_registrations.any?) || (!webauthn_registrations.loaded? && webauthn_registrations.exists?)
end
|
#two_factor_webauthn_u2f_enabled? ⇒ Boolean
786
787
788
|
# File 'app/models/user.rb', line 786
def two_factor_webauthn_u2f_enabled?
two_factor_u2f_enabled? || two_factor_webauthn_enabled?
end
|
#unique_email ⇒ Object
809
810
811
812
813
|
# File 'app/models/user.rb', line 809
def unique_email
if !emails.exists?(email: email) && Email.exists?(email: email)
errors.add(:email, _('has already been taken'))
end
end
|
#update_emails_with_primary_email(previous_confirmed_at, previous_email) ⇒ Object
Note: the use of the Emails services will cause `saves` on the user object, running through the callbacks again and can have side effects, such as the `previous_changes` hash and `_was` variables getting munged. By using an `after_commit` instead of `after_update`, we avoid the recursive callback scenario, though it then requires us to use the `previous_changes` hash rubocop: disable CodeReuse/ServiceClass
872
873
874
875
876
877
878
879
880
881
|
# File 'app/models/user.rb', line 872
def update_emails_with_primary_email(previous_confirmed_at, previous_email)
primary_email_record = emails.find_by(email: email)
Emails::DestroyService.new(self, user: self).execute(primary_email_record) if primary_email_record
Emails::CreateService.new(self, user: self, email: previous_email).execute(confirmed_at: previous_confirmed_at)
update_columns(confirmed_at: primary_email_record.confirmed_at) if primary_email_record&.confirmed_at
end
|
#update_invalid_gpg_signatures ⇒ Object
rubocop: enable CodeReuse/ServiceClass
884
885
886
|
# File 'app/models/user.rb', line 884
def update_invalid_gpg_signatures
gpg_keys.each(&:update_invalid_gpg_signatures)
end
|
#update_secondary_emails! ⇒ Object
1167
1168
1169
1170
1171
1172
|
# File 'app/models/user.rb', line 1167
def update_secondary_emails!
set_notification_email
set_public_email
set_commit_email
save if notification_email_changed? || public_email_changed? || commit_email_changed?
end
|
#update_todos_count_cache ⇒ Object
1532
1533
1534
1535
|
# File 'app/models/user.rb', line 1532
def update_todos_count_cache
todos_done_count(force: true)
todos_pending_count(force: true)
end
|
#update_tracked_fields!(request) ⇒ Object
Override Devise::Models::Trackable#update_tracked_fields! to limit database writes to at most once every hour rubocop: disable CodeReuse/ServiceClass
#update_two_factor_requirement ⇒ Object
1604
1605
1606
1607
1608
1609
1610
1611
|
# File 'app/models/user.rb', line 1604
def update_two_factor_requirement
periods = expanded_groups_requiring_two_factor_authentication.pluck(:two_factor_grace_period)
self.require_two_factor_authentication_from_group = periods.any?
self.two_factor_grace_period = periods.min || User.column_defaults['two_factor_grace_period']
save
end
|
#user_detail ⇒ Object
1695
1696
1697
|
# File 'app/models/user.rb', line 1695
def user_detail
super.presence || build_user_detail
end
|
#user_preference ⇒ Object
Avoid migrations only building user preference object when needed.
1691
1692
1693
|
# File 'app/models/user.rb', line 1691
def user_preference
super.presence || build_user_preference
end
|
#username_changed_hook ⇒ Object
1328
1329
1330
|
# File 'app/models/user.rb', line 1328
def username_changed_hook
system_hook_service.execute_hooks_for(self, :rename)
end
|
#verified_email?(check_email) ⇒ Boolean
1294
1295
1296
1297
1298
1299
1300
1301
1302
|
# File 'app/models/user.rb', line 1294
def verified_email?(check_email)
downcased = check_email.downcase
return true if persisted? &&
id == Gitlab::PrivateCommitEmail.user_id_for_email(downcased)
verified_emails.include?(check_email.downcase)
end
|
#verified_emails(include_private_email: true) ⇒ Object
1270
1271
1272
1273
1274
1275
1276
|
# File 'app/models/user.rb', line 1270
def verified_emails(include_private_email: true)
verified_emails = []
verified_emails << email if primary_email_verified?
verified_emails << private_commit_email if include_private_email
verified_emails.concat(emails.confirmed.pluck(:email))
verified_emails
end
|
#will_save_change_to_login? ⇒ Boolean
will_save_change_to_attribute? is used by Devise to check if it is necessary to clear any existing reset_password_tokens before updating an authentication_key and login in our case is a virtual attribute to allow login by username or email.
805
806
807
|
# File 'app/models/user.rb', line 805
def will_save_change_to_login?
will_save_change_to_username? || will_save_change_to_email?
end
|
#with_defaults ⇒ Object
1211
1212
1213
1214
1215
1216
1217
|
# File 'app/models/user.rb', line 1211
def with_defaults
User.defaults.each do |k, v|
public_send("#{k}=", v) end
self
end
|