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,
#check_timeout!

If timeoutMS is set for the operation context, checks whether there is enough time left to send the corresponding message to the server (remaining timeout is bigger than minimum round trip time for the server).

#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 189

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 288

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 217

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_calculator.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_calculator.average_round_trip_time,
    @server.round_trip_time_calculator.minimum_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, minimum_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 225

def post_handshake(response, average_rtt, minimum_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 259

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