lib/mongo/auth.rb
# frozen_string_literal: true
# rubocop:todo all
# Copyright (C) 2014-2020 MongoDB Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
require 'mongo/auth/credential_cache'
require 'mongo/auth/stringprep'
require 'mongo/auth/conversation_base'
require 'mongo/auth/sasl_conversation_base'
require 'mongo/auth/scram_conversation_base'
require 'mongo/auth/user'
require 'mongo/auth/roles'
require 'mongo/auth/base'
require 'mongo/auth/aws'
require 'mongo/auth/cr'
require 'mongo/auth/gssapi'
require 'mongo/auth/ldap'
require 'mongo/auth/scram'
require 'mongo/auth/scram256'
require 'mongo/auth/x509'
require 'mongo/error/read_write_retryable'
require 'mongo/error/labelable'
module Mongo
# This namespace contains all authentication related behavior.
#
# @since 2.0.0
module Auth
extend self
# The external database name.
#
# @since 2.0.0
# @api private
EXTERNAL = '$external'.freeze
# Constant for the nonce command.
#
# @since 2.0.0
# @api private
GET_NONCE = { getnonce: 1 }.freeze
# Constant for the nonce field.
#
# @since 2.0.0
# @api private
NONCE = 'nonce'.freeze
# Map the symbols parsed from the URI connection string to strategies.
#
# @note This map is not frozen because when mongo_kerberos is loaded,
# it mutates this map by adding the Kerberos authenticator.
#
# @since 2.0.0
SOURCES = {
aws: Aws,
gssapi: Gssapi,
mongodb_cr: CR,
mongodb_x509: X509,
plain: LDAP,
scram: Scram,
scram256: Scram256,
}
# Get an authenticator for the provided user to authenticate over the
# provided connection.
#
# @param [ Auth::User ] user The user to authenticate.
# @param [ Mongo::Connection ] connection The connection to authenticate over.
#
# @option opts [ String | nil ] speculative_auth_client_nonce The client
# nonce used in speculative auth on the specified connection that
# produced the specified speculative auth result.
# @option opts [ BSON::Document | nil ] speculative_auth_result The
# value of speculativeAuthenticate field of hello response of
# the handshake on the specified connection.
#
# @return [ Auth::Aws | Auth::CR | Auth::Gssapi | Auth::LDAP |
# Auth::Scram | Auth::Scram256 | Auth::X509 ] The authenticator.
#
# @since 2.0.0
# @api private
def get(user, connection, **opts)
mechanism = user.mechanism
raise InvalidMechanism.new(mechanism) if !SOURCES.has_key?(mechanism)
SOURCES[mechanism].new(user, connection, **opts)
end
# Raised when trying to authorize with an invalid configuration
#
# @since 2.11.0
class InvalidConfiguration < Mongo::Error::AuthError; end
# Raised when trying to get an invalid authorization mechanism.
#
# @since 2.0.0
class InvalidMechanism < InvalidConfiguration
# Instantiate the new error.
#
# @example Instantiate the error.
# Mongo::Auth::InvalidMechanism.new(:test)
#
# @param [ Symbol ] mechanism The provided mechanism.
#
# @since 2.0.0
def initialize(mechanism)
known_mechanisms = SOURCES.keys.sort.map do |key|
key.inspect
end.join(', ')
super("#{mechanism.inspect} is invalid, please use one of the following mechanisms: #{known_mechanisms}")
end
end
# Raised when a user is not authorized on a database.
#
# @since 2.0.0
class Unauthorized < Mongo::Error::AuthError
include Error::ReadWriteRetryable
include Error::Labelable
# @return [ Integer ] The error code.
attr_reader :code
# Instantiate the new error.
#
# @example Instantiate the error.
# Mongo::Auth::Unauthorized.new(user)
#
# @param [ Mongo::Auth::User ] user The unauthorized user.
# @param [ String ] used_mechanism Auth mechanism actually used for
# authentication. This is a full string like SCRAM-SHA-256.
# @param [ String ] message The error message returned by the server.
# @param [ Server ] server The server instance that authentication
# was attempted against.
# @param [ Integer ] The error code.
#
# @since 2.0.0
def initialize(user, used_mechanism: nil, message: nil,
server: nil, code: nil
)
@code = code
configured_bits = []
used_bits = [
"auth source: #{user.auth_source}",
]
if user.mechanism
configured_bits << "mechanism: #{user.mechanism}"
end
if used_mechanism
used_bits << "used mechanism: #{used_mechanism}"
end
if server
used_bits << "used server: #{server.address} (#{server.status})"
end
used_user = if user.mechanism == :mongodb_x509
'Client certificate'
else
"User #{user.name}"
end
if configured_bits.empty?
configured_bits = ''
else
configured_bits = " (#{configured_bits.join(', ')})"
end
used_bits = " (#{used_bits.join(', ')})"
msg = "#{used_user}#{configured_bits} is not authorized to access #{user.database}#{used_bits}"
if message
msg += ': ' + message
end
super(msg)
end
end
end
end