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. When possible, you should prefer to verify tokens 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