diff options
Diffstat (limited to 'app/models/user.rb')
-rw-r--r-- | app/models/user.rb | 101 |
1 files changed, 65 insertions, 36 deletions
diff --git a/app/models/user.rb b/app/models/user.rb index ee51c35d576..2eb5c63a4cc 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -105,6 +105,7 @@ class User < ApplicationRecord has_many :groups, through: :group_members has_many :owned_groups, -> { where(members: { access_level: Gitlab::Access::OWNER }) }, through: :group_members, source: :group has_many :maintainers_groups, -> { where(members: { access_level: Gitlab::Access::MAINTAINER }) }, through: :group_members, source: :group + has_many :developer_groups, -> { where(members: { access_level: ::Gitlab::Access::DEVELOPER }) }, through: :group_members, source: :group has_many :owned_or_maintainers_groups, -> { where(members: { access_level: [Gitlab::Access::MAINTAINER, Gitlab::Access::OWNER] }) }, through: :group_members, @@ -159,12 +160,12 @@ class User < ApplicationRecord # Validations # # Note: devise :validatable above adds validations for :email and :password - validates :name, presence: true + validates :name, presence: true, length: { maximum: 128 } validates :email, confirmation: true validates :notification_email, presence: true - validates :notification_email, email: true, if: ->(user) { user.notification_email != user.email } - validates :public_email, presence: true, uniqueness: true, email: true, allow_blank: true - validates :commit_email, email: true, allow_nil: true, if: ->(user) { user.commit_email != user.email } + validates :notification_email, devise_email: true, if: ->(user) { user.notification_email != user.email } + validates :public_email, presence: true, 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 :bio, length: { maximum: 255 }, allow_blank: true validates :projects_limit, presence: true, @@ -193,7 +194,7 @@ class User < ApplicationRecord before_validation :ensure_namespace_correct before_save :ensure_namespace_correct # in case validation is skipped after_validation :set_username_errors - after_update :username_changed_hook, if: :username_changed? + after_update :username_changed_hook, if: :saved_change_to_username? after_destroy :post_destroy_hook after_destroy :remove_key_cache after_commit(on: :update) do @@ -229,6 +230,9 @@ class User < ApplicationRecord delegate :notes_filter_for, to: :user_preference delegate :set_notes_filter, to: :user_preference delegate :first_day_of_week, :first_day_of_week=, to: :user_preference + delegate :timezone, :timezone=, to: :user_preference + delegate :time_display_relative, :time_display_relative=, to: :user_preference + delegate :time_format_in_24h, :time_format_in_24h=, to: :user_preference accepts_nested_attributes_for :user_preference, update_only: true @@ -276,6 +280,7 @@ class User < ApplicationRecord scope :by_username, -> (usernames) { iwhere(username: Array(usernames).map(&:to_s)) } scope :for_todos, -> (todos) { where(id: todos.select(:user_id)) } scope :with_emails, -> { preload(:emails) } + scope :with_dashboard, -> (dashboard) { where(dashboard: dashboard) } # Limits the users to those that have TODOs, optionally in the given state. # @@ -432,7 +437,7 @@ class User < ApplicationRecord 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(order % { query: ActiveRecord::Base.connection.quote(query) }, :name) + ).reorder(order % { query: ApplicationRecord.connection.quote(query) }, :name) end # Limits the result set to users _not_ in the given query/list of IDs. @@ -470,7 +475,7 @@ class User < ApplicationRecord end def by_login(login) - return nil unless login + return unless login if login.include?('@'.freeze) unscoped.iwhere(email: login).take @@ -515,7 +520,7 @@ class User < ApplicationRecord def ghost email = 'ghost%s@example.com' unique_internal(where(ghost: true), '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.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 @@ -535,20 +540,16 @@ class User < ApplicationRecord username end - def self.internal_attributes - [:ghost] - end - def internal? - self.class.internal_attributes.any? { |a| self[a] } + ghost? end def self.internal - where(Hash[internal_attributes.zip([true] * internal_attributes.size)]) + where(ghost: true) end def self.non_internal - where(internal_attributes.map { |attr| "#{attr} IS NOT TRUE" }.join(" AND ")) + where('ghost IS NOT TRUE') end # @@ -624,32 +625,32 @@ class User < ApplicationRecord 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.') + errors.add(:username, _('cannot be changed if a personal project has container registry tags.')) end end def unique_email if !emails.exists?(email: email) && Email.exists?(email: email) - errors.add(:email, 'has already been taken') + errors.add(:email, _('has already been taken')) end end def owns_notification_email return if temp_oauth_email? - errors.add(:notification_email, "is not an email you own") unless all_emails.include?(notification_email) + errors.add(:notification_email, _("is not an email you own")) unless all_emails.include?(notification_email) end def owns_public_email return if public_email.blank? - errors.add(:public_email, "is not an email you own") unless all_emails.include?(public_email) + errors.add(:public_email, _("is not an email you own")) unless all_emails.include?(public_email) end 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) + errors.add(:commit_email, _("is not an email you own")) unless verified_emails.include?(commit_email) end # Define commit_email-related attribute methods explicitly instead of relying @@ -759,11 +760,15 @@ class User < ApplicationRecord # 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)` - def authorizations_for_projects(min_access_level: nil) - authorizations = project_authorizations.select(1).where('project_authorizations.project_id = projects.id') + 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? @@ -882,7 +887,12 @@ class User < ApplicationRecord # rubocop: enable CodeReuse/ServiceClass def several_namespaces? - owned_groups.any? || maintainers_groups.any? + 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 def namespace_id @@ -917,6 +927,10 @@ class User < ApplicationRecord DeployKey.unscoped.in_projects(authorized_projects.pluck(:id)).distinct(:id) end + def highest_role + members.maximum(:access_level) || Gitlab::Access::NO_ACCESS + end + def accessible_deploy_keys @accessible_deploy_keys ||= begin key_ids = project_deploy_keys.pluck(:id) @@ -1164,12 +1178,24 @@ class User < ApplicationRecord @manageable_namespaces ||= [namespace] + manageable_groups end - def manageable_groups - Gitlab::ObjectHierarchy.new(owned_or_maintainers_groups).base_and_descendants + 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 - def manageable_groups_with_routes - manageable_groups.eager_load(:route).order('routes.path') + 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 def namespaces @@ -1471,15 +1497,6 @@ class User < ApplicationRecord devise_mailer.__send__(notification, self, *args).deliver_later # rubocop:disable GitlabSecurity/PublicSend end - # This works around a bug in Devise 4.2.0 that erroneously causes a user to - # be considered active in MySQL specs due to a sub-second comparison - # issue. For more details, see: https://gitlab.com/gitlab-org/gitlab-ee/issues/2362#note_29004709 - def confirmation_period_valid? - return false if self.class.allow_unconfirmed_access_for == 0.days - - super - end - def ensure_user_rights_and_limits if external? self.can_create_group = false @@ -1568,4 +1585,16 @@ class User < ApplicationRecord ensure Gitlab::ExclusiveLease.cancel(lease_key, uuid) end + + def groups_with_developer_maintainer_project_access + project_creation_levels = [::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS] + + if ::Gitlab::CurrentSettings.default_project_creation == ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS + 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) + end end |