Source code for django_otp

from django.contrib.auth.signals import user_logged_in
from django.db import transaction

DEVICE_ID_SESSION_KEY = 'otp_device_id'


[docs] def login(request, device): """ Persist the given OTP device in the current session. The device will be rejected if it does not belong to ``request.user``. This is called automatically any time :func:`django.contrib.auth.login` is called with a user having an ``otp_device`` atribute. If you use Django's :class:`~django.contrib.auth.views.LoginView` view with the django-otp authentication forms, then you won't need to call this. :param request: The HTTP request :type request: :class:`~django.http.HttpRequest` :param device: The OTP device used to verify the user. :type device: :class:`~django_otp.models.Device` """ user = getattr(request, 'user', None) if (user is not None) and (device is not None) and (device.user_id == user.pk): request.session[DEVICE_ID_SESSION_KEY] = device.persistent_id request.user.otp_device = device
def _handle_auth_login(sender, request, user, **kwargs): """ Automatically persists an OTP device that was set by an OTP-aware AuthenticationForm. """ if hasattr(user, 'otp_device'): login(request, user.otp_device) user_logged_in.connect(_handle_auth_login)
[docs] def verify_token(user, device_id, token): """ Attempts to verify a :term:`token` against a specific device, identified by :attr:`~django_otp.models.Device.persistent_id`. This wraps the verification process in a transaction to ensure that things like throttling polices are properly enforced. :param user: The user supplying the token. :type user: :class:`~django.contrib.auth.models.User` :param str device_id: A device's persistent_id value. :param str token: An OTP token to verify. :returns: The device that accepted ``token``, if any. :rtype: :class:`~django_otp.models.Device` or ``None`` """ from django_otp.models import Device verified = None with transaction.atomic(): device = Device.from_persistent_id(device_id, for_verify=True) if ( (device is not None) and (device.user_id == user.pk) and device.verify_token(token) ): verified = device return verified
[docs] def match_token(user, token): """ Attempts to verify a :term:`token` on every device attached to the given user until one of them succeeds. .. warning:: This originally existed for more convenient integration with the admin site. Its use is no longer recommended and it is not guaranteed to interact well with more recent features (such as throttling). Tokens should always be verified against specific devices. :param user: The user supplying the token. :type user: :class:`~django.contrib.auth.models.User` :param str token: An OTP token to verify. :returns: The device that accepted ``token``, if any. :rtype: :class:`~django_otp.models.Device` or ``None`` """ with transaction.atomic(): for device in devices_for_user(user, for_verify=True): if device.verify_token(token): break else: device = None return device
[docs] def devices_for_user(user, confirmed=True, for_verify=False): """ Return an iterable of all devices registered to the given user. Returns an empty iterable for anonymous users. :param user: standard or custom user object. :type user: :class:`~django.contrib.auth.models.User` :param bool confirmed: If ``None``, all matching devices are returned. Otherwise, this can be any true or false value to limit the query to confirmed or unconfirmed devices, respectively. :param bool for_verify: If ``True``, we'll load the devices with :meth:`~django.db.models.query.QuerySet.select_for_update` to prevent concurrent verifications from succeeding. In which case, this must be called inside a transaction. :rtype: iterable """ if user.is_anonymous: return for model in device_classes(): device_set = model.objects.devices_for_user(user, confirmed=confirmed) if for_verify: device_set = device_set.select_for_update() yield from device_set
[docs] def user_has_device(user, confirmed=True): """ Return ``True`` if the user has at least one device. Returns ``False`` for anonymous users. :param user: standard or custom user object. :type user: :class:`~django.contrib.auth.models.User` :param confirmed: If ``None``, all matching devices are considered. Otherwise, this can be any true or false value to limit the query to confirmed or unconfirmed devices, respectively. """ try: next(devices_for_user(user, confirmed=confirmed)) except StopIteration: has_device = False else: has_device = True return has_device
def device_classes(): """ Returns an iterable of all loaded device models. """ from django.apps import apps # isort: skip from django_otp.models import Device for config in apps.get_app_configs(): for model in config.get_models(): if issubclass(model, Device): yield model