Source code for pysasl.identity


import secrets
from abc import abstractmethod
from typing import Optional, Sequence
from typing_extensions import Protocol, Self

from .hashing import HashInterface, Cleartext
from .prep import saslprep, Preparation

__all__ = ['Identity', 'ClearIdentity', 'HashedIdentity']


[docs]class Identity(Protocol): """Represents an server-side identity that credentials will be authenticated against. """ __slots__: Sequence[str] = [] @property @abstractmethod def authcid(self) -> str: """The authentication identity, e.g. a login username.""" ...
[docs] @abstractmethod def compare_authcid(self, authcid: str) -> bool: """Compare the identity's authcid with the given *authcid*. Args: authcid: The authentication identity string value. """ ...
[docs] @abstractmethod def compare_secret(self, secret: str) -> bool: """Compare the identity's secret with the given *secret*. The comparison must account for things like hashing or token algorithms. Args: secret: The authentication secret string value. """ ...
[docs] @abstractmethod def get_clear_secret(self) -> Optional[str]: """Return the cleartext secret string if it is available.""" ...
[docs]class ClearIdentity(Identity): """An :class:`Identity` that stores the secret string in cleartext. Args: authcid: The authentication identity, e.g. a login username. secret: The authentication secret string value, e.g. a login password. prepare: The string preparation function. """ __slots__ = ['_authcid', '_secret', '_prepare'] def __init__(self, authcid: str, secret: str, *, prepare: Preparation = saslprep) -> None: super().__init__() self._authcid = authcid self._secret = secret self._prepare = prepare @property def authcid(self) -> str: return self._authcid def _compare(self, this: str, that: str) -> bool: prepare = self._prepare prepared_this = prepare(this).encode('utf-8') prepared_that = prepare(that).encode('utf-8') return secrets.compare_digest(prepared_this, prepared_that)
[docs] def compare_authcid(self, authcid: str) -> bool: return self._compare(self.authcid, authcid)
[docs] def compare_secret(self, secret: str) -> bool: return self._compare(self._secret, secret)
[docs] def get_clear_secret(self) -> str: """Return the cleartext secret string.""" return self._secret
def __repr__(self) -> str: return f'ClearIdentity({self.authcid!r}, ...)'
[docs]class HashedIdentity(Identity): """An :class:`Identity` where the secret has been hashed for storage. Args: authcid: The authentication identity, e.g. a login username. digest: The hashed secret string, using :attr:`.hash`. hash: The hash algorithm to use to verify the secret. prepare: The string preparation function. """ __slots__ = ['_authcid', '_digest', '_hash', '_prepare'] def __init__(self, authcid: str, digest: str, *, hash: HashInterface, prepare: Preparation = saslprep) -> None: super().__init__() self._authcid = authcid self._digest = digest self._hash = hash self._prepare = prepare @property def authcid(self) -> str: return self._authcid
[docs] @classmethod def create(cls, authcid: str, secret: str, *, hash: HashInterface, prepare: Preparation = saslprep) -> Self: """Prepare and hash the given *secret*, returning a :class:`HashedIdentity`. Args: authcid: The authentication identity, e.g. a login username. secret: The cleartext secret string. hash: The hash algorithm to use to verify the secret. prepare: The string preparation function. """ digest = hash.hash(prepare(secret)) return cls(authcid, digest, hash=hash, prepare=prepare)
@property def digest(self) -> str: """The hashed secret string, using :attr:`.hash`.""" return self._digest @property def hash(self) -> HashInterface: """The hash implementation to use to verify the secret.""" return self._hash def _compare(self, this: str, that: str) -> bool: prepare = self._prepare prepared_this = prepare(this).encode('utf-8') prepared_that = prepare(that).encode('utf-8') return secrets.compare_digest(prepared_this, prepared_that)
[docs] def compare_authcid(self, authcid: str) -> bool: return self._compare(self.authcid, authcid)
[docs] def compare_secret(self, secret: str) -> bool: return self._hash.verify(self._prepare(secret), self._digest)
[docs] def get_clear_secret(self) -> Optional[str]: """Return the cleartext secret string, only if :attr:`.hash` is :class:`~pysasl.hashing.Cleartext`. """ if isinstance(self.hash, Cleartext): return self.digest else: return None
def __repr__(self) -> str: return f'HashedIdentity({self.authcid}, ..., hash={self._hash!r})'