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 24
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 37
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 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 NotImplementedError, "Speculative auth unexpectedly succeeded for mechanism #{speculative_auth_user.mechanism.inspect}"
    end
  elsif !description.arbiter?
    authenticate!
  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 ismaster response of
the handshake on this connection.
# File lib/mongo/server/pending_connection.rb, line 144
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 238
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 172
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 ismaster command.

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

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

  ismaster_doc = app_metadata.validated_document
  if speculative_auth_doc
    ismaster_doc = ismaster_doc.merge(speculativeAuthenticate: speculative_auth_doc)
  end

  ismaster_command = Protocol::Query.new(Database::ADMIN, Database::COMMAND,
    ismaster_doc, :limit => -1)

  response = nil
  @server.handle_handshake_failure! do
    begin
      response = @server.round_trip_time_averager.measure do
        add_server_diagnostics do
          socket.write(ismaster_command.serialize.to_s)
          Protocol::Message.deserialize(socket, Protocol::Message::MAX_MESSAGE_SIZE).documents.first
        end
      end
    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

  post_handshake(response, @server.round_trip_time_averager.average_round_trip_time)

  response
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

ismaster response for this particular connection.
# File lib/mongo/server/pending_connection.rb, line 180
def post_handshake(response, average_rtt)
  if response["ok"] == 1
    # Auth mechanism is entirely dependent on the contents of
    # ismaster response *for this connection*.
    # Ismaster 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_rtt).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 209
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
      # ismaster 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