Source code for asymmetric_jwt_auth.models

from django.conf import settings
from django.db import models
from django.utils import timezone
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
from django.utils.encoding import force_str, force_bytes
from jwt import PyJWKClient
from . import keys, tokens


def validate_public_key(keystr: str) -> None:
    """
    Check that the given value is a valid public key in either PEM or OpenSSH format. If it is invalid,
    raises ``django.core.exceptions.ValidationError``.
    """
    key_bytes = keystr.encode()
    exc, key = keys.PublicKey.load_serialized_public_key(key_bytes)
    is_valid = (exc is None) and (key is not None)
    if not is_valid:
        raise ValidationError("Public key is invalid: %s" % exc)


[docs]class PublicKey(models.Model): """ Store a public key and associate it to a particular user. Implements the same concept as the OpenSSH ``~/.ssh/authorized_keys`` file on a Unix system. """ #: Foreign key to the Django User model. Related name: ``public_keys``. user = models.ForeignKey( settings.AUTH_USER_MODEL, verbose_name=_("User"), related_name="public_keys", on_delete=models.CASCADE, ) #: Key text in either PEM or OpenSSH format. key = models.TextField( _("Public Key"), help_text=_("The user's RSA/Ed25519 public key"), validators=[validate_public_key], ) #: Comment describing the key. Use this to note what system is authenticating with the key, when it was last rotated, etc. comment = models.CharField( _("Comment"), max_length=100, help_text=_("Comment describing this key"), blank=True, ) #: Date and time that key was last used for authenticating a request. last_used_on = models.DateTimeField(_("Last Used On"), null=True, blank=True) class Meta: verbose_name = _("Public Key") verbose_name_plural = _("Public Keys") def get_key(self) -> keys.FacadePublicKey: key_bytes = force_bytes(self.key) exc, key = keys.PublicKey.load_serialized_public_key(key_bytes) if key is None: if exc is None: # pragma: no cover raise ValueError("Failed to load key") raise exc return key def update_last_used_datetime(self) -> None: self.last_used_on = timezone.now() self.save(update_fields=["last_used_on"])
[docs] def save(self, *args, **kwargs) -> None: key_parts = force_str(self.key).split(" ") if len(key_parts) == 3 and not self.comment: self.comment = key_parts.pop() super(PublicKey, self).save(*args, **kwargs)
[docs]class JWKSEndpointTrust(models.Model): """ Associate a JSON Web Key Set (JWKS) URL with a Django User. This accomplishes the same purpose of the PublicKey model, in a more automated fashion. Instead of manually assigning a public key to a user, the system will load a list of public keys from this URL. """ #: Foreign key to the Django User model. Related name: ``public_keys``. user = models.OneToOneField( settings.AUTH_USER_MODEL, verbose_name=_("User"), related_name="jwks_endpoint", on_delete=models.CASCADE, ) #: URL of the JSON Web Key Set (JWKS) jwks_url = models.URLField( _("JSON Web Key Set (JWKS) URL"), help_text=_("e.g. https://dev-87evx9ru.auth0.com/.well-known/jwks.json"), ) class Meta: verbose_name = _("JSON Web Key Set") verbose_name_plural = _("JSON Web Key Sets") @property def jwks_client(self) -> PyJWKClient: return PyJWKClient(self.jwks_url) def get_signing_key(self, untrusted_token: tokens.UntrustedToken) -> keys.PublicKey: jwk = self.jwks_client.get_signing_key_from_jwt(untrusted_token.token) return keys.PublicKey.from_cryptography_pubkey(jwk.key)