Misc cleanup

This commit is contained in:
Jeremy Stretch 2025-10-02 16:33:04 -04:00
parent 43fc7fb58a
commit f82f084c02
2 changed files with 25 additions and 13 deletions

View File

@ -29,6 +29,8 @@ class Token(models.Model):
An API token used for user authentication. This extends the stock model to allow each user to have multiple tokens.
It also supports setting an expiration time and toggling write ability.
"""
_token = None
version = models.PositiveSmallIntegerField(
verbose_name=_('version'),
choices=TokenVersionChoices,
@ -136,12 +138,12 @@ class Token(models.Model):
def __init__(self, *args, token=None, **kwargs):
super().__init__(*args, **kwargs)
# This stores the initial plaintext value (if given) on the creation of a new Token. If not provided, a
# random token value will be generated and assigned immediately prior to saving the Token instance.
self.token = token
def __str__(self):
if self.v1:
return self.partial
return self.key
return self.key if self.v2 else self.partial
def get_absolute_url(self):
return reverse('users:token', args=[self.pk])
@ -156,14 +158,19 @@ class Token(models.Model):
@property
def partial(self):
"""
Return a sanitized representation of a v1 token.
"""
return f'**********************************{self.plaintext[-6:]}' if self.plaintext else ''
@property
def token(self):
return getattr(self, '_token', None)
return self._token
@token.setter
def token(self, value):
if not self._state.adding:
raise ValueError("Cannot assign a new plaintext value for an existing token.")
self._token = value
if value is not None:
if self.v1:
@ -173,8 +180,11 @@ class Token(models.Model):
self.update_digest()
def clean(self):
if self._state.adding and self.v2 and not settings.API_TOKEN_PEPPERS:
raise ValidationError(_("Cannot create v2 tokens: API_TOKEN_PEPPERS is not defined."))
if self._state.adding:
if self.pepper_id is not None and self.pepper_id not in settings.API_TOKEN_PEPPERS:
raise ValidationError(_(
"Invalid pepper ID: {id}. Check configured API_TOKEN_PEPPERS."
).format(id=self.pepper_id))
def save(self, *args, **kwargs):
# If creating a new Token and no token value has been specified, generate one
@ -201,9 +211,9 @@ class Token(models.Model):
"""
Recalculate and save the HMAC digest using the currently defined pepper and token values.
"""
self.pepper_id, pepper_value = get_current_pepper()
self.pepper_id, pepper = get_current_pepper()
self.hmac_digest = hmac.new(
pepper_value.encode('utf-8'),
pepper.encode('utf-8'),
self.token.encode('utf-8'),
hashlib.sha256
).hexdigest()
@ -216,12 +226,14 @@ class Token(models.Model):
def validate(self, token):
"""
Returns true if the given token value validates.
Validate the given plaintext against the token.
For v1 tokens, check that the given value is equal to the stored plaintext. For v2 tokens, calculate an HMAC
from the Token's pepper ID and the given plaintext value, and check whether the result matches the recorded
digest.
"""
if self.is_expired:
return False
if self.v1:
return token == self.key
return token == self.token
if self.v2:
try:
pepper = settings.API_TOKEN_PEPPERS[self.pepper_id]

View File

@ -22,5 +22,5 @@ def get_current_pepper():
"""
if len(settings.API_TOKEN_PEPPERS) < 1:
raise ImproperlyConfigured("Must define API_TOKEN_PEPPERS to use v2 API tokens")
newest_id = sorted(settings.API_TOKEN_PEPPERS)[-1]
newest_id = sorted(settings.API_TOKEN_PEPPERS.keys())[-1]
return newest_id, settings.API_TOKEN_PEPPERS[newest_id]