class Mongo::Server::PendingConnection

This class encapsulates connections during handshake and authentication.

@api private

Attributes

id[R]

@return [ Integer ] The ID for the connection. This is the same ID

as that of the regular Connection object for which this
PendingConnection instance was created.

Public Class Methods

new(socket, server, monitoring, options = {}) click to toggle source
# File lib/mongo/server/pending_connection.rb, line 27
def initialize(socket, server, monitoring, options = {})
  @socket = socket
  @options = options
  @server = server
  @monitoring = monitoring
  @id = options[:id]
end

Public Instance Methods

handshake_and_authenticate!() click to toggle source
# File lib/mongo/server/pending_connection.rb, line 40
def handshake_and_authenticate!
  speculative_auth_doc = nil
  if options[:user] || options[:auth_mech]
    # To create an Auth instance, we need to specify the mechanism,
    # but at this point we don't know the mechanism that ultimately
    # will be used (since this depends on the data returned by
    # the handshake, specifically server version).
    # However, we know that only 4.4+ servers support speculative
    # authentication, and those servers also generally support
    # SCRAM-SHA-256. We expect that user accounts created for 4.4+
    # servers would generally allow SCRAM-SHA-256 authentication;
    # user accounts migrated from pre-4.4 servers may only allow
    # SCRAM-SHA-1. The use of SCRAM-SHA-256 by default is thus
    # sensible, and it is also mandated by the speculative auth spec.
    # If no mechanism was specified and we are talking to a 3.0+
    # server, we'll send speculative auth document, the server will
    # ignore it and we'll perform authentication using explicit
    # command after having defaulted the mechanism later to CR.
    # If no mechanism was specified and we are talking to a 4.4+
    # server and the user account doesn't allow SCRAM-SHA-256, we will
    # authenticate in a separate command with SCRAM-SHA-1 after
    # going through SCRAM mechanism negotiation.
    default_options = Options::Redacted.new(:auth_mech => :scram256)
    speculative_auth_user = Auth::User.new(default_options.merge(options))
    speculative_auth = Auth.get(speculative_auth_user, self)
    speculative_auth_doc = speculative_auth.conversation.speculative_auth_document
  end

  result = handshake!(speculative_auth_doc: speculative_auth_doc)

  if description.unknown?
    raise Error::InternalDriverError, "Connection description cannot be unknown after successful handshake: #{description.inspect}"
  end

  begin
    if speculative_auth_doc && (speculative_auth_result = result['speculativeAuthenticate'])
      unless description.features.scram_sha_1_enabled?
        raise Error::InvalidServerAuthResponse, "Speculative auth succeeded on a pre-3.0 server"
      end
      case speculative_auth_user.mechanism
      when :mongodb_x509
        # Done
      # We default auth mechanism to scram256, but if user specified
      # scram explicitly we may be able to authenticate speculatively
      # with scram.
      when :scram, :scram256
        authenticate!(
          speculative_auth_client_nonce: speculative_auth.conversation.client_nonce,
          speculative_auth_mech: speculative_auth_user.mechanism,
          speculative_auth_result: speculative_auth_result,
        )
      else
        raise Error::InternalDriverError, "Speculative auth unexpectedly succeeded for mechanism #{speculative_auth_user.mechanism.inspect}"
      end
    elsif !description.arbiter?
      authenticate!
    end
  rescue Mongo::Error, Mongo::Error::AuthError => exc
    exc.service_id = service_id
    raise
  end

  if description.unknown?
    raise Error::InternalDriverError, "Connection description cannot be unknown after successful authentication: #{description.inspect}"
  end

  if server.load_balancer? && !description.mongos?
    raise Error::BadLoadBalancerTarget, "Load-balanced operation requires being connected a mongos, but the server at #{address.seed} reported itself as #{description.server_type.to_s.gsub('_', ' ')}"
  end
end

Private Instance Methods

authenticate!( speculative_auth_client_nonce: nil, speculative_auth_mech: nil, speculative_auth_result: nil ) click to toggle source

@param [ String | nil ] speculative_auth_client_nonce The client

nonce used in speculative auth on this connection that
produced the specified speculative auth result.

@param [ Symbol | nil ] speculative_auth_mech Auth mechanism used

for speculative auth, if speculative auth succeeded. If speculative
auth was not performed or it failed, this must be nil.

@param [ BSON::Document | nil ] speculative_auth_result The

value of speculativeAuthenticate field of hello response of
the handshake on this connection.
# File lib/mongo/server/pending_connection.rb, line 172
def authenticate!(
  speculative_auth_client_nonce: nil,
  speculative_auth_mech: nil,
  speculative_auth_result: nil
)
  if options[:user] || options[:auth_mech]
    @server.handle_auth_failure! do
      begin
        auth = Auth.get(
          resolved_user(speculative_auth_mech: speculative_auth_mech),
          self,
          speculative_auth_client_nonce: speculative_auth_client_nonce,
          speculative_auth_result: speculative_auth_result,
        )
        auth.login
      rescue => exc
        msg = "Failed to authenticate to #{address}"
        Utils.warn_bg_exception(msg, exc,
          logger: options[:logger],
          log_prefix: options[:log_prefix],
          bg_error_backtrace: options[:bg_error_backtrace],
        )
        raise
      end
    end
  end
end
default_mechanism() click to toggle source
# File lib/mongo/server/pending_connection.rb, line 271
def default_mechanism
  if description.nil?
    raise Mongo::Error, 'Trying to query default mechanism when handshake has not completed'
  end

  if description.features.scram_sha_1_enabled?
    if @sasl_supported_mechanisms&.include?('SCRAM-SHA-256')
      :scram256
    else
      :scram
    end
  else
    :mongodb_cr
  end
end
ensure_connected() { |socket| ... } click to toggle source
# File lib/mongo/server/pending_connection.rb, line 200
def ensure_connected
  yield @socket
end
handshake!(speculative_auth_doc: nil) click to toggle source

@param [ BSON::Document | nil ] speculative_auth_doc The document to

provide in speculativeAuthenticate field of handshake command.

@return [ BSON::Document ] The document of the handshake response for

this particular connection.
# File lib/mongo/server/pending_connection.rb, line 118
def handshake!(speculative_auth_doc: nil)
  unless socket
    raise Error::InternalDriverError, "Cannot handshake because there is no usable socket (for #{address})"
  end

  hello_command = handshake_command(
    handshake_document(
      app_metadata,
      speculative_auth_doc: speculative_auth_doc,
      load_balancer: server.load_balancer?,
      server_api: options[:server_api]
    )
  )
  doc = nil
  @server.handle_handshake_failure! do
    begin
      response = @server.round_trip_time_averager.measure do
        add_server_diagnostics do
          socket.write(hello_command.serialize.to_s)
          Protocol::Message.deserialize(socket, Protocol::Message::MAX_MESSAGE_SIZE)
        end
      end
      result = Operation::Result.new([response])
      result.validate!
      doc = result.documents.first
    rescue => exc
      msg = "Failed to handshake with #{address}"
      Utils.warn_bg_exception(msg, exc,
        logger: options[:logger],
        log_prefix: options[:log_prefix],
        bg_error_backtrace: options[:bg_error_backtrace],
      )
      raise
    end
  end

  if @server.force_load_balancer?
    doc['serviceId'] ||= "fake:#{rand(2**32-1)+1}"
  end

  post_handshake(doc, @server.round_trip_time_averager.average_round_trip_time)

  doc
end
post_handshake(response, average_rtt) click to toggle source

This is a separate method to keep the nesting level down.

@return [ Server::Description ] The server description calculated from

the handshake response for this particular connection.
# File lib/mongo/server/pending_connection.rb, line 208
def post_handshake(response, average_rtt)
  if response["ok"] == 1
    # Auth mechanism is entirely dependent on the contents of
    # hello response *for this connection*.
    # Hello received by the monitoring connection should advertise
    # the same wire protocol, but if it doesn't, we use whatever
    # the monitoring connection advertised for filling out the
    # server description and whatever the non-monitoring connection
    # (that's this one) advertised for performing auth on that
    # connection.
    @sasl_supported_mechanisms = response['saslSupportedMechs']
    set_compressor!(response)
  else
    @sasl_supported_mechanisms = nil
  end

  @description = Description.new(
    address, response,
    average_round_trip_time: average_rtt,
    load_balancer: server.load_balancer?,
    force_load_balancer: options[:connect] == :load_balanced,
  ).tap do |new_description|
    @server.cluster.run_sdam_flow(@server.description, new_description)
  end
end
resolved_user(speculative_auth_mech: nil) click to toggle source

The user as going to be used for authentication. This user has the auth mechanism set and, if necessary, auth source.

@param [ Symbol | nil ] speculative_auth_mech Auth mechanism used

for speculative auth, if speculative auth succeeded. If speculative
auth was not performed or it failed, this must be nil.

@return [ Auth::User ] The resolved user.

# File lib/mongo/server/pending_connection.rb, line 242
def resolved_user(speculative_auth_mech: nil)
  @resolved_user ||= begin
    unless options[:user] || options[:auth_mech]
      raise Mongo::Error, 'No authentication information specified in the client'
    end

    user_options = Options::Redacted.new(
      # When speculative auth is performed, we always use SCRAM-SHA-256.
      # At the same time we perform SCRAM mechanism negotiation in the
      # hello request.
      # If the credentials we are trying to authenticate with do not
      # map to an existing user, SCRAM mechanism negotiation will not
      # return anything which would cause the driver to use
      # SCRAM-SHA-1. However, on 4.4+ servers speculative auth would
      # succeed (technically just the first round-trip, not the entire
      # authentication flow) and we would be continuing it here;
      # in this case, we must use SCRAM-SHA-256 as the mechanism since
      # that is what the conversation was started with, even though
      # SCRAM mechanism negotiation did not return SCRAM-SHA-256 as a
      # valid mechanism to use for these credentials.
      :auth_mech => speculative_auth_mech || default_mechanism,
    ).merge(options)
    if user_options[:auth_mech] == :mongodb_x509
      user_options[:auth_source] = '$external'
    end
    Auth::User.new(user_options)
  end
end