# frozen_string_literal: true require_relative "gs2_header" module Net class IMAP < Protocol module SASL # Abstract base class for the SASL mechanisms defined in # RFC7628[https://tools.ietf.org/html/rfc7628]: # * OAUTHBEARER[rdoc-ref:OAuthBearerAuthenticator] # (OAuthBearerAuthenticator) # * OAUTH10A class OAuthAuthenticator include GS2Header # 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 # Hostname to which the client connected. (optional) attr_reader :host # Service port to which the client connected. (optional) attr_reader :port # HTTP method. (optional) attr_reader :mthd # HTTP path data. (optional) attr_reader :path # HTTP post data. (optional) attr_reader :post # The query string. (optional) attr_reader :qs alias query qs # Stores the most recent server "challenge". When authentication fails, # this may hold information about the failure reason, as JSON. attr_reader :last_server_response # Creates an RFC7628[https://tools.ietf.org/html/rfc7628] OAuth # authenticator. # # ==== Parameters # # See child classes for required parameter(s). The following parameters # are all optional, but it is worth noting that application protocols # are allowed to require #authzid (or other parameters, such as # #host or #port) as are specific server implementations. # # * _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 OAuth # token. # # * _optional_ #host — Hostname to which the client connected. # * _optional_ #port — Service port to which the client connected. # * _optional_ #mthd — HTTP method # * _optional_ #path — HTTP path data # * _optional_ #post — HTTP post data # * _optional_ #qs — HTTP query string # # _optional_ #query — An alias for #qs # # Any other keyword parameters are quietly ignored. def initialize(authzid: nil, host: nil, port: nil, username: nil, query: nil, mthd: nil, path: nil, post: nil, qs: nil, **) @authzid = authzid || username @host = host @port = port @mthd = mthd @path = path @post = post @qs = qs || query @done = false end # The {RFC7628 §3.1}[https://www.rfc-editor.org/rfc/rfc7628#section-3.1] # formatted response. def initial_client_response kv_pairs = { host: host, port: port, mthd: mthd, path: path, post: post, qs: qs, auth: authorization, # authorization is implemented by subclasses }.compact [gs2_header, *kv_pairs.map {|kv| kv.join("=") }, "\1"].join("\1") end # Returns initial_client_response the first time, then "^A". def process(data) @last_server_response = data done? ? "\1" : initial_client_response 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 # Value of the HTTP Authorization header # # Implemented by subclasses. def authorization; raise "must be implemented by subclass" end end # Authenticator for the "+OAUTHBEARER+" SASL mechanism, specified in # RFC7628[https://tools.ietf.org/html/rfc7628]. Authenticates using OAuth # 2.0 bearer tokens, as described in # RFC6750[https://tools.ietf.org/html/rfc6750]. Use via # Net::IMAP#authenticate. # # RFC6750[https://tools.ietf.org/html/rfc6750] requires Transport Layer # Security (TLS) to secure the protocol interaction between the client and # the resource server. TLS _MUST_ be used for +OAUTHBEARER+ to protect # the bearer token. class OAuthBearerAuthenticator < OAuthAuthenticator # An OAuth 2.0 bearer token. See {RFC-6750}[https://www.rfc-editor.org/rfc/rfc6750] attr_reader :oauth2_token alias secret oauth2_token # :call-seq: # new(oauth2_token, **options) -> authenticator # new(authzid, oauth2_token, **options) -> authenticator # new(oauth2_token:, **options) -> authenticator # # Creates an Authenticator for the "+OAUTHBEARER+" SASL mechanism. # # Called by Net::IMAP#authenticate and similar methods on other clients. # # ==== Parameters # # * #oauth2_token — An OAuth2 bearer token # # All other keyword parameters are passed to # {super}[rdoc-ref:OAuthAuthenticator::new] (see OAuthAuthenticator). # The most common ones are: # # * _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 # #oauth2_token. # # * _optional_ #host — Hostname to which the client connected. # * _optional_ #port — Service port to which the client connected. # # Although only oauth2_token is required by this mechanism, it is worth # noting that application protocols are allowed to # require #authzid (or other parameters, such as #host # _or_ #port) as are specific server implementations. def initialize(arg1 = nil, arg2 = nil, oauth2_token: nil, secret: nil, **args, &blk) username, oauth2_token_arg = arg2.nil? ? [nil, arg1] : [arg1, arg2] super(username: username, **args, &blk) @oauth2_token = oauth2_token || secret || oauth2_token_arg or raise ArgumentError, "missing oauth2_token" end # :call-seq: # initial_response? -> true # # +OAUTHBEARER+ sends an initial client response. def initial_response?; true end # Value of the HTTP Authorization header def authorization; "Bearer #{oauth2_token}" end end end end end