.. _custom-authenticators:

**************
Authenticators
**************

Unfortunately, there is no one standard that defines how authentication nor
do vendors fully implement the current standards for authentication.
Therefore the Snippet Framework supports the ability to customize
your authentication flow. As previously mentioned there are five out-of-the-box
authenticators that can be leveraged. These authenticators are described under the
:ref:`rest_authentication` section.

When building a custom authenticator it is recommended to start with one
of the above authenticators and modify the code accordingly.


Authenticator ABC
=================

All the authenticators must inherit from the same ABC (Abstract Base Class) and thus
all have similar structure. Below is an implementation of an ``Authenticator`` class that
provides no authentication at all. This shows all the required methods that must be defined
in a brand new authenticator.

.. code-block:: python
    :linenos:

    from silo.auth import add_auth, Authenticator

    @add_auth
    class AuthNone(Authenticator):
        """AuthNone encapsulates the authentication lifecycle for no authentication.
        :param Authenticator Authenticator: Parent ABC Class
        :param silo.apps.collection.credentials.Credential credentials: credentials
            retrieved from em7/UCF
        """

        def __init__(self, credentials, **kwargs):
            """Constructor method"""
            Authenticator.__init__(self, credentials)

        def check_if_authenticated(self):
            """
            Required for ABC implementation. Since there is nothing to authenticate against
            we will always return True.
            :return: True
            :rtype: boolean
            """
            return True

        def check_result(self, result):
            """
            Provides a check on the returned response from the REST request that was made.
            :param response result: Response object after making a REST call
            :return: True
            :rtype: boolean
            """
            return True

        def authenticate(self):
            """
            Authenticate is a no-op for NoneAuth
            :return: True
            :rtype: boolean
            """
            return True

        def modify_request(self, res):
            """Function to apply authorization implementation for request session
            :param session session: Request to be updated with credentials
            :return: True
            :rtype: boolean
            """
            res.auth = None
            return True

        @staticmethod
        def build(credentials, **kwargs):
            """Build method to be called by get_authmethod in low_code"""
            return AuthNone(credentials, **kwargs)

        @staticmethod
        def get_name():
            return "AuthNone"

        @staticmethod
        def get_desc():
            return "No Authentication for HTTP"

The most important functions to understand are as follows:

* ``authenticate()`` – This function is called before making any API request.
  Its job is to make sure that authentication has occurred. It is present in
  all authenticators and doesn’t really do anything in AuthBasic though
  becomes very important for the token based authentication schemes. In those
  cases its function is to make the API request to the token server and then
  stores that token for later use by ``modify_request()``.
* ``modify_request()`` - This function is called after ``authenticate()`` has
  been performed and its job is to modify the session with the authentication
  information. This function has access to the session and thus can add the
  token to the headers e.g. in AuthToken. However, it can be easily modified
  e.g. to place the token in a query parameter.
* ``get_name()`` - This function helps define the look up name for the
  authenticator. The returned value in this function will be the same as
  the credential's ``Authenticator Override`` value.

.. note::

    The out-of-the-box http step uses the **Requests** library. Request sessions
    must be understood as the parameters of a session is what normally needs
    to be modifed by ``modify_request()``. See `Request Sessions
    <https://requests.readthedocs.io/en/latest/api/#request-sessions>`_

Logging
=======

The Authenticator provides a logger with which to debug authentication issues. This logger
provides credential masking if used in conjunction with the authenticator's credential filter
object.

.. code-block:: python

    from silo.auth import auth_logger

In order for these debug logs to be visible, the aligned credential must have its
``Logging Debug`` value set to ``True``.

.. warning::

    Turning on debug runs the risk of recording a secret to the logs.


Credential Masking/Filtering
----------------------------

Credentials can be masked or filter from the ``auth_logger`` output. Suppose
we had a ``AuthBasic`` whose password was *lowcodepassword*. When logging is
enabled then our logs that would have had *lowcodepassword* is replaced with
``*****``. Again, the secret is replaced with five asterix.

.. code-block:: python
    :caption: Adding a secret for masking
    :emphasize-lines: 9

    from silo.auth import add_auth, Authenticator

    @add_auth
    class AuthExampleCredFilter(Authenticator):

        def __init__(self, credentials, **kwargs):
            """Constructor method"""
            Authenticator.__init__(self, credentials)
            self.cred_filter.update_secret("secret", "lowcodepassword")

.. code-block:: python
    :caption: docstring for update secret

    def update_secret(self, key, secret):
        """Function to add/modify an additional secret for log removal

        This function will transform the credentials into unicode
        and store its entries as secret items in self._secrets.

        :param str key: key for secret into dict
        :param str secret: actual secret text to remove from log
        """

.. note::
    The key parameter is used for subsequent updates to a secret that must be
    masked. For example, a token that will be refreshed will need to be
    updated and the previous secret will no longer need to be masked.

Authenticator Override
======================

Before we dive into developing new or augmenting an existing authenticator, we
need to understand how to reuse the *Low-code tools: rest v100* credential
type in order to point to a different authenticator while stilling being able
to re-use existing credential fields.

Below is an image of the *Low-code tools: rest v100* credential type. On the
bottom right of the image, is a field called ``Authenticator Override``.

.. image:: ../_static/authentication/images/authenticator_override.png

The contents of this field must match the output of the ``get_name()``
function. Below is a shortened example of an authenticator. Its name is
``AuthCustom``. If we wanted to use this new authenticator then we would
need to update the credential field, ``Authenticator Override``, to be
``AuthCustom``.

.. code-block:: python

    @add_auth
    class AuthCustom(Authenticator):
        # There are more methods not shown

        @staticmethod
        def get_name():
            return "AuthCustom"

In the following examples, after we create our authenticator we will need to
update the ``Authenticator Override`` field to match our ``get_name()`` value.


Credential Fields
=================

When using Authenticator Override, your custom authenticator has access to
the following fields from the `credentials.fields` dictionary. When creating
a custom authenticator, we will be reusing these fields to implement new
authentication flows.

.. note::
    Not all fields are view-able/visible from the Credential UI. In the table
    below, **Visibility (AuthType)** denotes when a field will be visible given
    an Authentiction Type.

.. list-table:: Available Credential Fields
   :header-rows: 1

   * - Display Name
     - credentials.fields Name
     - Visibility (AuthType)
     - Type
     - Default Value
     - Description
   * - Authentication Type
     - ``authType``
     - All
     - string
     - ``AuthNone``
     - Fixed string defining the authenticator to use i.e.
       ``AuthNone``, ``AuthBasic``, ``AuthToken``,
       ``AuthApiKey``, and ``AuthOAuth2``

   * - URL
     - ``url``
     - All
     - string
     - ""
     - URL that should be used to retrieve device data
       [http(s)://Host:Port/Path]

   * - Token Retrieval Endpoint
     - ``authEndpoint``
     - ``AuthToken``
     - string
     - ""
     - Fields indicates that the token should be retrieved from a different API

   * - Authorization Header
     - ``authHeader``
     - ``AuthApiKey``, ``AuthToken``
     - string
     - ``Authorization``
     - Where the authentication server expects the key/token from the
       request header.

   * - Token Key
     - ``tokenKey``
     - ``AuthToken``
     - string
     - ``access_token``
     - Key value in the JSON device response that contains the token
       value to extract. Example: ``{"token": "tokenvalue123"}``. Providing
       ``"token"`` would pull out tokenvalue123

   * - Bearer Token Format
     - ``tokenFormat``
     - ``AuthToken``
     - string
     - ``Bearer {}``
     - Formulate how the bearer token should be configured and sent.
       The typical format is ``"Bearer {}"`` but varies per API. ``{}``
       must be  included so that the token is inserted in that location.

   * - Username
     - ``username``
     - ``AuthBasic``, ``AuthToken``
     - string
     - ""
     - Username for authentication
   * - Password
     - ``password``
     - ``AuthBasic``, ``AuthToken``
     - string
     - ""
     - Password for authentication
   * - Proxy Settings
     - ``proxy``
     - All
     - boolean
     - ``False``
     - Toggle-able that allow users to input proxy settings
   * - Proxy Hostname/IP
     - ``proxyHost``
     - All
     - string
     - ""
     - hostname/IP of your proxy server (optional)
   * - Proxy Port
     - ``proxyPort``
     - All
     - number
     - ""
     - Port used by your proxy server
   * - Proxy User
     - ``proxyUser``
     - All
     - string
     - ""
     - Proxy user (optional)
   * - Proxy Password
     - ``proxyPasswd``
     - All
     - string
     - ""
     - Proxy password (optional)
   * - HTTP Header 1
     - ``httpHeader1``
     - ``AuthToken``, ``AuthApiKey``
     - string
     - ""
     - Optional HTTP header (e.g. ``X-Sample-Header:Sample Value``)
   * - HTTP Header 2
     - ``httpHeader2``
     - ``AuthToken``, ``AuthApiKey``
     - string
     - ""
     - Optional HTTP header (e.g. ``X-Sample-Header:Sample Value``)
   * - HTTP Headers (JSON input)
     - ``httpHeaders``
     - All
     - string
     - ""
     - Accepts additional header values in the form of JSON.
       Example: ``{"header1": "key1", "header2": "key2"}``

   * - SSL Peer Verify
     - ``sslverifypeer``
     - All
     - boolean
     - True
     - This option determines whether the authenticity of
       the peer's certificate should be verified. OFF means
       to have an un-secure connection.

   * - Authentication Failure Retry Time (seconds)
     - ``timeToNextRequest``
     - ``AuthToken``, ``AuthOAuth2``
     - number
     - 60
     - Number of seconds before retrying a failed
       authentication request.
   * - Additional Options
     - ``opts``
     - ``AuthToken``, ``AuthApiKey``
     - string
     - ""
     - Additional Options (switch that creates additional
       options field that accepts JSON).

   * - Logging Debug
     - ``debug``
     - All
     - boolean
     - ``False``
     - Enables the logging authentication debug output
   * - Token Refresh Implementation
     - ``tokenAuthRefresh``
     - ``AuthToken``, ``AuthOAuth2``
     - string
     - ``tokenAuthRefreshDynamic``
     - Refresh algorithm choice. Options ``tokenAuthRefreshStatic``
       or ``tokenAuthRefreshDynamic``

   * - Token TTL (seconds)
     - ``tokenRefreshTTL``
     - ``AuthToken``, ``AuthOAuth2``
     - number
     - 3600
     - TTL of the token (in seconds) before auto-refreshing
       for ``tokenAuthRefreshStatic``

   * - Expiry Time Key
     - ``tokenExpiresInSelector``
     - ``AuthToken``, ``AuthOAuth2``
     - string
     - ``expires_in``
     - The key for the expiry time within the JSON
       response

   * - Client ID
     - ``oauth2ClientId``
     - ``AuthOAuth2``
     - string
     - ""
     - Your application's Client ID
   * - Client Secret
     - ``oauth2ClientSecret``
     - ``AuthOAuth2``
     - string
     - ""
     - Your application's Client Secret
   * - Access Token URL
     - ``oauth2TokenURL``
     - ``AuthOAuth2``
     - string
     - ""
     - URL for generating the Oauth2 token
   * - Request Header
     - ``oauth2TokenLabel``
     - ``AuthOAuth2``
     - string
     - ``Authorization``
     - Header utilized when making requests with the token
   * - Token Format
     - ``oauth2TokenFormat``
     - ``AuthOAuth2``
     - string
     - ``Bearer {}``
     - Formulate how the bearer token should be configured
       and sent. The typical format is ``"Bearer {}"`` but varies
       per API. ``{}`` must be included so that the token is inserted
       in that location.

   * - Response Token Key
     - ``oauth2TokenKey``
     - ``AuthOAuth2``
     - string
     - ``access_token``
     - The key that contains the token within the JSON response
   * - Oauth2 Grant Type
     - ``oauth2Type``
     - ``AuthOAuth2``
     - string
     - ``None``
     - Supported authentication methods for Oauth2 i.e. ``None``,
       ``ResourceOwner``, or ``ClientCredentials``

   * - Client auth method
     - ``oauth2AuthType``
     - ``AuthOAuth2``
     - string
     - ``basic_auth``
     - How to send ClientID and ClientSecret i.e. ``basic_auth`` or
       ``post_body``

   * - Token Scope
     - ``oauth2ClientCredentialsScope``
     - ``AuthOAuth2``
     - string
     - ""
     - Specify scope for client credential grant type
   * - Resource Owner Username
     - ``oauth2ResourceOwnerUsername``
     - ``AuthOAuth2``
     - string
     - ""
     - Resource owner username for requesting the token
   * - Resource Owner Password
     - ``oauth2ResourceOwnerPassword``
     - ``AuthOAuth2``
     - string
     - ""
     - Resource owner password for requesting the token
   * - Token Scopes
     - ``oauth2ResourceOwnerScope``
     - ``AuthOAuth2``
     - string
     - ""
     - Specify all required scopes for the request. Refer
       to your application when specifying multiple scopes.

   * - Additional body Parameters
     - ``oauth2ResourceOwnerAdditionalParams``
     - ``AuthOAuth2``
     - string
     - ""
     - Any non-standard body parameters that need to be
       specified for the request. You must enter the params
       in JSON format as a dictionary.
       Example: ``{"scope": "hello", "otherParam": "thanks"}``

   * - Prefix
     - ``prefix``
     - ``AuthApiKey``
     - string
     - ""
     - Prefix to add to the API Key
   * - Suffix
     - ``suffix``
     - ``AuthApiKey``
     - string
     - ""
     - Suffix to add to the API Key
   * - API Key
     - ``apikey``
     - ``AuthApiKey``
     - string
     - ""
     - API Key to use for authentication
   * - Authenticator Override
     - ``authOverride``
     - All
     - string
     - ""
     - This is only used when a custom authenticator is
       leveraging this credential type. Enter the name of
       the custom authenticator.


Examples
========

.. toctree::
  :maxdepth: 1

  authenticators/digest
  authenticators/query
  authenticators/openstack