Jeremy Stretch 4 месяцев назад
Родитель
Сommit
f82f084c02
2 измененных файлов с 25 добавлено и 13 удалено
  1. 24 12
      netbox/users/models/tokens.py
  2. 1 1
      netbox/users/utils.py

+ 24 - 12
netbox/users/models/tokens.py

@@ -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]

+ 1 - 1
netbox/users/utils.py

@@ -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]