Skip to content

Commit

Permalink
Documentation cleanups
Browse files Browse the repository at this point in the history
  • Loading branch information
fluffy-critter committed Jul 22, 2020
1 parent 25983d7 commit d9e54ca
Show file tree
Hide file tree
Showing 10 changed files with 84 additions and 56 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
all: setup version format mypy cov pylint flake8 doc requirements.txt
all: setup version format mypy cov pylint flake8 doc

.PHONY: setup
setup:
Expand Down
10 changes: 5 additions & 5 deletions authl/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,15 +117,15 @@ def from_config(config: typing.Dict[str, typing.Any],
Handlers will be enabled based on truthy values of the following keys:
``EMAIL_FROM`` / ``EMAIL_SENDMAIL`` -- enable :py:mod:`handlers.email_addr`
* ``EMAIL_FROM`` / ``EMAIL_SENDMAIL``: enable :py:mod:`handlers.email_addr`
``FEDIVERSE_NAME`` -- enable :py:mod:`handlers.fediverse`
* ``FEDIVERSE_NAME``: enable :py:mod:`handlers.fediverse`
``INDIEAUTH_CLIENT_ID`` -- enable :py:mod:`handlers.indieauth`
* ``INDIEAUTH_CLIENT_ID``: enable :py:mod:`handlers.indieauth`
``TWITTER_CLIENT_KEY`` -- enable :py:mod:`handlers.twitter`
* ``TWITTER_CLIENT_KEY``: enable :py:mod:`handlers.twitter`
``TEST_ENABLED`` -- enable :py:mod:`handlers.test_handler`
*``TEST_ENABLED``: enable :py:mod:`handlers.test_handler`
For additional configuration settings, see each handler's respective ``from_config()``.
Expand Down
27 changes: 16 additions & 11 deletions authl/handlers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@

"""
Base handler class
==================
The :py:class:`Handler` class defines the abstract interface for an
authentication handler. Handlers are registered to an :py:class:`authl.Authl`
instance which then selects the handler based on the provided identity.
The basic flow for how a handler is selected is:
1. The :py:class:`authl.Authl` instance checks to see if any handler knows how to
handle the identity URL directly
2. The instance retrieves the URL, and hands the parse tree and response
headers off to each handler to see if it's able to handle the URL based
on that
#. The :py:class:`authl.Authl` instance checks to see if any handler knows how
to handle the identity URL directly; if so, it returns the first match.
#. The instance retrieves the URL, and hands the parse tree and response headers
off to each handler to see if it's able to handle the URL based on that; if
so, it returns the first match.
In the case of a Webfinger address (e.g. ``@user@example.com``) it repeats this
process for every profile URL provided by the Webfinger response.
process for every profile URL provided by the Webfinger response until it finds
a match.
"""
"""

import typing
from abc import ABC, abstractmethod
Expand All @@ -34,8 +39,8 @@ def handles_url(self, url: str) -> typing.Optional[str]:
It is okay to check for an API endpoint (relative to the URL) in
implementing this. However, if the content kept at the URL itself needs
to be parsed to make the determination, implement that in handles_page
instead.
to be parsed to make the determination, implement that in
:py:func:`handles_page` instead.
Whatever value this returns will be passed back in to initiate_auth, so
if that value matters, return a reasonable URL.
Expand All @@ -50,9 +55,9 @@ def handles_page(self, url: str, headers, content, links) -> bool:
:param str url: the canonicized identity URL
:param dict headers: the raw headers from the page request, as a
MultiDict (as provided by the `Requests`_ library)
:param bs4.BeautifulSoup content: -- the page content, as a
:param bs4.BeautifulSoup content: the page content, as a
`BeautifulSoup4`_ parse tree
:param dict links: -- the results of parsing the Link: headers, as a
:param dict links: the results of parsing the Link: headers, as a
dict of rel -> dict of 'url' and 'rel', as provided by the
`Requests`_ library
Expand Down
36 changes: 18 additions & 18 deletions authl/handlers/email_addr.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,34 +221,34 @@ def from_config(config, token_store: tokens.TokenStore):
:param dict config: The configuration settings for the handler. Relevant
keys:
* ``EMAIL_SENDMAIL``: a function to call to send the email (defaults to
using :py:func:`simple_sendmail`)
* ``EMAIL_SENDMAIL``: a function to call to send the email; if omitted,
generates one using :py:func:`simple_sendmail` configured with:
* ``EMAIL_FROM``: the ``From:`` address to use when sending an email
* ``EMAIL_FROM``: the ``From:`` address to use when sending an email
* ``EMAIL_SUBJECT``: the ``Subject:`` to use for a login email
* ``EMAIL_SUBJECT``: the ``Subject:`` to use for a login email
* ``EMAIL_CHECK_MESSAGE``: The :py:class:`authl.disposition.Notify` client
data. Defaults to a simple string-based message.
* ``SMTP_HOST``: the outgoing SMTP host
* ``EMAIL_TEMPLATE_FILE``: A path to a text file for the email message; if
not specified a default template will be used.
* ``SMTP_PORT``: the outgoing SMTP port
* ``EMAIL_EXPIRE_TIME``: How long a login email is valid for, in seconds
(defaults to the :py:class:`EmailAddress` default value)
* ``SMTP_USE_SSL``: whether to use SSL for the SMTP connection (defaults
to ``False``). It is *highly recommended* to set this to `True` if
your ``SMTP_HOST`` is anything other than ``localhost``.
* ``SMTP_HOST``: the outgoing SMTP host (required if no
``EMAIL_SENDMAIL``)
* ``SMTP_USERNAME``: the username to use with the SMTP server
* ``SMTP_PORT``: the outgoing SMTP port (required if no ``EMAIL_SENDMAIL``)
* ``SMTP_PASSWORD``: the password to use with the SMTP server
* ``SMTP_USE_SSL``: whether to use SSL for the SMTP connection (defaults
to ``False``). It is *highly recommended* to set this to `True` if
your ``SMTP_HOST`` is anything other than `localhost`.
* ``EMAIL_CHECK_MESSAGE``: The :py:class:`authl.disposition.Notify` client
data. Defaults to a simple string-based message.
* ``SMTP_USERNAME``: the username to use with the SMTP server
* ``EMAIL_TEMPLATE_FILE``: A path to a text file for the email message; if
not specified a default template will be used.
* ``EMAIL_EXPIRE_TIME``: How long a login email is valid for, in seconds
(defaults to the :py:class:`EmailAddress` default value)
* ``SMTP_PASSWORD``: the password to use with the SMTP server
:param tokens.TokenStore token_store: the authentication token storage
mechanism; see :py:mod:`authl.tokens` for more information.
Expand Down
17 changes: 12 additions & 5 deletions authl/handlers/fediverse.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
=================
This handler allows login via Fediverse instances; currently `Mastodon
<https://joinmastodon.org>` and `Pleroma <https://pleroma.social>` are
<https://joinmastodon.org>`_ and `Pleroma <https://pleroma.social>`_ are
supported, as is anything else with basic support for the Mastodon client API.
See :py:func:`from_config` for the simplest configuration mechanism.
Expand All @@ -28,7 +28,7 @@
class Fediverse(Handler):
""" Handler for Fediverse services (Mastodon, Pleroma) """

class Client:
class _Client:
""" Fediverse OAuth client info """
# pylint:disable=too-few-public-methods

Expand Down Expand Up @@ -115,6 +115,10 @@ def _get_instance(url) -> typing.Optional[str]:
return None

def handles_url(self, url):
"""
Checks for an ``/api/v1/instance`` endpoint to determine if this
is a Mastodon-compatible instance
"""
LOGGER.info("Checking URL %s", url)

instance = self._get_instance(url)
Expand All @@ -132,7 +136,7 @@ def handles_url(self, url):
return instance

@functools.lru_cache(128)
def _get_client(self, id_url: str, callback_uri: str) -> typing.Optional['Fediverse.Client']:
def _get_client(self, id_url: str, callback_uri: str) -> typing.Optional['Fediverse._Client']:
""" Get the client data """
instance = self._get_instance(id_url)
if not instance:
Expand All @@ -151,7 +155,7 @@ def _get_client(self, id_url: str, callback_uri: str) -> typing.Optional['Fedive
if info['redirect_uri'] != callback_uri:
raise ValueError("Got incorrect redirect_uri")

return Fediverse.Client(instance, {
return Fediverse._Client(instance, {
'client_id': info['client_id'],
'redirect_uri': info['redirect_uri'],
'scope': 'read:accounts'
Expand Down Expand Up @@ -213,7 +217,7 @@ def check_callback(self, url, get, data):
if time.time() > when + self._timeout:
return disposition.Error("Transaction timed out", redir)

client = Fediverse.Client(*client_tuple)
client = Fediverse._Client(*client_tuple)

if 'error' in get:
return disposition.Error("Error signing into Fediverse", redir)
Expand Down Expand Up @@ -258,8 +262,11 @@ def from_config(config, token_store: tokens.TokenStore):
""" Generate a Fediverse handler from the given config dictionary.
:param dict config: Configuration values; relevant keys:
* ``FEDIVERSE_NAME``: the name of your website (required)
* ``FEDIVERSE_HOMEPAGE``: your website's homepage (recommended)
* ``FEDIVERSE_TIMEOUT``: the maximum time to wait for login to complete
:param tokens.TokenStore token_store: The authentication token storage
Expand Down
22 changes: 13 additions & 9 deletions authl/handlers/indieauth.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ class IndieAuth(Handler):
""" Directly support login via IndieAuth, without requiring third-party
IndieLogin brokerage.
SECURITY NOTE: When used with tokens.Serializer, this is subject to certain
**SECURITY NOTE:** When used with tokens.Serializer, this is subject to certain
classes of replay attack; for example, if the user endpoint uses irrevocable
signed tokens for the code grant (which is done in many endpoints, e.g.
SelfAuth), an attacker can replay a transaction that it intercepts. As such
Expand Down Expand Up @@ -175,28 +175,32 @@ def logo_html(self):

def __init__(self, client_id: typing.Union[str, typing.Callable[..., str]],
token_store: tokens.TokenStore, timeout: int = None):
""" Construct an IndieAuth handler
"""
:param client_id: The client_id to send to the remote IndieAuth
provider. Can be a string or a function that returns a string.
provider. Can be a string or a function that returns a string.
:param token_store: Storage for the tokens
:param int timeout: Maximum time to wait for login to complete (default: 600)
:param int timeout: Maximum time to wait for login to complete
(default: 600)
"""

self._client_id = client_id
self._token_store = token_store
self._timeout = timeout or 600

def handles_url(self, url):
# If we already know what endpoint exists for this, go ahead and say it.
# Otherwise, we fall through to handles_page.
"""
If this page is already known to have an IndieAuth endpoint, we reuse
that; otherwise this falls through to :py:func:`handles_page`.
"""
if url in _ENDPOINT_CACHE:
return url
return None

def handles_page(self, url, headers, content, links):
""" Returns whether an ``authorization_endpoint`` was found on the page. """
return find_endpoint(url, links, content) is not None

def initiate_auth(self, id_url, callback_uri, redir):
Expand Down Expand Up @@ -270,8 +274,8 @@ def from_config(config, token_store):
Possible configuration values:
INDIEAUTH_CLIENT_ID -- the client ID (URL) of your website (required)
INDIEAUTH_PENDING_TTL -- timemout for a pending transction
* ``INDIEAUTH_CLIENT_ID``: the client ID (URL) of your website (required)
* ``INDIEAUTH_PENDING_TTL``:timemout for a pending transction
"""
return IndieAuth(config['INDIEAUTH_CLIENT_ID'],
token_store,
Expand Down
6 changes: 6 additions & 0 deletions authl/handlers/test_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,17 @@ class TestHandler(Handler):
'test:'. Primarily for testing purposes. """

def handles_url(self, url):
""" Returns ``True`` if the URL starts with ``'test:'``. """
if url.startswith('test:'):
return url
return None

def initiate_auth(self, id_url, callback_uri, redir):
"""
Immediately returns a :py:class:`disposition.Verified`, unless the URL
is ``'test:error'`` in which case it returns a
:py:class:`disposition.Error`.
"""
if id_url == 'test:error':
return disposition.Error("Error identity requested", redir)
return disposition.Verified(id_url, redir)
Expand Down
15 changes: 9 additions & 6 deletions authl/handlers/twitter.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@
To use it you will need to register your website as an application via the
`Twitter developer portal <https://developer.twitter.com/en>`_ and retrieve your
``client_key`` and ``client_secret`` from there. You will also need to register
your website's Twitter callback handler.
your website's Twitter callback handler(s). Remember to include all URLs that
the callback might be accessed from, including test domains.
It is **highly recommended** that you only store the ``client_key`` and
``client_secret`` in an environment variable rather than by checked-in code as a
basic security precaution.
``client_secret`` in an environment variable rather than by checked-in code, as
a basic security precaution against credential leaks.
See :py:func:`from_config` for the simplest configuration mechanism.
Expand Down Expand Up @@ -175,9 +176,11 @@ def from_config(config, storage):
Posible configuration values:
TWITTER_CLIENT_KEY -- The Twitter app's client_key
TWITTER_CLIENT_SECRET -- The Twitter app's client_secret
TWITTER_TIMEOUT -- How long to wait for the user to log in
* ``TWITTER_CLIENT_KEY``: The Twitter app's client_key
* ``TWITTER_CLIENT_SECRET``: The Twitter app's client_secret
* ``TWITTER_TIMEOUT``: How long to wait for the user to log in
It is ***HIGHLY RECOMMENDED*** that the client key and secret be provided
via environment variables or some other mechanism that doesn't involve
Expand Down
4 changes: 4 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
project = "Authl"
master_doc = "index"
extensions = ["sphinx.ext.autodoc"]
autoclass_content = "both"
autodoc_member_order = "groupwise"
autodoc_inherit_docstrings = False
autodoc_mock_imports = ["flask"]
1 change: 0 additions & 1 deletion docs/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
sphinx
flask

0 comments on commit d9e54ca

Please sign in to comment.