# frozen_string_literal: true module Net::IMAP::SASL # Registry for SASL authenticators # # Registered authenticators must respond to +#new+ or +#call+ (e.g. a class or # a proc), receiving any credentials and options and returning an # authenticator instance. The returned object represents a single # authentication exchange and must not be reused for multiple # authentication attempts. # # An authenticator instance object must respond to +#process+, receiving the # server's challenge and returning the client's response. Optionally, it may # also respond to +#initial_response?+ and +#done?+. When # +#initial_response?+ returns +true+, +#process+ may be called the first # time with +nil+. When +#done?+ returns +false+, the exchange is incomplete # and an exception should be raised if the exchange terminates prematurely. # # See the source for PlainAuthenticator, XOAuth2Authenticator, and # ScramSHA1Authenticator for examples. class Authenticators # Create a new Authenticators registry. # # This class is usually not instantiated directly. Use SASL.authenticators # to reuse the default global registry. # # When +use_defaults+ is +false+, the registry will start empty. When # +use_deprecated+ is +false+, deprecated authenticators will not be # included with the defaults. def initialize(use_defaults: true, use_deprecated: true) @authenticators = {} return unless use_defaults add_authenticator "Anonymous" add_authenticator "External" add_authenticator "OAuthBearer" add_authenticator "Plain" add_authenticator "Scram-SHA-1" add_authenticator "Scram-SHA-256" add_authenticator "XOAuth2" return unless use_deprecated add_authenticator "Login" # deprecated add_authenticator "Cram-MD5" # deprecated add_authenticator "Digest-MD5" # deprecated end # Returns the names of all registered SASL mechanisms. def names; @authenticators.keys end # :call-seq: # add_authenticator(mechanism) # add_authenticator(mechanism, authenticator_class) # add_authenticator(mechanism, authenticator_proc) # # Registers an authenticator for #authenticator to use. +mechanism+ is the # name of the # {SASL mechanism}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml] # implemented by +authenticator_class+ (for instance, "PLAIN"). # # If +mechanism+ refers to an existing authenticator, a warning will be # printed and the old authenticator will be replaced. # # When only a single argument is given, the authenticator class will be # lazily loaded from Net::IMAP::SASL::#{name}Authenticator (case is # preserved and non-alphanumeric characters are removed.. def add_authenticator(name, authenticator = nil) key = -name.to_s.upcase.tr(?_, ?-) authenticator ||= begin class_name = "#{name.gsub(/[^a-zA-Z0-9]/, "")}Authenticator".to_sym auth_class = nil ->(*creds, **props, &block) { auth_class ||= Net::IMAP::SASL.const_get(class_name) auth_class.new(*creds, **props, &block) } end @authenticators[key] = authenticator end # Removes the authenticator registered for +name+ def remove_authenticator(name) key = -name.to_s.upcase.tr(?_, ?-) @authenticators.delete(key) end def mechanism?(name) key = -name.to_s.upcase.tr(?_, ?-) @authenticators.key?(key) end # :call-seq: # authenticator(mechanism, ...) -> auth_session # # Builds an authenticator instance using the authenticator registered to # +mechanism+. The returned object represents a single authentication # exchange and must not be reused for multiple authentication # attempts. # # All arguments (except +mechanism+) are forwarded to the registered # authenticator's +#new+ or +#call+ method. Each authenticator must # document its own arguments. # # [Note] # This method is intended for internal use by connection protocol code # only. Protocol client users should see refer to their client's # documentation, e.g. Net::IMAP#authenticate. def authenticator(mechanism, ...) key = -mechanism.to_s.upcase.tr(?_, ?-) auth = @authenticators.fetch(key) do raise ArgumentError, 'unknown auth type - "%s"' % key end auth.respond_to?(:new) ? auth.new(...) : auth.call(...) end alias new authenticator end end