class Mongo::Server::PendingConnection
This class encapsulates connections during handshake and authentication.
@api private
Attributes
@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
# File lib/mongo/server/pending_connection.rb, line 26 def initialize(socket, server, monitoring, options = {}) @socket = socket @options = options @server = server @monitoring = monitoring @id = options[:id] end
Public Instance Methods
# File lib/mongo/server/pending_connection.rb, line 39 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
@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 171 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
# File lib/mongo/server/pending_connection.rb, line 270 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
# File lib/mongo/server/pending_connection.rb, line 199 def ensure_connected yield @socket end
@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 117 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
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 207 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
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 241 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