"""Authentication backends that define the behaviour for chekinga user's latch."""# SPDX-License-Identifier: BSD-3-Clausefromdjango.contrib.authimportget_user_modelfromdjango.contrib.auth.backendsimportModelBackendfromdjango.core.exceptionsimportPermissionDeniedfromdjango.utils.cryptoimportget_random_stringfromlatch_sdk.exceptionsimportLatchErrorfrom.importget_latch_apiUserModel=get_user_model()
[docs]defcan_pass_latch(user):""" Check the ``user``'s latch state. Return ``True`` if the latch is open, ``False`` if it's closed. """can_pass=Truelatch_api=get_latch_api()try:l_config=user.latch_configexceptUserModel.latch_config.RelatedObjectDoesNotExist:# In order to prevent an attacker knowing a user has configured# the Latch service, we need to make a mock call to the API# so the latency difference between a user with a configured latch# and a user without is not significant enough.try:latch_api.account_status(get_random_string(64))exceptLatchError:passelse:status=latch_api.account_status(l_config.account_id)can_pass=status.statusreturncan_pass
[docs]classLatchModelBackendMixin:""" A mixin for authentication backends that checks if the user has its latch on. In order to be able to use Latch and not provoke any error, this class must be the first one to be inherited from when creating a custom authentication backend. .. automethod:: user_can_authenticate .. automethod:: get_user """# pylint: disable=too-few-public-methods
[docs]defuser_can_authenticate(self,user):""" Reject users who have their latch on. Users without the Latch configured are allowed. If the user's latch is on, i.e. it cannot access the application, then a :exc:`~django.core.exceptions.PermissionDenied` is raised. Though it may seems this breaks the original "contract" of the :meth:`~django.contrib.auth.backends.BaseBackend.user_can_authenticate` method, there is no other place to raise the exception without overriding the :meth:`~django.contrib.auth.backends.BaseBackend.authenticate` method and blocking the check on the rest of the authentication backends (which is the objective of Latch: completely block the access, though in future releases this can be extended and generalized). """ifnotcan_pass_latch(user):raisePermissionDenied()returnsuper().user_can_authenticate(user)
[docs]defget_user(self,user_id):""" Returns the user object related to ``user_id``. We need to override this method because we are raising the :exc:`~django.core.exceptions.PermissionDenied` exception in the method :meth:`django.contrib.auth.backends.BaseBackend.user_can_authenticate` instead of on :meth:`django.contrib.auth.backends.BaseBackend.authenticate`. Then, if we use :class:`django.contrib.auth.backends.ModelBackend` the exception would not be caught in :meth:`~django.contrib.auth.backends.ModelBackend.get_user`. The decision behind of raising the exception in :meth:`django.contrib.auth.backends.BaseBackend.user_can_authenticate` is because the method :meth:`django.contrib.auth.backends.BaseBackend.authenticate` is more likely to be overridden, but, of course, overriding :meth:`django.contrib.auth.backends.BaseBackend.get_user` may be also a big sacrifice. """try:user=UserModel._default_manager.get(pk=user_id)# pylint: disable=protected-accessexceptUserModel.DoesNotExist:returnNonereturnuserifsuper().user_can_authenticate(user)elseNone
[docs]classLatchDefaultModelBackend(LatchModelBackendMixin,ModelBackend):""" A subclass of :class:`django.contrib.auth.backends.ModelBackend` that also checks if the user's latch is on. This backend is useful for a fast integration of the Latch service into a Django project that uses the default authentication process, so it has `the same limitations <https://docs.djangoproject.com/en/5.2/topics/auth/customizing/#specifying-authentication-backends>`_. """