Digest Authentication

In this example, a new custom authenticator will be made re-using the out-of-the-box AuthBasic authenticator and a Request’s digest authencation object. See HTTPDigestAuth Finally, we will test it the newly created authenticator against a test endpoint that requires Digest Authentication. See RFC 7616

The first step is understanding the AuthBasic authenticator. Below is the implementation of the authenticator. It is using HTTPBasicAuth from the Requests library.

from silo.auth import Authenticator, add_auth

@add_auth
class AuthBasic(Authenticator):
    """AuthBasic encapsulates the authentication lifecycle for BasicAuth.
    username, password and debug are the expected keys for AuthBasic.
    Debug is used for logging requests to stderr for debug purposes.
    :param Authenticator Authenticator: Parent ABC Class
    :param silo.apps.collection.credential.Credential credentials: credentials retrieved
    from em7/UCF
    """
    def __init__(self, credentials):
        """Constructor method"""
        Authenticator.__init__(self, credentials)
        self.auth = HTTPBasicAuth(credentials.fields["username"], credentials.fields["password"])
        self.cred_filter = CredentialFilter(credentials)
        self.cred_filter.update_secret_from_auth(self.auth)

    def check_if_authenticated(self):
        """
        Required for ABC implementation. This is a return True.
        This is due to the fact that we will essentially authenticate
        on every resource request.
        :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.
        Used in HTTPRequest to trigger a retry on an unsuccessful request.
        :param response result: Response object after making a REST call
        :return: If request was successful or not, True -> Success, False -> Fail
        :rtype: boolean
        """
        return http_check_result(result)

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

    def modify_request(self, res):
        """Function to apply authorization impl to Request Session
        :param session res: Request to be updated with credentials
        :type res: [type]
        """
        res.auth = self.auth
        return True

    @staticmethod
    def build(credentials):
        """Build method to be called by get_authmethod in low_code"""
        return AuthBasic(credentials)

    @staticmethod
    def get_name():
        return "AuthBasic"

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

The following changes are needed to change the authenticator from HTTPBasicAuth to the HTTPDigestAuth and allow use to use this new authenticator. This authenticator will rely on reusing methods from AuthBasic, but switches out the underlying Requests authenticator.

Use provided Requests Authenticator
from requests import HTTPDigestAuth

self.auth = HTTPDigestAuth(credentials.fields["username"], credentials.fields["password"])
Specify new Authenticator name
@staticmethod
    def get_name():
        return "AuthDigest"

Putting It All Together

Below is the new authenticator and a test endpoint to verify its functionality.

Snippet

from silo.auth import AuthBasic, add_auth
from requests.auth import HTTPDigestAuth

@add_auth
class AuthDigest(AuthBasic):
    """AuthDigest encapsulates the authentication lifecycle for DigestAuth.
    username and password are the expected credential keys for AuthDigest.
    Debug is used for logging requests to stderr for debug purposes.
    :param Authenticator Authenticator: Parent ABC Class
    :param silo.apps.collection.credential.Credential credentials: credentials retrieved
    from em7/UCF
    """
    def __init__(self, credentials, **kwargs):
        """Constructor method"""
        AuthBasic.__init__(self, credentials)
        self.auth = HTTPDigestAuth(credentials.fields["username"], credentials.fields["password"])

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

    @staticmethod
    def get_name():
        return "AuthDigest"

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

Snippet Argument

low_code:
version: 2
steps:
    - http:
        url: "https://www.httpbin.org/digest-auth/auth/sfuser/sfpassword"
    - json

Credential

  1. Authentication Override: AuthDigest

  2. Username: sfuser

  3. Password: sfpassword

The expected return is a 200 status code and the following output.

{"authenticated": True, "user": "sfuser"}