Browse Source

Merge pull request #434 from justanr/ISS-431-interface-documentation

Iss 431 interface documentation
Peter Justin 7 years ago
parent
commit
715f2d8978

+ 10 - 0
docs/accountmanagement.rst

@@ -0,0 +1,10 @@
+.. _accountmanagement:
+
+Account Management
+==================
+
+.. autoclass:: flaskbb.core.auth.password.ResetPasswordService
+    :members:
+
+.. autoclass:: flaskbb.core.auth.activation.AccountActivator
+    :members:

+ 5 - 0
docs/api.rst

@@ -6,4 +6,9 @@ API
 .. toctree::
 .. toctree::
    :maxdepth: 2
    :maxdepth: 2
 
 
+   coreexceptions
    models
    models
+   registration
+   authentication
+   accountmanagement
+   tokens

+ 80 - 0
docs/authentication.rst

@@ -0,0 +1,80 @@
+.. _authentication:
+
+Authentication
+==============
+
+FlaskBB exposes several interfaces and hooks to customize authentication and
+implementations of these. For details on the hooks see :ref:`hooks`
+
+Authentication Interfaces
+-------------------------
+
+.. module:: flaskbb.core.auth.authentication
+
+.. autoclass:: AuthenticationManager
+   :members:
+   :undoc-members:
+
+.. autoclass:: AuthenticationProvider
+   :members:
+   :undoc-members:
+
+.. autoclass:: PostAuthenticationHandler
+   :members:
+   :undoc-members:
+
+.. autoclass:: AuthenticationFailureHandler
+   :members:
+   :undoc-members:
+
+
+Authentication Provided Implementations
+---------------------------------------
+
+.. module:: flaskbb.auth.services.authentication
+.. autoclass:: DefaultFlaskBBAuthProvider
+.. autoclass:: MarkFailedLogin
+.. autoclass:: BlockUnactivatedUser
+.. autoclass:: ClearFailedLogins
+.. autoclass:: PluginAuthenticationManager
+
+
+Reauthentication Interfaces
+---------------------------
+
+.. module:: flaskbb.core.auth.authentication
+
+.. autoclass:: ReauthenticateManager
+   :members:
+   :undoc-members:
+
+.. autoclass:: ReauthenticateProvider
+   :members:
+   :undoc-members:
+
+.. autoclass:: PostReauthenticateHandler
+   :members:
+   :undoc-members:
+
+.. autoclass:: ReauthenticateFailureHandler
+   :members:
+   :undoc-members:
+
+
+Reauthentication Provided Implementations
+-----------------------------------------
+
+.. module:: flaskbb.auth.services.reauthentication
+.. autoclass:: DefaultFlaskBBReauthProvider
+.. autoclass:: ClearFailedLoginsOnReauth
+.. autoclass:: MarkFailedReauth
+.. autoclass:: PluginReauthenticationManager
+
+
+Exceptions
+----------
+
+.. module:: flaskbb.core.auth.authentication
+
+.. autoexception:: StopAuthentication
+.. autoexception:: ForceLogout

+ 13 - 0
docs/coreexceptions.rst

@@ -0,0 +1,13 @@
+.. _coreexceptions:
+
+.. module:: flaskbb.core.exceptions
+
+Core Exceptions
+===============
+
+These are exceptions that aren't specific to any one part of FlaskBB
+and are used ubiquitously.
+
+.. autoexception:: BaseFlaskBBError
+.. autoexception:: ValidationError
+.. autoexception:: StopValidation

+ 3 - 1
docs/hooks.rst

@@ -17,6 +17,9 @@ A hook needs a hook specification which are defined in
 :mod:`flaskbb.plugins.spec`. All hooks have to be prefixed with
 :mod:`flaskbb.plugins.spec`. All hooks have to be prefixed with
 ``flaskbb_`` and template hooks with ``flaskbb_tpl_``.
 ``flaskbb_`` and template hooks with ``flaskbb_tpl_``.
 
 
+Be sure to also check out the :ref:`api` documentation for interfaces that
+interact with these plugins in interesting ways.
+
 
 
 Python Hooks
 Python Hooks
 ------------
 ------------
@@ -77,7 +80,6 @@ FlaskBB Form Hooks
 
 
 .. autofunction:: flaskbb_form_new_post_save
 .. autofunction:: flaskbb_form_new_post_save
 .. autofunction:: flaskbb_form_new_post
 .. autofunction:: flaskbb_form_new_post
-
 .. autofunction:: flaskbb_form_new_topic
 .. autofunction:: flaskbb_form_new_topic
 .. autofunction:: flaskbb_form_new_topic_save
 .. autofunction:: flaskbb_form_new_topic_save
 
 

+ 32 - 0
docs/registration.rst

@@ -0,0 +1,32 @@
+.. _registration:
+
+Registration
+============
+
+These interfaces and implementations control the user registration flow in
+FlaskBB.
+
+Registration Interfaces
+-----------------------
+
+.. module:: flaskbb.core.auth.registration
+
+.. autoclass:: UserRegistrationInfo
+
+.. autoclass:: UserValidator
+    :members:
+
+.. autoclass:: UserRegistrationService
+    :members:
+
+
+Registration Provided Implementations
+-------------------------------------
+
+.. module:: flaskbb.auth.services.registration
+
+.. autoclass:: UsernameRequirements
+.. autoclass:: UsernameValidator
+.. autoclass:: UsernameUniquenessValidator
+.. autoclass:: EmailUniquenessValidator
+.. autoclass:: RegistrationService

+ 20 - 0
docs/tokens.rst

@@ -0,0 +1,20 @@
+.. _tokens:
+
+Tokens
+======
+
+.. module:: flaskbb.core.tokens
+
+.. autoclass:: TokenActions
+    :members:
+
+.. autoclass:: Token
+
+.. autoclass:: TokenSerializer
+    :members:
+
+.. autoclass:: TokenVerifier
+    :members:
+
+.. autoexception:: TokenError
+    :members:

+ 4 - 0
flaskbb/auth/services/activation.py

@@ -17,6 +17,10 @@ from ...email import send_activation_token
 
 
 
 
 class AccountActivator(_AccountActivator):
 class AccountActivator(_AccountActivator):
+    """
+    Default account activator for FlaskBB, handles the activation
+    process through email.
+    """
 
 
     def __init__(self, token_serializer, users):
     def __init__(self, token_serializer, users):
         self.token_serializer = token_serializer
         self.token_serializer = token_serializer

+ 36 - 1
flaskbb/auth/services/authentication.py

@@ -12,9 +12,10 @@
 import logging
 import logging
 from datetime import datetime
 from datetime import datetime
 
 
+from pytz import UTC
+
 import attr
 import attr
 from flask_babelplus import gettext as _
 from flask_babelplus import gettext as _
-from pytz import UTC
 from werkzeug import check_password_hash
 from werkzeug import check_password_hash
 
 
 from ...core.auth.authentication import (AuthenticationFailureHandler,
 from ...core.auth.authentication import (AuthenticationFailureHandler,
@@ -31,11 +32,20 @@ logger = logging.getLogger(__name__)
 
 
 @attr.s(frozen=True)
 @attr.s(frozen=True)
 class FailedLoginConfiguration(object):
 class FailedLoginConfiguration(object):
+    """
+    Used to configure how many failed logins are accepted until an account
+    is temporarily locked out and how long to temporarily lock the account
+    out for.
+    """
     limit = attr.ib()
     limit = attr.ib()
     lockout_window = attr.ib()
     lockout_window = attr.ib()
 
 
 
 
 class BlockTooManyFailedLogins(AuthenticationProvider):
 class BlockTooManyFailedLogins(AuthenticationProvider):
+    """
+    Pre authentication check to block a login from an account that has too
+    many failed login attempts in place.
+    """
 
 
     def __init__(self, configuration):
     def __init__(self, configuration):
         self.configuration = configuration
         self.configuration = configuration
@@ -65,6 +75,15 @@ class BlockTooManyFailedLogins(AuthenticationProvider):
 
 
 
 
 class DefaultFlaskBBAuthProvider(AuthenticationProvider):
 class DefaultFlaskBBAuthProvider(AuthenticationProvider):
+    """
+    This is the default username/email and password authentication checker,
+    locates the user based on the identifer passed -- either username or email
+    -- and compares the supplied password to the hash connected to the matching
+    user (if any).
+
+    Offers protection against timing attacks that would rely on the difference
+    in response time from not matching a password hash.
+    """
 
 
     def authenticate(self, identifier, secret):
     def authenticate(self, identifier, secret):
         user = User.query.filter(
         user = User.query.filter(
@@ -81,6 +100,10 @@ class DefaultFlaskBBAuthProvider(AuthenticationProvider):
 
 
 
 
 class MarkFailedLogin(AuthenticationFailureHandler):
 class MarkFailedLogin(AuthenticationFailureHandler):
+    """
+    Failure handler that marks the login attempt on the user and sets the
+    last failed date when it happened.
+    """
 
 
     def handle_authentication_failure(self, identifier):
     def handle_authentication_failure(self, identifier):
         user = User.query.filter(
         user = User.query.filter(
@@ -93,6 +116,10 @@ class MarkFailedLogin(AuthenticationFailureHandler):
 
 
 
 
 class BlockUnactivatedUser(PostAuthenticationHandler):
 class BlockUnactivatedUser(PostAuthenticationHandler):
+    """
+    Post auth handler that will block a user that has managed to pass the
+    authentication check but has not actually activated their account yet.
+    """
 
 
     def handle_post_auth(self, user):
     def handle_post_auth(self, user):
         if not user.activated:  # pragma: no branch
         if not user.activated:  # pragma: no branch
@@ -106,12 +133,20 @@ class BlockUnactivatedUser(PostAuthenticationHandler):
 
 
 
 
 class ClearFailedLogins(PostAuthenticationHandler):
 class ClearFailedLogins(PostAuthenticationHandler):
+    """
+    Post auth handler that clears all failed login attempts from a user's
+    account.
+    """
 
 
     def handle_post_auth(self, user):
     def handle_post_auth(self, user):
         user.login_attempts = 0
         user.login_attempts = 0
 
 
 
 
 class PluginAuthenticationManager(AuthenticationManager):
 class PluginAuthenticationManager(AuthenticationManager):
+    """
+    Authentication manager relying on plugin hooks to manage the authentication
+    process. This is the default authentication manager for FlaskBB.
+    """
 
 
     def __init__(self, plugin_manager, session):
     def __init__(self, plugin_manager, session):
         self.plugin_manager = plugin_manager
         self.plugin_manager = plugin_manager

+ 6 - 0
flaskbb/auth/services/password.py

@@ -17,6 +17,10 @@ from ...email import send_reset_token
 
 
 
 
 class ResetPasswordService(_ResetPasswordService):
 class ResetPasswordService(_ResetPasswordService):
+    """
+    Default password reset handler for FlaskBB, manages the process through
+    email.
+    """
 
 
     def __init__(self, token_serializer, users, token_verifiers):
     def __init__(self, token_serializer, users, token_verifiers):
         self.token_serializer = token_serializer
         self.token_serializer = token_serializer
@@ -24,6 +28,7 @@ class ResetPasswordService(_ResetPasswordService):
         self.token_verifiers = token_verifiers
         self.token_verifiers = token_verifiers
 
 
     def initiate_password_reset(self, email):
     def initiate_password_reset(self, email):
+
         user = self.users.query.filter_by(email=email).first()
         user = self.users.query.filter_by(email=email).first()
 
 
         if user is None:
         if user is None:
@@ -38,6 +43,7 @@ class ResetPasswordService(_ResetPasswordService):
         )
         )
 
 
     def reset_password(self, token, email, new_password):
     def reset_password(self, token, email, new_password):
+
         token = self.token_serializer.loads(token)
         token = self.token_serializer.loads(token)
         if token.operation != TokenActions.RESET_PASSWORD:
         if token.operation != TokenActions.RESET_PASSWORD:
             raise TokenError.invalid()
             raise TokenError.invalid()

+ 16 - 0
flaskbb/auth/services/reauthentication.py

@@ -24,6 +24,10 @@ logger = logging.getLogger(__name__)
 
 
 
 
 class DefaultFlaskBBReauthProvider(ReauthenticateProvider):
 class DefaultFlaskBBReauthProvider(ReauthenticateProvider):
+    """
+    This is the default reauth provider in FlaskBB, it compares the provided
+    password against the current user's hashed password.
+    """
 
 
     def reauthenticate(self, user, secret):
     def reauthenticate(self, user, secret):
         if check_password_hash(user.password, secret):  # pragma: no branch
         if check_password_hash(user.password, secret):  # pragma: no branch
@@ -31,12 +35,20 @@ class DefaultFlaskBBReauthProvider(ReauthenticateProvider):
 
 
 
 
 class ClearFailedLoginsOnReauth(PostReauthenticateHandler):
 class ClearFailedLoginsOnReauth(PostReauthenticateHandler):
+    """
+    Handler that clears failed login attempts after a successful
+    reauthentication.
+    """
 
 
     def handle_post_reauth(self, user):
     def handle_post_reauth(self, user):
         user.login_attempts = 0
         user.login_attempts = 0
 
 
 
 
 class MarkFailedReauth(ReauthenticateFailureHandler):
 class MarkFailedReauth(ReauthenticateFailureHandler):
+    """
+    Failure handler that marks the failed reauth attempt as a failed login
+    and when it occurred.
+    """
 
 
     def handle_reauth_failure(self, user):
     def handle_reauth_failure(self, user):
         user.login_attempts += 1
         user.login_attempts += 1
@@ -44,6 +56,10 @@ class MarkFailedReauth(ReauthenticateFailureHandler):
 
 
 
 
 class PluginReauthenticationManager(ReauthenticateManager):
 class PluginReauthenticationManager(ReauthenticateManager):
+    """
+    Default reauthentication manager for FlaskBB, it relies on plugin hooks
+    to manage the reauthentication flow.
+    """
 
 
     def __init__(self, plugin_manager, session):
     def __init__(self, plugin_manager, session):
         self.plugin_manager = plugin_manager
         self.plugin_manager = plugin_manager

+ 25 - 0
flaskbb/auth/services/registration.py

@@ -24,12 +24,20 @@ __all__ = (
 
 
 @attr.s(hash=False, repr=True, frozen=True, cmp=False)
 @attr.s(hash=False, repr=True, frozen=True, cmp=False)
 class UsernameRequirements(object):
 class UsernameRequirements(object):
+    """
+    Configuration for username requirements, minimum and maximum length
+    and disallowed names.
+    """
     min = attr.ib()
     min = attr.ib()
     max = attr.ib()
     max = attr.ib()
     blacklist = attr.ib()
     blacklist = attr.ib()
 
 
 
 
 class UsernameValidator(UserValidator):
 class UsernameValidator(UserValidator):
+    """
+    Validates that the username for the registering user meets the minimum
+    requirements (appropriate length, not a forbidden name).
+    """
 
 
     def __init__(self, requirements):
     def __init__(self, requirements):
         self._requirements = requirements
         self._requirements = requirements
@@ -58,6 +66,9 @@ class UsernameValidator(UserValidator):
 
 
 
 
 class UsernameUniquenessValidator(UserValidator):
 class UsernameUniquenessValidator(UserValidator):
+    """
+    Validates that the provided username is unique in the application.
+    """
 
 
     def __init__(self, users):
     def __init__(self, users):
         self.users = users
         self.users = users
@@ -77,6 +88,9 @@ class UsernameUniquenessValidator(UserValidator):
 
 
 
 
 class EmailUniquenessValidator(UserValidator):
 class EmailUniquenessValidator(UserValidator):
+    """
+    Validates that the provided email is unique in the application.
+    """
 
 
     def __init__(self, users):
     def __init__(self, users):
         self.users = users
         self.users = users
@@ -93,6 +107,17 @@ class EmailUniquenessValidator(UserValidator):
 
 
 
 
 class RegistrationService(UserRegistrationService):
 class RegistrationService(UserRegistrationService):
+    """
+    Default registration service for FlaskBB, runs the registration information
+    against the provided validators and if it passes, creates the user.
+
+    If any of the provided
+    :class:`UserValidators<flaskbb.core.auth.registration.UserValidator>`
+    raise a :class:`ValidationError<flaskbb.core.exceptions.ValidationError>`
+    then the register method will raise a
+    :class:`StopValidation<flaskbb.core.exceptions.StopValidation>` with all
+    reasons why the registration was prevented.
+    """
 
 
     def __init__(self, validators, user_repo):
     def __init__(self, validators, user_repo):
         self.validators = validators
         self.validators = validators

+ 33 - 0
flaskbb/core/auth/activation.py

@@ -15,10 +15,43 @@ from ..._compat import ABC
 
 
 
 
 class AccountActivator(ABC):
 class AccountActivator(ABC):
+    """
+    Interface for managing account activation in installations that require
+    a user to activate their account before using it.
+    """
+
     @abstractmethod
     @abstractmethod
     def initiate_account_activation(self, user):
     def initiate_account_activation(self, user):
+        """
+        This method is abstract.
+
+        Used to extend an offer of activation to the user. This may take any
+        form, but is recommended to take the form of a permanent communication
+        such as email.
+
+        This method may raise :class:`ValidationError<flaskbb.core.exceptions.ValidationError>`
+        to communicate a failure when creating the token for the user to
+        activate their account with (such as when a user has requested a token
+        be sent to an email that is not registered in the installation or
+        the account associated with that email has already been activated).
+
+        :param user: The user that the activation request applies to.
+        :type user: :class:`User<flaskbb.user.models.User>`
+        """
         pass
         pass
 
 
     @abstractmethod
     @abstractmethod
     def activate_account(self, token):
     def activate_account(self, token):
+        """
+        This method is abstract.
+
+        Used to handle the actual activation of an account. The token
+        passed in is the serialized token communicated to the user to use
+        for activation. This method may raise
+        :class:`TokenError<flaskbb.core.tokens.TokenError>` or
+        :class:`ValidationError<flaskbb.core.exceptions.ValidationError>`
+        to communicate failures when parsing or consuming the token.
+
+        :param str token: The raw serialized token sent to the user
+        """
         pass
         pass

+ 178 - 3
flaskbb/core/auth/authentication.py

@@ -16,6 +16,8 @@ class StopAuthentication(BaseFlaskBBError):
     """
     """
     Used by Authentication providers to halt any further
     Used by Authentication providers to halt any further
     attempts to authenticate a user.
     attempts to authenticate a user.
+
+    :param reason str: The reason why authentication was halted
     """
     """
 
 
     def __init__(self, reason):
     def __init__(self, reason):
@@ -26,6 +28,8 @@ class StopAuthentication(BaseFlaskBBError):
 class ForceLogout(BaseFlaskBBError):
 class ForceLogout(BaseFlaskBBError):
     """
     """
     Used to forcefully log a user out.
     Used to forcefully log a user out.
+
+    :param reason str: The reason why the user was force logged out
     """
     """
 
 
     def __init__(self, reason):
     def __init__(self, reason):
@@ -34,22 +38,69 @@ class ForceLogout(BaseFlaskBBError):
 
 
 
 
 class AuthenticationManager(ABC):
 class AuthenticationManager(ABC):
+    """
+    Used to handle the authentication process. A default is implemented,
+    however this interface is provided in case alternative flows are needed.
+
+    If a user successfully passes through the entire authentication process,
+    then it should be returned to the caller.
+    """
 
 
     @abstractmethod
     @abstractmethod
     def authenticate(self, identifier, secret):
     def authenticate(self, identifier, secret):
         """
         """
-        Manages the entire authentication process in FlaskBB.
+        This method is abstract.
+
+        :param str identifier: An identifer for the user, typically this is
+            either a username or an email.
+        :param str secret: A secret to verify the user is who they say they are
+        :returns: A fully authenticated but not yet logged in user
+        :rtype: :class:`User<flaskbb.user.models.User>`
 
 
-        If a user is successfully authenticated, it is returned
-        from this method.
         """
         """
         pass
         pass
 
 
 
 
 class AuthenticationProvider(ABC):
 class AuthenticationProvider(ABC):
+    """
+    Used to provide an authentication service for FlaskBB.
+
+    For example, an implementer may choose to use LDAP as an authentication
+    source::
+
+        class LDAPAuthenticationProvider(AuthenticationProvider):
+            def __init__(self, ldap_client):
+                self.ldap_client = ldap_client
+
+            def authenticate(self, identifier, secret):
+                user_dn = "uid={},ou=flaskbb,ou=org".format(identifier)
+                try:
+                    self.ldap_client.bind_user(user_dn, secret)
+                    return User.query.join(
+                            UserLDAP
+                        ).filter(
+                            UserLDAP.dn==user_dn
+                        ).with_entities(User).one()
+                except Exception:
+                    return None
+
+    During an authentication process, a provider may raise a
+    :class:`StopAuthentication<flaskbb.core.auth.authentication.StopAuthentication>`
+    exception to completely, but safely halt the process. This is most useful
+    when multiple providers are being used.
+    """
 
 
     @abstractmethod
     @abstractmethod
     def authenticate(self, identifier, secret):
     def authenticate(self, identifier, secret):
+        """
+        This method is abstract.
+
+        :param str identifier: An identifer for the user, typically this is
+            either a username or an email.
+        :param str secret: A secret to verify the user is who they say they are
+        :returns: An authenticated user.
+        :rtype: :class:`User<flaskbb.user.models.User>`
+        """
         pass
         pass
 
 
     def __call__(self, identifier, secret):
     def __call__(self, identifier, secret):
@@ -57,9 +108,26 @@ class AuthenticationProvider(ABC):
 
 
 
 
 class AuthenticationFailureHandler(ABC):
 class AuthenticationFailureHandler(ABC):
+    """
+    Used to post process authentication failures, such as no provider returning
+    a user or a provider raising
+    :class:`StopAuthentication<flaskbb.core.auth.authentication.StopAuthentication>`.
+
+    Postprocessing may take many forms, such as incrementing the login attempts
+    locking an account if too many attempts are made, forcing a reauth if
+    the user is currently authenticated in a different session, etc.
+
+    Failure handlers should not return a value as it will not be considered.
+    """
 
 
     @abstractmethod
     @abstractmethod
     def handle_authentication_failure(self, identifier):
     def handle_authentication_failure(self, identifier):
+        """
+        This method is abstract.
+
+        :param str identifier: An identifer for the user, typically this is
+            either a username or an email.
+        """
         pass
         pass
 
 
     def __call__(self, identifier):
     def __call__(self, identifier):
@@ -67,9 +135,35 @@ class AuthenticationFailureHandler(ABC):
 
 
 
 
 class PostAuthenticationHandler(ABC):
 class PostAuthenticationHandler(ABC):
+    """
+    Used to post process authentication success. Post authentication handlers
+    recieve the user instance that was returned by the successful
+    authentication rather than the identifer.
+
+    Postprocessors may decide to preform actions such as flashing a message
+    to the user, clearing failed login attempts, etc.
+
+    Alternatively, a postprocessor can decide to fail the authentication
+    process anyways by raising
+    :class:`StopAuthentication<flaskbb.core.auth.authentication.StopAuthentication>`,
+    for example a user may successfully authenticate but has not yet activated
+    their account.
+
+    Cancelling a successful authentication will cause registered
+    :class:`AuthenticationFailureHandler<flaskbb.core.auth.authentication.AuthenticationFailureHandler>`
+    instances to be run.
+
+    Success handlers should not return a value as it will not be considered.
+    """
 
 
     @abstractmethod
     @abstractmethod
     def handle_post_auth(self, user):
     def handle_post_auth(self, user):
+        """
+        This method is abstact.
+
+        :param user: An authenticated but not yet logged in user
+        :type user: :class:`User<flaskbb.user.model.User>`
+        """
         pass
         pass
 
 
     def __call__(self, user):
     def __call__(self, user):
@@ -77,9 +171,23 @@ class PostAuthenticationHandler(ABC):
 
 
 
 
 class ReauthenticateManager(ABC):
 class ReauthenticateManager(ABC):
+    """
+    Used to handle the reauthentication process in FlaskBB. A default
+    implementation is provided, however this is interface exists in case
+    alternative flows are desired.
 
 
+    Unlike the AuthenticationManager, there is no need to return the user to
+    the caller.
+    """
     @abstractmethod
     @abstractmethod
     def reauthenticate(self, user, secret):
     def reauthenticate(self, user, secret):
+        """
+        This method is abstract.
+
+        :param user: The current user instance
+        :param str secret: The secret provided by the user
+        :type user: :class:`User<flaskbb.user.models.User>`
+        """
         pass
         pass
 
 
     def __call__(self, user, secret):
     def __call__(self, user, secret):
@@ -87,9 +195,50 @@ class ReauthenticateManager(ABC):
 
 
 
 
 class ReauthenticateProvider(ABC):
 class ReauthenticateProvider(ABC):
+    """
+    Used to reauthenticate a user that is already logged into the system,
+    for example when suspicious activity is detected in their session.
+
+    ReauthenticateProviders are similiar to
+    :class:`AuthenticationProvider<flaskbb.core.auth.authentication.AuthenticationProvider>`
+    except they receive a user instance rather than an identifer for a user.
+
+    A successful reauthentication should return True while failures should
+    return None in order to give other providers an attempt run.
+
+    If a ReauthenticateProvider determines that reauthentication should
+    immediately end, it may raise
+    :class:`StopAuthentication<flaskbb.core.auth.authentication.StopAuthentication>`
+    to safely end the process.
+
+
+    An example::
+
+        class LDAPReauthenticateProvider(ReauthenticateProvider):
+            def __init__(self, ldap_client):
+                self.ldap_client = ldap_client
+
+            def reauthenticate(self, user, secret):
+                user_dn = "uid={},ou=flaskbb,ou=org".format(user.username)
+                try:
+                    self.ldap_client.bind_user(user_dn, secret)
+                    return True
+                except Exception:
+                    return None
+
+    """
 
 
     @abstractmethod
     @abstractmethod
     def reauthenticate(self, user, secret):
     def reauthenticate(self, user, secret):
+        """
+        This method is abstract.
+
+        :param user: The current user instance
+        :param str secret: The secret provided by the user
+        :type user: :class:`User<flaskbb.user.models.User>`
+        :returns: True for a successful reauth, otherwise None
+        """
+
         pass
         pass
 
 
     def __call__(self, user, secret):
     def __call__(self, user, secret):
@@ -97,9 +246,21 @@ class ReauthenticateProvider(ABC):
 
 
 
 
 class ReauthenticateFailureHandler(ABC):
 class ReauthenticateFailureHandler(ABC):
+    """
+    Used to manager reauthentication failures in FlaskBB.
 
 
+    ReauthenticateFailureHandlers are similiar to
+    :class:`AuthenticationFailureHandler<flaskbb.core.auth.authentication.AuthenticationFailureHandler>`
+    except they receive the user instance rather than an indentifier for a user
+    """
     @abstractmethod
     @abstractmethod
     def handle_reauth_failure(self, user):
     def handle_reauth_failure(self, user):
+        """
+        This method is abstract.
+
+        :param user: The current user instance that failed the reauth attempt
+        :type user: :class:`User<flaskbb.user.models.User>`
+        """
         pass
         pass
 
 
     def __call__(self, user):
     def __call__(self, user):
@@ -107,9 +268,23 @@ class ReauthenticateFailureHandler(ABC):
 
 
 
 
 class PostReauthenticateHandler(ABC):
 class PostReauthenticateHandler(ABC):
+    """
+    Used to post process successful reauthentication attempts.
+
+    PostAuthenticationHandlers are similar to
+    :class:`PostAuthenticationHandler<flaskbb.core.auth.authentication.PostAuthenticationHandler>`,
+    including their ability to cancel a successful attempt by raising
+    :class:`StopAuthentication<flaskbb.core.auth.authentication.StopAuthentication>`
+    """
 
 
     @abstractmethod
     @abstractmethod
     def handle_post_reauth(self, user):
     def handle_post_reauth(self, user):
+        """
+        This method is abstract.
+
+        :param user: The current user instance that passed the reauth attempt
+        :type user: :class:`User<flaskbb.user.models.User>`
+        """
         pass
         pass
 
 
     def __call__(self, user):
     def __call__(self, user):

+ 31 - 0
flaskbb/core/auth/password.py

@@ -15,11 +15,42 @@ from ..._compat import ABC
 
 
 
 
 class ResetPasswordService(ABC):
 class ResetPasswordService(ABC):
+    """
+    Interface for managing the password reset experience in FlaskBB.
+    """
 
 
     @abstractmethod
     @abstractmethod
     def initiate_password_reset(self, email):
     def initiate_password_reset(self, email):
+        """
+        This method is abstract.
+
+        Used to send a password reset token to a user.
+
+        This method may raise a
+        :class:`ValidationError<flaskbb.core.exceptions.ValidationError>`
+        when generating the token, such as when the user requests a reset token
+        be sent to an email that isn't registered in the application.
+
+        :param str email: The email to send the reset request to.
+        """
         pass
         pass
 
 
     @abstractmethod
     @abstractmethod
     def reset_password(self, token, email, new_password):
     def reset_password(self, token, email, new_password):
+        """
+        This method is abstract.
+
+        Used to process a password reset token and handle resetting the user's
+        password to the newly desired one. The token passed to this message
+        is the raw, serialized token sent to the user.
+
+        This method may raise
+        :class:`TokenError<flaskbb.core.tokens.TokenError>` or
+        :class:`ValidationError<flaskbb.core.exceptions.ValidationError>`
+        to communicate failures when parsing or consuming the token.
+
+        :param str token: The raw serialized token sent to the user
+        :param str email: The email entered by the user at password reset
+        :param str new_password: The new password to assign to the user
+        """
         pass
         pass

+ 26 - 4
flaskbb/core/auth/registration.py

@@ -19,6 +19,10 @@ from ..._compat import ABC
 
 
 @attr.s(hash=True, cmp=False, repr=True, frozen=True)
 @attr.s(hash=True, cmp=False, repr=True, frozen=True)
 class UserRegistrationInfo(object):
 class UserRegistrationInfo(object):
+    """
+    User registration object, contains all relevant information for validating
+    and creating a new user.
+    """
     username = attr.ib()
     username = attr.ib()
     password = attr.ib(repr=False)
     password = attr.ib(repr=False)
     email = attr.ib()
     email = attr.ib()
@@ -27,20 +31,38 @@ class UserRegistrationInfo(object):
 
 
 
 
 class UserValidator(ABC):
 class UserValidator(ABC):
+    """
+    Used to validate user registrations and stop the registration process
+    by raising a
+    :class:`ValidationError<flaskbb.core.exceptions.ValidationError>`.
+    """
+
     @abstractmethod
     @abstractmethod
     def validate(self, user_info):
     def validate(self, user_info):
         """
         """
-        Used to check if a user should be allowed to register.
-        Should raise ValidationError if the user should not be
-        allowed to register.
+        This method is abstract.
+
+        :param user_info: The provided registration information.
+        :type user_info: :class:`UserRegistrationInfo<flaskbb.core.auth.registration.UserRegistrationInfo>`
         """
         """
-        return True
 
 
     def __call__(self, user_info):
     def __call__(self, user_info):
         return self.validate(user_info)
         return self.validate(user_info)
 
 
 
 
 class UserRegistrationService(ABC):
 class UserRegistrationService(ABC):
+    """
+    Used to manage the registration process. A default implementation is
+    provided however, this interface is provided in case alternative
+    flows are needed.
+    """
+
     @abstractmethod
     @abstractmethod
     def register(self, user_info):
     def register(self, user_info):
+        """
+        This method is abstract.
+
+        :param user_info: The provided user registration information.
+        :type user_info: :class:`UserRegistrationInfo<flaskbb.core.auth.registration.UserRegistrationInfo>`
+        """
         pass
         pass

+ 13 - 4
flaskbb/core/exceptions.py

@@ -11,14 +11,23 @@
     :license: BSD, see LICENSE for more details
     :license: BSD, see LICENSE for more details
 """
 """
 
 
+
 class BaseFlaskBBError(Exception):
 class BaseFlaskBBError(Exception):
-    "Root exception for FlaskBB"
+    """
+    Root exception for FlaskBB.
+    """
 
 
 
 
 class ValidationError(BaseFlaskBBError):
 class ValidationError(BaseFlaskBBError):
     """
     """
     Used to signal validation errors for things such as
     Used to signal validation errors for things such as
     token verification, user registration, etc.
     token verification, user registration, etc.
+
+    :param str attribute: The attribute the validation error applies to,
+        if the validation error applies to multiple attributes or to
+        the entire object, this should be set to None
+    :param str reason: Why the attribute, collection of attributes or object
+        is invalid.
     """
     """
 
 
     def __init__(self, attribute, reason):
     def __init__(self, attribute, reason):
@@ -33,11 +42,11 @@ class StopValidation(BaseFlaskBBError):
     validation should end immediately and no further
     validation should end immediately and no further
     processing should be done.
     processing should be done.
 
 
-    The reasons passed should be an iterable of
-    tuples consisting of `(attribute, reason)`
-
     Can also be used to communicate all errors
     Can also be used to communicate all errors
     raised during a validation run.
     raised during a validation run.
+
+    :param reasons: A sequence of `(attribute, reason)` pairs explaining
+        why the object is invalid.
     """
     """
 
 
     def __init__(self, reasons):
     def __init__(self, reasons):

+ 53 - 7
flaskbb/core/tokens.py

@@ -23,6 +23,8 @@ class TokenError(BaseFlaskBBError):
     Raised when there is an issue with deserializing
     Raised when there is an issue with deserializing
     a token. Has helper classmethods to ensure
     a token. Has helper classmethods to ensure
     consistent verbiage.
     consistent verbiage.
+
+    :param str reason: An explanation of why the token is invalid
     """
     """
 
 
     def __init__(self, reason):
     def __init__(self, reason):
@@ -31,10 +33,19 @@ class TokenError(BaseFlaskBBError):
 
 
     @classmethod
     @classmethod
     def invalid(cls):
     def invalid(cls):
+        """
+        Used to raise an exception about a token that is invalid
+        due to being signed incorrectly, has been tampered with,
+        is unparsable or contains an inappropriate action.
+        """
         return cls(_('Token is invalid'))
         return cls(_('Token is invalid'))
 
 
     @classmethod
     @classmethod
     def expired(cls):
     def expired(cls):
+        """
+        Used to raise an exception about a token that has expired and is
+        no longer usable.
+        """
         return cls(_('Token is expired'))
         return cls(_('Token is expired'))
 
 
     # in theory this would never be raised
     # in theory this would never be raised
@@ -48,33 +59,60 @@ class TokenError(BaseFlaskBBError):
 # holder for token actions
 # holder for token actions
 # not an enum so plugins can add to it
 # not an enum so plugins can add to it
 class TokenActions:
 class TokenActions:
+    """
+    Collection of token actions.
+
+    .. note::
+        This is just a class rather than an enum because enums cannot be
+        extended at runtime which would limit the number of token actions
+        to the ones implemented by FlaskBB itself and block extension of
+        tokens by plugins.
+    """
     RESET_PASSWORD = 'reset_password'
     RESET_PASSWORD = 'reset_password'
     ACTIVATE_ACCOUNT = 'activate_account'
     ACTIVATE_ACCOUNT = 'activate_account'
 
 
 
 
 @attr.s(frozen=True, cmp=True, hash=True)
 @attr.s(frozen=True, cmp=True, hash=True)
 class Token(object):
 class Token(object):
+    """
+    :param int user_id:
+    :param str operation: An operation taken from
+        :class:`TokenActions<flaskbb.core.tokens.TokenActions>`
+    """
     user_id = attr.ib()
     user_id = attr.ib()
     operation = attr.ib()
     operation = attr.ib()
 
 
 
 
 class TokenSerializer(ABC):
 class TokenSerializer(ABC):
     """
     """
-    Interface for token serializers.
 
 
-    dumps must accept a Token instance and produce
-    a JWT
-
-    loads must accept a string representation of
-    a JWT and produce a token instance
     """
     """
 
 
     @abstractmethod
     @abstractmethod
     def dumps(self, token):
     def dumps(self, token):
+        """
+        This method is abstract.
+
+        Used to transform a token into a string representation of it.
+
+        :param token:
+        :type token: :class:`Token<flaskbb.core.tokens.Token>`
+        :returns str:
+        """
         pass
         pass
 
 
     @abstractmethod
     @abstractmethod
     def loads(self, raw_token):
     def loads(self, raw_token):
+        """
+        This method is abstract
+
+        Used to transform a string representation of a token into an
+        actual :class:`Token<flaskbb.core.tokens.Token>` instance
+
+        :param str raw_token:
+        :returns token: The parsed token
+        :rtype: :class:`Token<flaskbb.core.tokens.Token`>
+        """
         pass
         pass
 
 
 
 
@@ -84,12 +122,20 @@ class TokenVerifier(ABC):
     deserialization, such as an email matching the
     deserialization, such as an email matching the
     user id in the provided token.
     user id in the provided token.
 
 
-    Should raise a flaskbb.core.exceptions.ValidationError
+    Should raise a
+    :class:`ValidationError<flaskbb.core.exceptions.ValidationError>`
     if verification fails.
     if verification fails.
     """
     """
 
 
     @abstractmethod
     @abstractmethod
     def verify_token(self, token, **kwargs):
     def verify_token(self, token, **kwargs):
+        """
+        This method is abstract.
+
+        :param token: The parsed token to verify
+        :param kwargs: Arbitrary context for validation of the token
+        :type token: :class:`Token<flaskbb.core.tokens.Token>`
+        """
         pass
         pass
 
 
     def __call__(self, token, **kwargs):
     def __call__(self, token, **kwargs):

+ 18 - 0
flaskbb/plugins/spec.py

@@ -166,6 +166,9 @@ def flaskbb_authenticate(identifier, secret):
     authentication is tried last to give others an attempt to
     authentication is tried last to give others an attempt to
     authenticate the user instead.
     authenticate the user instead.
 
 
+    See also:
+    :class:`AuthenticationProvider<flaskbb.core.auth.AuthenticationProvider>`
+
     Example of alternative auth::
     Example of alternative auth::
 
 
         def ldap_auth(identifier, secret):
         def ldap_auth(identifier, secret):
@@ -222,6 +225,9 @@ def flaskbb_post_authenticate(user):
     :class:`flaskbb.core.exceptions.StopAuthentication`
     :class:`flaskbb.core.exceptions.StopAuthentication`
     and include why the login was prevented.
     and include why the login was prevented.
 
 
+    See also:
+    :class:`PostAuthenticationHandler<flaskbb.core.auth.PostAuthenticationHandler>`
+
     Example::
     Example::
 
 
         def post_auth(user):
         def post_auth(user):
@@ -243,6 +249,9 @@ def flaskbb_authentication_failed(identifier):
     :class:`flaskbb.core.exceptions.StopAuthentication`
     :class:`flaskbb.core.exceptions.StopAuthentication`
     is raised during the login process.
     is raised during the login process.
 
 
+    See also:
+    :class:`AuthenticationFailureHandler<flaskbb.core.auth.AuthenticationFailureHandler>`
+
     Example::
     Example::
 
 
         def mark_failed_logins(identifier):
         def mark_failed_logins(identifier):
@@ -277,6 +286,9 @@ def flaskbb_reauth_attempt(user, secret):
     If a hook decides that a reauthenticate attempt should
     If a hook decides that a reauthenticate attempt should
     cease, it may raise StopAuthentication.
     cease, it may raise StopAuthentication.
 
 
+    See also:
+    :class:`ReauthenticateProvider<flaskbb.core.auth.ReauthenticateProvider>`
+
     Example of checking secret or passing to the next implementer::
     Example of checking secret or passing to the next implementer::
 
 
         @impl
         @impl
@@ -303,6 +315,9 @@ def flaskbb_post_reauth(user):
     may still force a reauth to fail by raising StopAuthentication.
     may still force a reauth to fail by raising StopAuthentication.
 
 
     Results from these hooks are not considered.
     Results from these hooks are not considered.
+
+    See also:
+    :class:`PostReauthenticateHandler<flaskbb.core.auth.PostAuthenticationHandler>`
     """
     """
 
 
 @spec
 @spec
@@ -316,6 +331,9 @@ def flaskbb_reauth_failed(user):
     If an implementation raises ForceLogout it should register
     If an implementation raises ForceLogout it should register
     itself as trylast to give other reauth failed handlers an
     itself as trylast to give other reauth failed handlers an
     opprotunity to run first.
     opprotunity to run first.
+
+    See also:
+    :class:`ReauthenticateFailureHandler<flaskbb.core.auth.ReauthenticateFailureHandler>`
     """
     """
 
 
 # Form hooks
 # Form hooks