authentik.stages.authenticator

Authenticator devices helpers

  1"""Authenticator devices helpers"""
  2
  3from typing import TYPE_CHECKING
  4
  5from django.db import transaction
  6
  7if TYPE_CHECKING:
  8    from authentik.core.models import User
  9
 10
 11def verify_token(user, device_id, token):
 12    """
 13    Attempts to verify a :term:`token` against a specific device, identified by
 14    :attr:`~authentik.stages.authenticator.models.Device.persistent_id`.
 15
 16    This wraps the verification process in a transaction to ensure that things
 17    like throttling polices are properly enforced.
 18
 19    :param user: The user supplying the token.
 20    :type user: :class:`~django.contrib.auth.models.User`
 21
 22    :param str device_id: A device's persistent_id value.
 23
 24    :param str token: An OTP token to verify.
 25
 26    :returns: The device that accepted ``token``, if any.
 27    :rtype: :class:`~authentik.stages.authenticator.models.Device` or ``None``
 28
 29    """
 30    from authentik.stages.authenticator.models import Device
 31
 32    verified = None
 33    with transaction.atomic():
 34        device = Device.from_persistent_id(device_id, for_verify=True)
 35        if (device is not None) and (device.user_id == user.pk) and device.verify_token(token):
 36            verified = device
 37
 38    return verified
 39
 40
 41def match_token(user, token):
 42    """
 43    Attempts to verify a :term:`token` on every device attached to the given
 44    user until one of them succeeds.
 45
 46    .. warning::
 47
 48        This originally existed for more convenient integration with the admin
 49        site. Its use is no longer recommended and it is not guaranteed to
 50        interact well with more recent features (such as throttling). Tokens
 51        should always be verified against specific devices.
 52
 53    :param user: The user supplying the token.
 54    :type user: :class:`~django.contrib.auth.models.User`
 55
 56    :param str token: An OTP token to verify.
 57
 58    :returns: The device that accepted ``token``, if any.
 59    :rtype: :class:`~authentik.stages.authenticator.models.Device` or ``None``
 60    """
 61    with transaction.atomic():
 62        for device in devices_for_user(user, for_verify=True):
 63            if device.verify_token(token):
 64                break
 65        else:
 66            device = None
 67
 68    return device
 69
 70
 71def devices_for_user(user: User, confirmed: bool | None = True, for_verify: bool = False):
 72    """
 73    Return an iterable of all devices registered to the given user.
 74
 75    Returns an empty iterable for anonymous users.
 76
 77    :param user: standard or custom user object.
 78    :type user: :class:`~django.contrib.auth.models.User`
 79
 80    :param bool confirmed: If ``None``, all matching devices are returned.
 81        Otherwise, this can be any true or false value to limit the query
 82        to confirmed or unconfirmed devices, respectively.
 83
 84    :param bool for_verify: If ``True``, we'll load the devices with
 85        :meth:`~django.db.models.query.QuerySet.select_for_update` to prevent
 86        concurrent verifications from succeeding. In which case, this must be
 87        called inside a transaction.
 88
 89    :rtype: iterable
 90    """
 91    if user.is_anonymous:
 92        return
 93
 94    for model in device_classes():
 95        device_set = model.objects.devices_for_user(user, confirmed=confirmed)
 96        if for_verify:
 97            device_set = device_set.select_for_update()
 98
 99        yield from device_set
100
101
102def user_has_device(user, confirmed=True):
103    """
104    Return ``True`` if the user has at least one device.
105
106    Returns ``False`` for anonymous users.
107
108    :param user: standard or custom user object.
109    :type user: :class:`~django.contrib.auth.models.User`
110
111    :param confirmed: If ``None``, all matching devices are considered.
112        Otherwise, this can be any true or false value to limit the query
113        to confirmed or unconfirmed devices, respectively.
114    """
115    try:
116        next(devices_for_user(user, confirmed=confirmed))
117    except StopIteration:
118        has_device = False
119    else:
120        has_device = True
121
122    return has_device
123
124
125def device_classes():
126    """
127    Returns an iterable of all loaded device models.
128    """
129    from django.apps import apps
130
131    from authentik.stages.authenticator.models import Device
132
133    for config in apps.get_app_configs():
134        for model in config.get_models():
135            if issubclass(model, Device):
136                yield model
def verify_token(user, device_id, token):
12def verify_token(user, device_id, token):
13    """
14    Attempts to verify a :term:`token` against a specific device, identified by
15    :attr:`~authentik.stages.authenticator.models.Device.persistent_id`.
16
17    This wraps the verification process in a transaction to ensure that things
18    like throttling polices are properly enforced.
19
20    :param user: The user supplying the token.
21    :type user: :class:`~django.contrib.auth.models.User`
22
23    :param str device_id: A device's persistent_id value.
24
25    :param str token: An OTP token to verify.
26
27    :returns: The device that accepted ``token``, if any.
28    :rtype: :class:`~authentik.stages.authenticator.models.Device` or ``None``
29
30    """
31    from authentik.stages.authenticator.models import Device
32
33    verified = None
34    with transaction.atomic():
35        device = Device.from_persistent_id(device_id, for_verify=True)
36        if (device is not None) and (device.user_id == user.pk) and device.verify_token(token):
37            verified = device
38
39    return verified

Attempts to verify a :term:token against a specific device, identified by :attr:~authentik.stages.authenticator.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:~authentik.stages.authenticator.models.Device or None

def match_token(user, token):
42def match_token(user, token):
43    """
44    Attempts to verify a :term:`token` on every device attached to the given
45    user until one of them succeeds.
46
47    .. warning::
48
49        This originally existed for more convenient integration with the admin
50        site. Its use is no longer recommended and it is not guaranteed to
51        interact well with more recent features (such as throttling). Tokens
52        should always be verified against specific devices.
53
54    :param user: The user supplying the token.
55    :type user: :class:`~django.contrib.auth.models.User`
56
57    :param str token: An OTP token to verify.
58
59    :returns: The device that accepted ``token``, if any.
60    :rtype: :class:`~authentik.stages.authenticator.models.Device` or ``None``
61    """
62    with transaction.atomic():
63        for device in devices_for_user(user, for_verify=True):
64            if device.verify_token(token):
65                break
66        else:
67            device = None
68
69    return device

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:~authentik.stages.authenticator.models.Device or None

def devices_for_user(unknown):
 72def devices_for_user(user: User, confirmed: bool | None = True, for_verify: bool = False):
 73    """
 74    Return an iterable of all devices registered to the given user.
 75
 76    Returns an empty iterable for anonymous users.
 77
 78    :param user: standard or custom user object.
 79    :type user: :class:`~django.contrib.auth.models.User`
 80
 81    :param bool confirmed: If ``None``, all matching devices are returned.
 82        Otherwise, this can be any true or false value to limit the query
 83        to confirmed or unconfirmed devices, respectively.
 84
 85    :param bool for_verify: If ``True``, we'll load the devices with
 86        :meth:`~django.db.models.query.QuerySet.select_for_update` to prevent
 87        concurrent verifications from succeeding. In which case, this must be
 88        called inside a transaction.
 89
 90    :rtype: iterable
 91    """
 92    if user.is_anonymous:
 93        return
 94
 95    for model in device_classes():
 96        device_set = model.objects.devices_for_user(user, confirmed=confirmed)
 97        if for_verify:
 98            device_set = device_set.select_for_update()
 99
100        yield from device_set

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

def user_has_device(user, confirmed=True):
103def user_has_device(user, confirmed=True):
104    """
105    Return ``True`` if the user has at least one device.
106
107    Returns ``False`` for anonymous users.
108
109    :param user: standard or custom user object.
110    :type user: :class:`~django.contrib.auth.models.User`
111
112    :param confirmed: If ``None``, all matching devices are considered.
113        Otherwise, this can be any true or false value to limit the query
114        to confirmed or unconfirmed devices, respectively.
115    """
116    try:
117        next(devices_for_user(user, confirmed=confirmed))
118    except StopIteration:
119        has_device = False
120    else:
121        has_device = True
122
123    return has_device

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.

def device_classes():
126def device_classes():
127    """
128    Returns an iterable of all loaded device models.
129    """
130    from django.apps import apps
131
132    from authentik.stages.authenticator.models import Device
133
134    for config in apps.get_app_configs():
135        for model in config.get_models():
136            if issubclass(model, Device):
137                yield model

Returns an iterable of all loaded device models.