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