Skip to content

Commit 34c1064

Browse files
authored
🔀 Merge pull request #320 from ruby/sasl/client-adapter-api
🔒 📚 Improvements and docs for SASL::ClientAdapter
2 parents 585bf65 + 9f483ce commit 34c1064

File tree

3 files changed

+105
-25
lines changed

3 files changed

+105
-25
lines changed

lib/net/imap/sasl/client_adapter.rb

Lines changed: 45 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# frozen_string_literal: true
22

3+
require "forwardable"
4+
35
module Net
46
class IMAP
57
module SASL
@@ -8,18 +10,28 @@ module SASL
810
#
911
# TODO: use with more clients, to verify the API can accommodate them.
1012
#
11-
# An abstract base class for implementing a SASL authentication exchange.
12-
# Different clients will each have their own adapter subclass, overridden
13-
# to match their needs.
13+
# Represents the client to a SASL::AuthenticationExchange. By default,
14+
# most methods simply delegate to #client. Clients should subclass
15+
# SASL::ClientAdapter and override methods as needed to match the
16+
# semantics of this API to their API.
1417
#
15-
# Although the default implementations _may_ be sufficient, subclasses
16-
# will probably need to override some methods. Additionally, subclasses
17-
# may need to include a protocol adapter mixin, if the default
18+
# Subclasses should also include a protocol adapter mixin when the default
1819
# ProtocolAdapters::Generic isn't sufficient.
20+
#
21+
# === Protocol Requirements
22+
#
23+
# {RFC4422 §4}[https://www.rfc-editor.org/rfc/rfc4422.html#section-4]
24+
# lists requirements for protocol specifications to offer SASL. Where
25+
# possible, ClientAdapter delegates the handling of these requirements to
26+
# SASL::ProtocolAdapters.
1927
class ClientAdapter
28+
extend Forwardable
29+
2030
include ProtocolAdapters::Generic
2131

2232
# The client that handles communication with the protocol server.
33+
#
34+
# Most ClientAdapter methods are simply delegated to #client by default.
2335
attr_reader :client
2436

2537
# +command_proc+ can used to avoid exposing private methods on #client.
@@ -51,15 +63,17 @@ def initialize(client, &command_proc)
5163
# AuthenticationExchange.authenticate.
5264
def authenticate(...) AuthenticationExchange.authenticate(self, ...) end
5365

66+
##
67+
# method: sasl_ir_capable?
5468
# Do the protocol, server, and client all support an initial response?
55-
#
56-
# By default, this simply delegates to <tt>client.sasl_ir_capable?</tt>.
57-
def sasl_ir_capable?; client.sasl_ir_capable? end
69+
def_delegator :client, :sasl_ir_capable?
5870

59-
# Does the server advertise support for the mechanism?
71+
##
72+
# method: auth_capable?
73+
# call-seq: auth_capable?(mechanism)
6074
#
61-
# By default, this simply delegates to <tt>client.auth_capable?</tt>.
62-
def auth_capable?(mechanism); client.auth_capable?(mechanism) end
75+
# Does the server advertise support for the +mechanism+?
76+
def_delegator :client, :auth_capable?
6377

6478
# Calls command_proc with +command_name+ (see
6579
# SASL::ProtocolAdapters::Generic#command_name),
@@ -79,19 +93,30 @@ def run_command(mechanism, initial_response = nil, &continuations_handler)
7993
command_proc.call(*args, &continuations_handler)
8094
end
8195

96+
##
97+
# method: host
98+
# The hostname to which the client connected.
99+
def_delegator :client, :host
100+
101+
##
102+
# method: port
103+
# The destination port to which the client connected.
104+
def_delegator :client, :port
105+
82106
# Returns an array of server responses errors raised by run_command.
83107
# Exceptions in this array won't drop the connection.
84108
def response_errors; [] end
85109

86-
# Drop the connection gracefully.
87-
#
88-
# By default, this simply delegates to <tt>client.drop_connection</tt>.
89-
def drop_connection; client.drop_connection end
110+
##
111+
# method: drop_connection
112+
# Drop the connection gracefully, sending a "LOGOUT" command as needed.
113+
def_delegator :client, :drop_connection
114+
115+
##
116+
# method: drop_connection!
117+
# Drop the connection abruptly, closing the socket without logging out.
118+
def_delegator :client, :drop_connection!
90119

91-
# Drop the connection abruptly.
92-
#
93-
# By default, this simply delegates to <tt>client.drop_connection!</tt>.
94-
def drop_connection!; client.drop_connection! end
95120
end
96121
end
97122
end

lib/net/imap/sasl/protocol_adapters.rb

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,72 @@ module Net
44
class IMAP
55
module SASL
66

7+
# SASL::ProtocolAdapters modules are meant to be used as mixins for
8+
# SASL::ClientAdapter and its subclasses. Where the client adapter must
9+
# be customized for each client library, the protocol adapter mixin
10+
# handles \SASL requirements that are part of the protocol specification,
11+
# but not specific to any particular client library. In particular, see
12+
# {RFC4422 §4}[https://www.rfc-editor.org/rfc/rfc4422.html#section-4]
13+
#
14+
# === Interface
15+
#
16+
# >>>
17+
# NOTE: This API is experimental, and may change.
18+
#
19+
# - {#command_name}[rdoc-ref:Generic#command_name] -- The name of the
20+
# command used to to initiate an authentication exchange.
21+
# - {#service}[rdoc-ref:Generic#service] -- The GSSAPI service name.
22+
# - {#encode_ir}[rdoc-ref:Generic#encode_ir]--Encodes an initial response.
23+
# - {#decode}[rdoc-ref:Generic#decode] -- Decodes a server challenge.
24+
# - {#encode}[rdoc-ref:Generic#encode] -- Encodes a client response.
25+
# - {#cancel_response}[rdoc-ref:Generic#cancel_response] -- The encoded
26+
# client response used to cancel an authentication exchange.
27+
#
28+
# Other protocol requirements of the \SASL authentication exchange are
29+
# handled by SASL::ClientAdapter.
30+
#
31+
# === Included protocol adapters
32+
#
33+
# - Generic -- a basic implementation of all of the methods listed above.
34+
# - IMAP -- An adapter for the IMAP4 protocol.
35+
# - SMTP -- An adapter for the \SMTP protocol with the +AUTH+ capability.
36+
# - POP -- An adapter for the POP3 protocol with the +SASL+ capability.
737
module ProtocolAdapters
8-
# This API is experimental, and may change.
38+
# See SASL::ProtocolAdapters@Interface.
939
module Generic
40+
# The name of the protocol command used to initiate a \SASL
41+
# authentication exchange.
42+
#
43+
# The generic implementation returns <tt>"AUTHENTICATE"</tt>.
1044
def command_name; "AUTHENTICATE" end
11-
def service; raise "Implement in subclass or module" end
12-
def host; client.host end
13-
def port; client.port end
45+
46+
# A service name from the {GSSAPI/Kerberos/SASL Service Names
47+
# registry}[https://www.iana.org/assignments/gssapi-service-names/gssapi-service-names.xhtml].
48+
#
49+
# The generic implementation returns <tt>"host"</tt>, which is the
50+
# generic GSSAPI host-based service name.
51+
def service; "host" end
52+
53+
# Encodes an initial response string.
54+
#
55+
# The generic implementation returns the result of #encode, or returns
56+
# <tt>"="</tt> when +string+ is empty.
1457
def encode_ir(string) string.empty? ? "=" : encode(string) end
58+
59+
# Encodes a client response string.
60+
#
61+
# The generic implementation returns the Base64 encoding of +string+.
1562
def encode(string) [string].pack("m0") end
63+
64+
# Decodes a server challenge string.
65+
#
66+
# The generic implementation returns the Base64 decoding of +string+.
1667
def decode(string) string.unpack1("m0") end
68+
69+
# Returns the message used by the client to abort an authentication
70+
# exchange.
71+
#
72+
# The generic implementation returns <tt>"*"</tt>.
1773
def cancel_response; "*" end
1874
end
1975

lib/net/imap/sasl_adapter.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ class SASLAdapter < SASL::ClientAdapter
1212

1313
def response_errors; RESPONSE_ERRORS end
1414
def sasl_ir_capable?; client.capable?("SASL-IR") end
15-
def auth_capable?(mechanism); client.auth_capable?(mechanism) end
1615
def drop_connection; client.logout! end
1716
def drop_connection!; client.disconnect end
1817
end

0 commit comments

Comments
 (0)