diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-09-20 16:18:24 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-09-20 16:18:24 +0300 |
commit | 0653e08efd039a5905f3fa4f6e9cef9f5d2f799c (patch) | |
tree | 4dcc884cf6d81db44adae4aa99f8ec1233a41f55 /app/models/user.rb | |
parent | 744144d28e3e7fddc117924fef88de5d9674fe4c (diff) |
Add latest changes from gitlab-org/gitlab@14-3-stable-eev14.3.0-rc42
Diffstat (limited to 'app/models/user.rb')
-rw-r--r-- | app/models/user.rb | 181 |
1 files changed, 100 insertions, 81 deletions
diff --git a/app/models/user.rb b/app/models/user.rb index cb0f15c04cb..b5f0251f639 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -39,6 +39,12 @@ class User < ApplicationRecord MAX_USERNAME_LENGTH = 255 MIN_USERNAME_LENGTH = 2 + SECONDARY_EMAIL_ATTRIBUTES = [ + :commit_email, + :notification_email, + :public_email + ].freeze + add_authentication_token_field :incoming_email_token, token_generator: -> { SecureRandom.hex.to_i(16).to_s(36) } add_authentication_token_field :feed_token add_authentication_token_field :static_object_token @@ -181,7 +187,7 @@ class User < ApplicationRecord has_many :todos has_many :notification_settings has_many :award_emoji, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent - has_many :triggers, dependent: :destroy, class_name: 'Ci::Trigger', foreign_key: :owner_id # rubocop:disable Cop/ActiveRecordDependent + has_many :triggers, class_name: 'Ci::Trigger', foreign_key: :owner_id has_many :issue_assignees, inverse_of: :assignee has_many :merge_request_assignees, inverse_of: :assignee @@ -194,6 +200,7 @@ class User < ApplicationRecord has_many :custom_attributes, class_name: 'UserCustomAttribute' has_many :callouts, class_name: 'UserCallout' + has_many :group_callouts, class_name: 'Users::GroupCallout' has_many :term_agreements belongs_to :accepted_term, class_name: 'ApplicationSetting::Term' @@ -222,10 +229,9 @@ class User < ApplicationRecord validates :first_name, length: { maximum: 127 } validates :last_name, length: { maximum: 127 } validates :email, confirmation: true - validates :notification_email, presence: true - validates :notification_email, devise_email: true, if: ->(user) { user.notification_email != user.email } + validates :notification_email, devise_email: true, allow_blank: true, if: ->(user) { user.notification_email != user.email } validates :public_email, uniqueness: true, devise_email: true, allow_blank: true - validates :commit_email, devise_email: true, allow_nil: true, if: ->(user) { user.commit_email != user.email } + validates :commit_email, devise_email: true, allow_blank: true, if: ->(user) { user.commit_email != user.email && user.commit_email != Gitlab::PrivateCommitEmail::TOKEN } validates :projects_limit, presence: true, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: Gitlab::Database::MAX_INT_VALUE } @@ -247,12 +253,10 @@ class User < ApplicationRecord validates :color_scheme_id, allow_nil: true, inclusion: { in: Gitlab::ColorSchemes.valid_ids, message: _("%{placeholder} is not a valid color scheme") % { placeholder: '%{value}' } } + validates :website_url, allow_blank: true, url: true, if: :website_url_changed? + before_validation :sanitize_attrs - before_validation :set_public_email, if: :public_email_changed? - before_validation :set_commit_email, if: :commit_email_changed? before_save :default_private_profile_to_false - before_save :set_public_email, if: :public_email_changed? # in case validation is skipped - before_save :set_commit_email, if: :commit_email_changed? # in case validation is skipped before_save :ensure_incoming_email_token before_save :ensure_user_rights_and_limits, if: ->(user) { user.new_record? || user.external_changed? } before_save :skip_reconfirmation!, if: ->(user) { user.email_changed? && user.read_only_attribute?(:email) } @@ -302,14 +306,13 @@ class User < ApplicationRecord :gitpod_enabled, :gitpod_enabled=, :setup_for_company, :setup_for_company=, :render_whitespace_in_code, :render_whitespace_in_code=, - :experience_level, :experience_level=, :markdown_surround_selection, :markdown_surround_selection=, to: :user_preference delegate :path, to: :namespace, allow_nil: true, prefix: true delegate :job_title, :job_title=, to: :user_detail, allow_nil: true delegate :other_role, :other_role=, to: :user_detail, allow_nil: true - delegate :bio, :bio=, :bio_html, to: :user_detail, allow_nil: true + delegate :bio, :bio=, to: :user_detail, allow_nil: true delegate :webauthn_xid, :webauthn_xid=, to: :user_detail, allow_nil: true delegate :pronouns, :pronouns=, to: :user_detail, allow_nil: true delegate :pronunciation, :pronunciation=, to: :user_detail, allow_nil: true @@ -347,6 +350,10 @@ class User < ApplicationRecord transition active: :banned end + event :unban do + transition banned: :active + end + event :deactivate do # Any additional changes to this event should be also # reflected in app/workers/users/deactivate_dormant_users_worker.rb @@ -374,7 +381,9 @@ class User < ApplicationRecord end after_transition any => :deactivated do |user| - NotificationService.new.user_deactivated(user.name, user.notification_email) + next unless Gitlab::CurrentSettings.user_deactivation_emails_enabled + + NotificationService.new.user_deactivated(user.name, user.notification_email_or_default) end # rubocop: enable CodeReuse/ServiceClass @@ -922,51 +931,18 @@ class User < ApplicationRecord end end - def notification_email_verified - return if read_attribute(:notification_email).blank? || temp_oauth_email? - - errors.add(:notification_email, _("must be an email you have verified")) unless verified_emails.include?(notification_email) - end - - def public_email_verified - return if public_email.blank? - - errors.add(:public_email, _("must be an email you have verified")) unless verified_emails.include?(public_email) - end - - def commit_email_verified - return if read_attribute(:commit_email).blank? - - errors.add(:commit_email, _("must be an email you have verified")) unless verified_emails.include?(commit_email) - end - - # 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. - - def commit_email - return self.email unless has_attribute?(:commit_email) - - if super == Gitlab::PrivateCommitEmail::TOKEN + def commit_email_or_default + if self.commit_email == Gitlab::PrivateCommitEmail::TOKEN return private_commit_email end # The commit email is the same as the primary email if undefined - super.presence || self.email + self.commit_email.presence || self.email end - def commit_email=(email) - super if has_attribute?(:commit_email) - end - - def commit_email_changed? - has_attribute?(:commit_email) && super - end - - def notification_email + def notification_email_or_default # The notification email is the same as the primary email if undefined - super.presence || self.email + self.notification_email.presence || self.email end def private_commit_email @@ -1009,7 +985,11 @@ class User < ApplicationRecord # Returns the groups a user is a member of, either directly or through a parent group def membership_groups - Gitlab::ObjectHierarchy.new(groups).base_and_descendants + if Feature.enabled?(:linear_user_membership_groups, self, default_enabled: :yaml) + groups.self_and_descendants + else + Gitlab::ObjectHierarchy.new(groups).base_and_descendants + end end # Returns a relation of groups the user has access to, including their parent @@ -1292,29 +1272,15 @@ class User < ApplicationRecord self.name = self.name.gsub(%r{</?[^>]*>}, '') end - def set_notification_email - if notification_email.blank? || all_emails.exclude?(notification_email) - self.notification_email = email - end - end - - def set_public_email - if public_email.blank? || all_emails.exclude?(public_email) - self.public_email = '' - end - end - - def set_commit_email - if commit_email.blank? || verified_emails.exclude?(commit_email) - self.commit_email = nil + def unset_secondary_emails_matching_deleted_email!(deleted_email) + secondary_email_attribute_changed = false + SECONDARY_EMAIL_ATTRIBUTES.each do |attribute| + if read_attribute(attribute) == deleted_email + self.write_attribute(attribute, nil) + secondary_email_attribute_changed = true + end end - end - - def update_secondary_emails! - set_notification_email - set_public_email - set_commit_email - save if notification_email_changed? || public_email_changed? || commit_email_changed? + save if secondary_email_attribute_changed end def admin_unsubscribe! @@ -1569,7 +1535,11 @@ class User < ApplicationRecord end 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 + owned_and_maintainer_group_hierarchy = if Feature.enabled?(:linear_user_manageable_groups, self, default_enabled: :yaml) + owned_or_maintainers_groups.self_and_descendants + else + Gitlab::ObjectHierarchy.new(owned_or_maintainers_groups).base_and_descendants + end if include_groups_with_developer_maintainer_access union_sql = ::Gitlab::SQL::Union.new( @@ -1628,6 +1598,8 @@ class User < ApplicationRecord true end + # TODO Please check all callers and remove allow_cross_joins_across_databases, + # when https://gitlab.com/gitlab-org/gitlab/-/issues/336436 is done. def ci_owned_runners @ci_owned_runners ||= begin project_runners = Ci::RunnerProject @@ -1644,9 +1616,15 @@ class User < ApplicationRecord end end + def owns_runner?(runner) + ::Gitlab::Database.allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/336436') do + ci_owned_runners.exists?(runner.id) + end + end + def notification_email_for(notification_group) # Return group-specific email address if present, otherwise return global notification email address - notification_group&.notification_email_for(self) || notification_email + notification_group&.notification_email_for(self) || notification_email_or_default end def notification_settings_for(source, inherit: false) @@ -1935,10 +1913,14 @@ class User < ApplicationRecord def dismissed_callout?(feature_name:, ignore_dismissal_earlier_than: nil) callout = callouts_by_feature_name[feature_name] - return false unless callout - return callout.dismissed_after?(ignore_dismissal_earlier_than) if ignore_dismissal_earlier_than + callout_dismissed?(callout, ignore_dismissal_earlier_than) + end - true + def dismissed_callout_for_group?(feature_name:, group:, ignore_dismissal_earlier_than: nil) + source_feature_name = "#{feature_name}_#{group.id}" + callout = group_callouts_by_feature_name[source_feature_name] + + callout_dismissed?(callout, ignore_dismissal_earlier_than) end # Load the current highest access by looking directly at the user's memberships @@ -1962,6 +1944,11 @@ class User < ApplicationRecord callouts.find_or_initialize_by(feature_name: ::UserCallout.feature_names[feature_name]) end + def find_or_initialize_group_callout(feature_name, group_id) + group_callouts + .find_or_initialize_by(feature_name: ::Users::GroupCallout.feature_names[feature_name], group_id: group_id) + end + def can_trigger_notifications? confirmed? && !blocked? && !ghost? end @@ -2015,10 +2002,39 @@ class User < ApplicationRecord private + def notification_email_verified + return if notification_email.blank? || temp_oauth_email? + + errors.add(:notification_email, _("must be an email you have verified")) unless verified_emails.include?(notification_email_or_default) + end + + def public_email_verified + return if public_email.blank? + + errors.add(:public_email, _("must be an email you have verified")) unless verified_emails.include?(public_email) + end + + def commit_email_verified + return if commit_email.blank? + + errors.add(:commit_email, _("must be an email you have verified")) unless verified_emails.include?(commit_email_or_default) + end + + def callout_dismissed?(callout, ignore_dismissal_earlier_than) + return false unless callout + return callout.dismissed_after?(ignore_dismissal_earlier_than) if ignore_dismissal_earlier_than + + true + end + def callouts_by_feature_name @callouts_by_feature_name ||= callouts.index_by(&:feature_name) end + def group_callouts_by_feature_name + @group_callouts_by_feature_name ||= group_callouts.index_by(&:source_feature_name) + end + def authorized_groups_without_shared_membership Group.from_union([ groups.select(Namespace.arel_table[Arel.star]), @@ -2080,7 +2096,7 @@ class User < ApplicationRecord def check_username_format return if username.blank? || Mime::EXTENSION_LOOKUP.keys.none? { |type| username.end_with?(".#{type}") } - errors.add(:username, _('ending with MIME type format is not allowed.')) + errors.add(:username, _('ending with a file extension is not allowed.')) end def groups_with_developer_maintainer_project_access @@ -2090,9 +2106,12 @@ class User < ApplicationRecord project_creation_levels << nil end - developer_groups_hierarchy = ::Gitlab::ObjectHierarchy.new(developer_groups).base_and_descendants - ::Group.where(id: developer_groups_hierarchy.select(:id), - project_creation_level: project_creation_levels) + if Feature.enabled?(:linear_user_groups_with_developer_maintainer_project_access, self, default_enabled: :yaml) + developer_groups.self_and_descendants.where(project_creation_level: project_creation_levels) + else + developer_groups_hierarchy = ::Gitlab::ObjectHierarchy.new(developer_groups).base_and_descendants + ::Group.where(id: developer_groups_hierarchy.select(:id), project_creation_level: project_creation_levels) + end end def no_recent_activity? |