Skip to content

Authentication

elva.auth

Module providing authentication utilities for server app module.

Classes:

  • BasicAuth

    Base class for Basic Authentication.

  • DummyAuth

    Dummy Basic Authentication class where password equals user name.

  • LDAPBasicAuth

    Basic Authentication using LDAP self-bind.

Functions:

Attributes:

  • AUTH_SCHEME

    Valid autentication schemes in Authorization HTTP request header.

AUTH_SCHEME = ['Basic', 'Digest', 'Negotiate'] module-attribute

Valid autentication schemes in Authorization HTTP request header.

BasicAuth(realm)

Base class for Basic Authentication.

This class is intended to be used in the server app module.

Parameters:

  • realm (str) –

    realm of the Basic Authentication.

Methods:

  • authenticate

    Wrapper around verify with processing and logging.

  • verify

    Decides whether the given credentials are valid or not.

Source code in src/elva/auth.py
def __init__(self, realm: str):
    """
    Arguments:
        realm: realm of the `Basic Authentication`.
    """
    self.realm = realm

authenticate(path, request_headers)

Wrapper around verify with processing and logging.

Parameters:

  • path (str) –

    the path used in the HTTP request.

  • request_headers (dict) –

    the HTTP request headers.

Returns:

Source code in src/elva/auth.py
def authenticate(
    self, path: str, request_headers: dict
) -> None | tuple[HTTPStatus, dict[str, str], None | bytes]:
    """
    Wrapper around [`verify`][elva.auth.BasicAuth.verify] with processing and logging.

    Arguments:
        path: the path used in the HTTP request.
        request_headers: the HTTP request headers.

    Returns:
        `None` if [`verify`][elva.auth.BasicAuth.verify] returns `True`, else it returns the request abort information as specified in [`abort_basic_auth`][elva.auth.abort_basic_auth].
    """
    try:
        scheme, credentials = process_authorization_header(request_headers)
    except KeyError:
        return self._log_and_abort("missing Authorization header")
    except ValueError:
        return self._log_and_abort("malformed Authorization header")

    match scheme:
        case "Basic":
            username, password = process_basic_auth_credentials(credentials)
        case _:
            return self._log_and_abort("unsupported Authorization scheme")

    if not self.verify(username, password):
        return self._log_and_abort("invalid credentials")

verify(username, password)

Decides whether the given credentials are valid or not.

This is defined as a no-op and is intended to implemented in inheriting subclasses.

Parameters:

  • username (str) –

    user name provided in the HTTP request headers.

  • password (str) –

    password provided in the HTTP request headers.

Returns:

  • bool

    True if credentials are valid, False if they are not.

Source code in src/elva/auth.py
def verify(self, username: str, password: str) -> bool:
    """
    Decides whether the given credentials are valid or not.

    This is defined as a no-op and is intended to implemented in inheriting subclasses.

    Arguments:
        username: user name provided in the HTTP request headers.
        password: password provided in the HTTP request headers.

    Returns:
        `True` if credentials are valid, `False` if they are not.
    """
    ...

DummyAuth(realm)

Bases: BasicAuth

Dummy Basic Authentication class where password equals user name.

Danger

This class is intended for testing only. DO NOT USE IN PRODUCTION!

Source code in src/elva/auth.py
def __init__(self, realm: str):
    """
    Arguments:
        realm: realm of the `Basic Authentication`.
    """
    self.realm = realm

LDAPBasicAuth(realm, server, base)

Bases: BasicAuth

Basic Authentication using LDAP self-bind.

Parameters:

  • realm (str) –

    realm of the Basic Authentication.

  • server (str) –

    address of the LDAP server.

  • base (str) –

    base for lookup on the LDAP server.

Methods:

  • verify

    Perform a self-bind connection to the given LDAP server.

Source code in src/elva/auth.py
def __init__(self, realm: str, server: str, base: str):
    """
    Arguments:
        realm: realm of the `Basic Authentication`.
        server: address of the LDAP server.
        base: base for lookup on the LDAP server.
    """
    super().__init__(realm)
    self.server = ldap3.Server(server, use_ssl=True)
    self.base = base
    self.log.info(f"server: {self.server.name}, base: {base}")

verify(username, password)

Perform a self-bind connection to the given LDAP server.

Parameters:

  • username (str) –

    user name to use for the LDAP self-bind connection.

  • password (str) –

    password to use for the LDAP self-bind connection.

Returns:

  • bool

    True if the LDAP self-bind connection could be established, i.e. was successful, False if no successful connection could be established.

Source code in src/elva/auth.py
def verify(self, username: str, password: str) -> bool:
    """
    Perform a self-bind connection to the given LDAP server.

    Arguments:
        username: user name to use for the LDAP self-bind connection.
        password: password to use for the LDAP self-bind connection.

    Returns:
        `True` if the LDAP self-bind connection could be established, i.e. was successful, `False` if no successful connection could be established.
    """
    user = f"uid={username},{self.base}"
    try:
        self.log.debug("try LDAP connection")
        with ldap3.Connection(
            self.server,
            user=user,
            password=password,
        ) as conn:
            if conn.result["description"] == "success":
                self.log.debug(f"successful self-bind with {user}")
                return True
            else:
                self.log.debug(f"self-bind with {user} not successful")
                return False

    except LDAPException:
        self.log.debug(f"unable to connect with {user}")
        return False

basic_authorization_header(username, password)

Compose the Base64 encoded Authorization header for Basic authentication.

Parameters:

  • username (str) –

    user name used for authentication.

  • password (str) –

    password used for authentication.

Returns:

  • dict[str, str]

    dictionary holding the Base64 encoded Authorization header contents as value.

Source code in src/elva/auth.py
def basic_authorization_header(username: str, password: str) -> dict[str, str]:
    """
    Compose the Base64 encoded `Authorization` header for `Basic` authentication.

    Arguments:
        username: user name used for authentication.
        password: password used for authentication.

    Returns:
        dictionary holding the Base64 encoded `Authorization` header contents as value.
    """
    bvalue = f"{username}:{password}".encode()
    b64bvalue = b64encode(bvalue).decode()

    return {"Authorization": f"Basic {b64bvalue}"}

process_authorization_header(request_headers)

Decompose Base64 encoded Authorization header into scheme and credentials.

Parameters:

  • request_headers (dict) –

    dictionary of HTTP request headers.

Returns:

  • tuple[str, str]

    tuple holding the scheme and the (still Base64 encoded) credentials.

Source code in src/elva/auth.py
def process_authorization_header(request_headers: dict) -> tuple[str, str]:
    """
    Decompose Base64 encoded `Authorization` header into scheme and credentials.

    Arguments:
        request_headers: dictionary of HTTP request headers.

    Returns:
        tuple holding the scheme and the (still Base64 encoded) credentials.
    """
    auth_header = request_headers["Authorization"]
    scheme, credentials = auth_header.split(" ", maxsplit=1)
    if scheme not in AUTH_SCHEME:
        raise ValueError("invalid scheme in Authorization header")
    return scheme, credentials

process_basic_auth_credentials(credentials)

Decode Base64 encoded Basic authorization header payload.

Parameters:

  • credentials (str) –

    Base64 encoded credentials from the Authorization HTTP request header.

Returns:

  • tuple[str, str]

    tuple holding decoded user name and password.

Source code in src/elva/auth.py
def process_basic_auth_credentials(credentials: str) -> tuple[str, str]:
    """
    Decode Base64 encoded `Basic` authorization header payload.

    Arguments:
        credentials: Base64 encoded credentials from the `Authorization` HTTP request header.

    Returns:
        tuple holding decoded user name and password.
    """
    bb64cred = credentials.encode()
    bcred = b64decode(bb64cred)
    cred = bcred.decode()
    username, password = cred.split(":", maxsplit=1)
    return username, password

abort_basic_auth(realm, body=None, status=HTTPStatus.UNAUTHORIZED)

Compose Basic Authentication abort information.

Parameters:

  • realm (str) –

    Basic Authentication realm.

  • body (None | str, default: None ) –

    message body to send.

  • status (HTTPStatus, default: UNAUTHORIZED ) –

    HTTP status for this abort.

Returns:

  • tuple[HTTPStatus, dict[str, str], None | bytes]

    tuple holding the HTTP status, the dictionary with the WWW-Authenticate header information for Basic Authentication and the UTF-8 encoded message body if given.

Source code in src/elva/auth.py
def abort_basic_auth(
    realm: str,
    body: None | str = None,
    status: HTTPStatus = HTTPStatus.UNAUTHORIZED,
) -> tuple[HTTPStatus, dict[str, str], None | bytes]:
    """
    Compose `Basic Authentication` abort information.

    Arguments:
        realm: `Basic Authentication` realm.
        body: message body to send.
        status: HTTP status for this abort.

    Returns:
        tuple holding the HTTP status, the dictionary with the `WWW-Authenticate` header information for `Basic Authentication` and the UTF-8 encoded message body if given.
    """
    headers = {"WWW-Authenticate": f"Basic realm={realm}"}
    if body:
        body = body.encode()
    return status, headers, body