gitlab-ce/app/models/import/source_user.rb

201 lines
6.6 KiB
Ruby

# frozen_string_literal: true
module Import
class SourceUser < ApplicationRecord
include Gitlab::SQL::Pattern
include EachBatch
self.table_name = 'import_source_users'
SORT_ORDERS = {
source_name_asc: { order_by: 'source_name', sort: 'asc' },
source_name_desc: { order_by: 'source_name', sort: 'desc' },
status_asc: { order_by: 'status', sort: 'asc' },
status_desc: { order_by: 'status', sort: 'desc' },
id_asc: { order_by: 'id', sort: 'asc' },
id_desc: { order_by: 'id', sort: 'desc' }
}.freeze
belongs_to :placeholder_user, class_name: 'User', optional: true
belongs_to :reassign_to_user, class_name: 'User', optional: true
belongs_to :reassigned_by_user, class_name: 'User', optional: true
belongs_to :namespace
validates :namespace_id, :import_type, :source_hostname, :source_user_identifier, :status, presence: true
validates :source_user_identifier, uniqueness: { scope: [:namespace_id, :source_hostname, :import_type] }
validates :placeholder_user_id, presence: true, unless: :completed?
validates :reassignment_token, absence: true, unless: :awaiting_approval?
validates :reassignment_token, length: { is: 32 }, if: :awaiting_approval?
validates :reassign_to_user_id, presence: true, if: -> {
awaiting_approval? || reassignment_in_progress? || completed?
}
validates :reassign_to_user_id, absence: true, if: -> { pending_reassignment? || keep_as_placeholder? }
validates :reassign_to_user_id, uniqueness: {
scope: [:namespace_id, :source_hostname, :import_type],
allow_nil: true,
message: ->(_object, _data) {
s_('Import|already assigned to another placeholder')
}
}
validate :validate_source_hostname, if: :source_hostname_changed?
scope :for_namespace, ->(namespace_id) { where(namespace_id: namespace_id) }
scope :by_source_hostname, ->(source_hostname) { where(source_hostname: source_hostname) }
scope :by_import_type, ->(import_type) { where(import_type: import_type) }
scope :for_placeholder_user, ->(user) { where(placeholder_user: user) }
scope :by_statuses, ->(statuses) { where(status: statuses) }
scope :awaiting_reassignment, -> { where(status: [0, 1, 2, 3, 4]) }
scope :reassigned, -> { where(status: [5, 6]) }
STATUSES = {
pending_reassignment: 0,
awaiting_approval: 1,
reassignment_in_progress: 2,
rejected: 3,
failed: 4,
completed: 5,
keep_as_placeholder: 6
}.freeze
ACCEPTED_STATUSES = %i[reassignment_in_progress completed failed].freeze
REASSIGNABLE_STATUSES = %i[pending_reassignment rejected].freeze
CANCELABLE_STATUSES = %i[awaiting_approval rejected].freeze
state_machine :status, initial: :pending_reassignment do
STATUSES.each do |status_name, value|
state status_name, value: value
end
before_transition awaiting_approval: any do |source_user|
source_user.reassignment_token = nil
end
before_transition any => :awaiting_approval do |source_user|
source_user.reassignment_token = SecureRandom.hex
end
event :reassign do
transition REASSIGNABLE_STATUSES => :awaiting_approval
end
event :reassign_without_confirmation do
transition REASSIGNABLE_STATUSES => :reassignment_in_progress, if: :bypass_placeholder_confirmation_allowed?
end
event :cancel_reassignment do
transition CANCELABLE_STATUSES => :pending_reassignment
end
event :keep_as_placeholder do
transition REASSIGNABLE_STATUSES => :keep_as_placeholder
end
event :accept do
transition awaiting_approval: :reassignment_in_progress
end
event :reject do
transition awaiting_approval: :rejected
end
event :complete do
transition reassignment_in_progress: :completed
end
event :fail_reassignment do
transition reassignment_in_progress: :failed
end
end
class << self
def find_source_user(source_user_identifier:, namespace:, source_hostname:, import_type:)
return unless namespace
find_by(
source_user_identifier: source_user_identifier,
namespace_id: namespace.id,
source_hostname: source_hostname,
import_type: import_type
)
end
def search(query)
return none unless query.is_a?(String)
fuzzy_search(query, [:source_name, :source_username])
end
def sort_by_attribute(method)
sort_order = SORT_ORDERS[method&.to_sym] || SORT_ORDERS[:source_name_asc]
reorder(sort_order[:order_by] => sort_order[:sort])
end
def namespace_placeholder_user_count(namespace, limit:)
for_namespace(namespace).distinct.limit(limit).count(:placeholder_user_id) -
(namespace.namespace_import_user.present? ? 1 : 0)
end
def source_users_with_missing_information(namespace:, source_hostname:, import_type:)
for_namespace(namespace)
.by_source_hostname(source_hostname)
.by_import_type(import_type)
.and(
where(source_name: nil).or(where(source_username: nil))
)
end
end
def mapped_user
accepted_status? ? reassign_to_user : placeholder_user
end
def mapped_user_id
accepted_status? ? reassign_to_user_id : placeholder_user_id
end
def accepted_status?
STATUSES.slice(*ACCEPTED_STATUSES).value?(status)
end
def reassignable_status?
STATUSES.slice(*REASSIGNABLE_STATUSES).value?(status)
end
def cancelable_status?
STATUSES.slice(*CANCELABLE_STATUSES).value?(status)
end
def validate_source_hostname
return unless source_hostname
uri = Gitlab::Utils.parse_url(source_hostname)
return if uri && uri.scheme && uri.host && uri.path.blank?
errors.add(:source_hostname, :invalid, message: 'must contain scheme and host, and not path')
end
private
def bypass_placeholder_confirmation_allowed?
admin_bypass_placeholder_confirmation_allowed? || enterprise_bypass_placeholder_confirmation_allowed?
end
def admin_bypass_placeholder_confirmation_allowed?
Import::UserMapping::AdminBypassAuthorizer.new(reassigned_by_user).allowed?
end
# rubocop:disable Gitlab/NoCodeCoverageComment -- method is tested in EE
# :nocov:
# Overridden in EE
def enterprise_bypass_placeholder_confirmation_allowed?
false
end
# :nocov:
# rubocop:enable Gitlab/NoCodeCoverageComment
end
end
Import::SourceUser.prepend_mod