# frozen_string_literal: true

module Net
  class IMAP < Protocol
    module SASL

      # Authenticator for the "+EXTERNAL+" SASL mechanism, as specified by
      # RFC-4422[https://www.rfc-editor.org/rfc/rfc4422].  See
      # Net::IMAP#authenticate.
      #
      # The EXTERNAL mechanism requests that the server use client credentials
      # established external to SASL, for example by TLS certificate or IPSec.
      class ExternalAuthenticator

        # Authorization identity: an identity to act as or on behalf of.  The
        # identity form is application protocol specific.  If not provided or
        # left blank, the server derives an authorization identity from the
        # authentication identity.  The server is responsible for verifying the
        # client's credentials and verifying that the identity it associates
        # with the client's authentication identity is allowed to act as (or on
        # behalf of) the authorization identity.
        #
        # For example, an administrator or superuser might take on another role:
        #
        #     imap.authenticate "PLAIN", "root", passwd, authzid: "user"
        #
        attr_reader :authzid
        alias username authzid

        # :call-seq:
        #   new(authzid: nil, **) -> authenticator
        #   new(username: nil, **) -> authenticator
        #   new(username = nil, **) -> authenticator
        #
        # Creates an Authenticator for the "+EXTERNAL+" SASL mechanism, as
        # specified in RFC-4422[https://www.rfc-editor.org/rfc/rfc4422].  To use
        # this, see Net::IMAP#authenticate or your client's authentication
        # method.
        #
        # ==== Parameters
        #
        # * _optional_ #authzid  ― Authorization identity to act as or on behalf of.
        #
        #   _optional_ #username ― An alias for #authzid.
        #
        #   Note that, unlike some other authenticators, +username+ sets the
        #   _authorization_ identity and not the _authentication_ identity.  The
        #   authentication identity is established for the client by the
        #   external credentials.
        #
        # Any other keyword parameters are quietly ignored.
        def initialize(user = nil, authzid: nil, username: nil, **)
          authzid ||= username || user
          @authzid = authzid&.to_str&.encode "UTF-8"
          if @authzid&.match?(/\u0000/u) # also validates UTF8 encoding
            raise ArgumentError, "contains NULL"
          end
          @done = false
        end

        # :call-seq:
        #   initial_response? -> true
        #
        # +EXTERNAL+ can send an initial client response.
        def initial_response?; true end

        # Returns #authzid, or an empty string if there is no authzid.
        def process(_)
          authzid || ""
        ensure
          @done = true
        end

        # Returns true when the initial client response was sent.
        #
        # The authentication should not succeed unless this returns true, but it
        # does *not* indicate success.
        def done?; @done end

      end
    end
  end
end
