Skip to content

Commit

Permalink
Improve Documentation (#245)
Browse files Browse the repository at this point in the history
* Tune CSS

* Change :boldital: to full-fledged py:attribute::

This allows reflinking + niceties such as versionadded:: and
deprecated::

* Update concepts.rst with new attribs in Session

* Deprecate Session.login_data

* Move _static into docs

They are not separable anyways.

* More CSS Tuning

* HUGE rewrite

* Now uses a lot of Sphinx's py domain roles
* This reactivates modindex
* The whole AUTH system moved to its own page because it's getting
 unwieldy
* Public classes/functions added to several pages

* Fetch master only if not in master

* Make sure that "run" with "if" is by bash
  • Loading branch information
pepoluan committed Feb 12, 2021
1 parent 6404ab2 commit 1a3438d
Show file tree
Hide file tree
Showing 11 changed files with 841 additions and 368 deletions.
20 changes: 17 additions & 3 deletions _static/aiosmtpd.css → aiosmtpd/docs/_static/aiosmtpd.css
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,33 @@ div.body h4 {

.pre, pre, code {
font-family: "Fira Code",Menlo,Consolas,"Ubuntu Mono",Inconsolata,"Bitstream Vera Sans Mono","lucida console","Courier New",monospace;
font-size: 90%;
font-size: 100%;
}

div.highlight pre {
font-size: 90%;
}

/*
code.descname {
font-size: 100% !important;
}
*/

dl.class > dt {
font-size: 100% !important;
}

dl.attribute > dt {
font-size: 100% !important;
}

dl.field-list {
margin-bottom: 0.25em !important;
}

dl.class > dt > code.descname {
font-size: 110% !important;
dl.field-list ul.simple {
margin-bottom: 0.25em !important;
}

.sig-paren {
Expand Down
196 changes: 196 additions & 0 deletions aiosmtpd/docs/auth.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
.. _auth:

=======================
Authentication System
=======================

``aiosmtpd`` provides a framework for SMTP Authentication that fully complies with :rfc:`4954`.


Activating Authentication
=========================

``aiosmtpd`` authentication is always activated,
but attempts to authenticate will always be rejected
unless the :attr:`authenticator` parameter of :class:`~aiosmtpd.smtp.SMTP`
is set to a valid & working :ref:`authcallback`.


AUTH API
========

The ``aiosmtpd`` Authentication Framework comprises several components,
who are collectivelly called the "AUTH API".

.. _authhandler:

AUTH Handler Hook
-----------------

.. py:method:: handle_AUTH(server: SMTP, session: Session, envelope: Envelope, args)
:async:

Called to handle ``AUTH`` command, if you need custom AUTH behavior.

Most of the time, you will NOT *need* to implement this hook;
:ref:`authmech` are provided to override/implement selective
SMTP AUTH mechanisms (see below).

If you do implement this hook:

You *MUST* comply with :rfc:`4954`.

``args`` will contain the list of words following the ``AUTH`` command.

You will have to leverage the :meth:`SMTP.push` and :meth:`SMTP.challenge_auth` methods
to interact with the clients.

You will need to modify the :attr:`session.auth_data <Session.auth_data>`
and :attr:`session.authenticated <Session.authenticated>` attributes.

You may ignore the ``envelope``.

.. _authmech:

AUTH Mechanism Hooks
--------------------

Separately from :ref:`authhandler`,
``aiosmtpd`` also implement support for "AUTH Mechanism Hooks".
These **async** hooks will implement the logic for SMTP Authentication Mechanisms.

Every AUTH Mechanism Hook is named ``auth_MECHANISM``
where ``MECHANISM`` is the all-uppercase name of the mechanism
that the hook will implement.

(Mechanism is the word following the ``AUTH`` command sent by client.)

.. important::

If ``MECHANISM`` has a dash within its name,
use **double-underscore** to represent the dash.
For example, to implement a ``MECH-WITH-DASHES`` mechanism,
name the AUTH hook as ``auth_MECH__WITH__DASHES``.

Single underscores will not be modified.
So a hook named ``auth_MECH_WITH_UNDERSCORE``
will implement the ``MECH_WITH_UNDERSCORE`` mechanism.

(If in the future a SASL mechanism with double underscores in its name gets defined,
this name-mangling mechanism will be revisited.
That is very unlikely to happen, though.)

Alternatively, you can also use the :func:`~aiosmtpd.smtp.auth_mechanism` decorator,
which you can import from the :mod:`aiosmtpd.smtp` module.

The SMTP class provides built-in AUTH hooks for the ``LOGIN`` and ``PLAIN``
mechanisms, named ``auth_LOGIN`` and ``auth_PLAIN``, respectively.
If the handler class implements ``auth_LOGIN`` and/or ``auth_PLAIN``, then
the methods of the handler instance will override the built-in methods.

.. py:method:: auth_MECHANISM(server: SMTP, args: List[str]) -> aiosmtpd.smtp.AuthResult
:async:

:param server: The instance of the :class:`SMTP` class invoking the AUTH Mechanism hook
:param args: A list of string split from the characters following the ``AUTH`` command.
``args[0]`` is usually equal to ``MECHANISM``
(unless the :func:`~aiosmtpd.smtp.auth_mechanism` decorator has been used).

The AUTH hook MUST perform the actual validation of AUTH credentials.

In the built-in AUTH hooks,
this is done by invoking the function specified
by the :attr:`authenticator` initialization argument.

AUTH Mechanism Hooks in handlers are NOT required to do the same,
and MAY implement their own authenticator system.

The AUTH Mechanism Hook MUST return an instance of :class:`AuthResult`
containing the result of the Authentication process.

.. important::

Defining *additional* AUTH hooks in your handler
will NOT disable the built-in LOGIN and PLAIN hooks;
if you do not want to offer the LOGIN and PLAIN mechanisms,
specify them in the :attr:`auth_exclude_mechanism` parameter
of the :class:`SMTP` class.


.. _authcallback:

Authenticator Callback
----------------------

.. py:function:: Authenticator(server, session, envelope, mechanism, auth_data) -> AuthResult
:param server: The :class:`~aiosmtpd.smtp.SMTP` instance that invoked the authenticator
:param session: A :class:`Session` instance containing session data *so far*
:param envelope: An :class:`Envelope` instance containing transaction data *so far*
:param mechanism: name of the AUTH Mechanism chosen by the client
:type mechanism: str
:param auth_data: A data structure containing authentication data gathered by the AUTH Mechanism
:return: Result of authentication
:rtype: AuthResult

This function would be invoked during or at the end of an Authentication Process by
AUTH Mechanisms.
Based on ``mechanism`` and ``auth_data``,
this function should return a decision on whether Authentication has been successful or not.

This function SHOULD NOT modify the attributes of ``session`` and ``envelope``.

The type and contents of the ``auth_data`` parameter is wholly at the discretion of the
calling AUTH Mechanism. For the built-in ``LOGIN`` and ``PLAIN`` Mechanisms, the type
of data will be :class:`aiosmtpd.smtp.LoginPassword`

.. versionadded:: 1.3

AuthResult API
--------------

.. class:: AuthResult(*, success, handled, message, auth_data)

.. py:attribute:: success
:type: bool

This attribute indicates whether Authentication is successful or not.

.. py:attribute:: handled
:type: bool
:value: True

This attribute indicates whether Authenticator Decision process
(e.g., sending of status codes)
have been carried out by Authenticator or not.

If set to ``True``, :meth:`smtp_AUTH` will not perform additional processing
and will simply exits.

Applicable only if ``success=False``

.. py:attribute:: message
:type: Optional[str]
:value: None

The message to send back to client, regardless of success status.

This message will be sent as-is;
as such, it MUST be prefixed with the correct SMTP Status Code
and optionally, SMTP Extended Status Code.

If not given (set/kept to ``None``),
:meth:`smtp_AUTH` will use standard SMTP Status Code & Message.

.. py:attribute:: auth_data
:type: Any
:value: None

Optional free-form authentication data.
This will be saved by :meth:`smtp_AUTH` into the ``session.auth_data`` attribute.

If ``auth_data`` has the attribute ``login``,
then :meth:`smtp_AUTH` will save ``auth_data.login`` into ``session.login_data`` as well.
This is to cater for possible backward-compatibility requirements,
where legacy handlers might be looking for ``session.login_data`` for some reasons.

40 changes: 39 additions & 1 deletion aiosmtpd/docs/concepts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ Sessions and envelopes
Two classes are used during the SMTP dialog with clients. Instances of these
are passed to the handler hooks.

.. note::

Handler Hooks MAY add new attributes to these classes for inter-hook coordination.


Session
-------
Expand All @@ -40,7 +44,9 @@ The session represents the state built up during a client's socket connection
to the server. Each time a client connects to the server, a new session
object is created.

.. class:: Session()
.. class:: Session(loop)

:param loop: asyncio event loop currently running :class:`SMTP`.

.. attribute:: peer

Expand Down Expand Up @@ -71,12 +77,38 @@ object is created.

This is the asyncio event loop instance.

:ref:`hooks` can utilize this if needed,
for instance invoking :meth:`~asyncio.loop.call_later` to set some timers.

.. attribute:: login_data

Contains the login information gathered during the ``AUTH`` procedure.
If it contains ``None``, that means authentication has not taken place
or has failed.

.. warning::

This is the "legacy" login_data,
populated only if :attr:`auth_callback` parameter is set.

.. deprecated:: 1.3

This attribute **will be removed in version 2.0**.

.. py:attribute:: auth_data
Contains the authentication data returned by
the :attr:`authenticator` callback.

.. py:attribute:: authenticated
:type: Optional[bool]

A tri-state flag indicating status of authentication:

* ``None`` := Authentication has not been performed
* ``False`` := Authentication has been performed, but failed
* ``True`` := Authentication has been performed, and succeeded


Envelope
--------
Expand All @@ -90,17 +122,20 @@ completed, or in certain error conditions as mandated by :rfc:`5321`.
.. class:: Envelope

.. attribute:: mail_from
:type: str

Defaulting to None, this attribute holds the email address given in the
``MAIL FROM`` command.

.. attribute:: mail_options
:type: List[str]

Defaulting to None, this attribute contains a list of any ESMTP mail
options provided by the client, such as those passed in by
:meth:`smtplib.SMTP.sendmail`

.. attribute:: content
:type: AnyStr

Defaulting to None, this attribute will contain the contents of the
message as provided by the ``DATA`` command. If the ``decode_data``
Expand All @@ -109,17 +144,20 @@ completed, or in certain error conditions as mandated by :rfc:`5321`.
bytes.

.. attribute:: original_content
:type: bytes

Defaulting to None, this attribute will contain the contents of the
message as provided by the ``DATA`` command. Unlike the :attr:`content`
attribute, this attribute will always contain the raw bytes.

.. attribute:: rcpt_tos
:type: List[str]

Defaulting to the empty list, this attribute will contain a list of the
email addresses provided in the ``RCPT TO`` commands.

.. attribute:: rcpt_options
:type: List[str]

Defaulting to the empty list, this attribute will contain the list of
any recipient options provided by the client, such as those passed in by
Expand Down
4 changes: 2 additions & 2 deletions aiosmtpd/docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ def syspath_insert(pth: Path):
pygments_style = "sphinx"

# A list of ignored prefixes for module index sorting.
# modindex_common_prefix = []
modindex_common_prefix = ["aiosmtpd."]

# If true, keep warnings as "system message" paragraphs in the built documents.
# keep_warnings = False
Expand Down Expand Up @@ -188,7 +188,7 @@ def syspath_insert(pth: Path):
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ["../../_static"]
html_static_path = ["_static"]

# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
Expand Down

0 comments on commit 1a3438d

Please sign in to comment.