123456789_123456789_123456789_123456789_123456789_

Class: Mongo::Server::PendingConnection Private

Do not use. This class is for internal use only.
Relationships & Source Files
Super Chains via Extension / Inclusion / Inheritance
Class Chain:
self, Forwardable, ConnectionBase, Forwardable, ConnectionCommon
Instance Chain:
Inherits: Mongo::Server::ConnectionBase
Defined in: lib/mongo/server/pending_connection.rb

Overview

This class encapsulates connections during handshake and authentication.

Since:

  • 2.0.0

Constant Summary

ConnectionCommon - Inherited

HELLO_DOC, LEGACY_HELLO_DOC

::Mongo::Loggable - Included

PREFIX

ConnectionBase - Inherited

DEFAULT_MAX_BSON_OBJECT_SIZE, MAX_BSON_COMMAND_OVERHEAD, REDUCED_MAX_BSON_SIZE

Class Method Summary

Instance Attribute Summary

ConnectionBase - Inherited

#description

Returns the server description for this connection, derived from the hello response for the handshake performed on this connection.

#options, #server

::Mongo::Monitoring::Publishable - Included

ConnectionCommon - Inherited

#compressor

The compressor negotiated during the handshake for this connection, if any.

#connected?

Determine if the connection is currently connected.

#pid, #socket

Instance Method Summary

ConnectionBase - Inherited

#app_metadata,
#dispatch

Dispatch a single message to the connection.

#generation

Connection pool generation from which this connection was created.

#service_id, #deliver, #serialize

::Mongo::Monitoring::Publishable - Included

::Mongo::Loggable - Included

#log_debug

Convenience method to log debug messages with the standard prefix.

#log_error

Convenience method to log error messages with the standard prefix.

#log_fatal

Convenience method to log fatal messages with the standard prefix.

#log_info

Convenience method to log info messages with the standard prefix.

#log_warn

Convenience method to log warn messages with the standard prefix.

#logger

Get the logger instance.

#_mongo_log_prefix, #format_message

ConnectionCommon - Inherited

#handshake_command

Build a command that should be used for connection handshake.

#handshake_document

Build a document that should be used for connection handshake.

#add_server_diagnostics

Yields to the block and, if the block raises an exception, adds a note to the exception with the address of the specified server.

#ensure_connected, #set_compressor!, #ssl_options

Instance Attribute Details

#idInteger (readonly)

Returns:

  • (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.

Since:

  • 2.0.0

[ GitHub ]

  
# File 'lib/mongo/server/pending_connection.rb', line 38

attr_reader :id

Instance Method Details

#authenticate!(speculative_auth_client_nonce: nil, speculative_auth_mech: nil, speculative_auth_result: nil) (private)

Parameters:

  • speculative_auth_client_nonce (String | nil)

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

  • speculative_auth_mech (Symbol | nil)

    ::Mongo::Auth mechanism used for speculative auth, if speculative auth succeeded. If speculative auth was not performed or it failed, this must be nil.

  • speculative_auth_result (BSON::Document | nil)

    The value of speculativeAuthenticate field of hello response of the handshake on this connection.

Since:

  • 2.0.0

[ GitHub ]

  
# File 'lib/mongo/server/pending_connection.rb', line 185

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.
      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 (private)

Since:

  • 2.0.0

[ GitHub ]

  
# File 'lib/mongo/server/pending_connection.rb', line 284

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| ... } (private)

Yields:

  • (@socket)

Since:

  • 2.0.0

[ GitHub ]

  
# File 'lib/mongo/server/pending_connection.rb', line 213

def ensure_connected
  yield @socket
end

#get_handshake_response(hello_command) ⇒ Mongo::Protocol::Reply (private)

Sends the hello command to the server, then receive and deserialize the response.

This method is extracted to be mocked in the tests.

Parameters:

  • Command (Protocol::Message)

    that should be sent to a server for handshake purposes.

Returns:

Since:

  • 2.0.0

[ GitHub ]

  
# File 'lib/mongo/server/pending_connection.rb', line 122

def get_handshake_response(hello_command)
  @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
end

#handshake!(speculative_auth_doc: nil) ⇒ BSON::Document (private)

Parameters:

  • speculative_auth_doc (BSON::Document | nil)

    The document to provide in speculativeAuthenticate field of handshake command.

Returns:

  • (BSON::Document)

    The document of the handshake response for this particular connection.

Since:

  • 2.0.0

[ GitHub ]

  
# File 'lib/mongo/server/pending_connection.rb', line 136

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(
      ,
      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 = get_handshake_response(hello_command)
      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

#handshake_and_authenticate!

Since:

  • 2.0.0

[ GitHub ]

  
# 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

#post_handshake(response, average_rtt) ⇒ Server::Description (private)

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

Returns:

  • (Server::Description)

    The server description calculated from the handshake response for this particular connection.

Since:

  • 2.0.0

[ GitHub ]

  
# File 'lib/mongo/server/pending_connection.rb', line 221

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) ⇒ Auth::User (private)

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

Parameters:

  • speculative_auth_mech (Symbol | nil)

    ::Mongo::Auth mechanism used for speculative auth, if speculative auth succeeded. If speculative auth was not performed or it failed, this must be nil.

Returns:

Since:

  • 2.0.0

[ GitHub ]

  
# File 'lib/mongo/server/pending_connection.rb', line 255

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