Просмотр исходного кода

Enforce a fixed key length for v2 tokens

Jeremy Stretch 4 месяцев назад
Родитель
Сommit
5dc48f3a88

+ 3 - 0
netbox/users/constants.py

@@ -10,4 +10,7 @@ OBJECTPERMISSION_OBJECT_TYPES = Q(
 
 CONSTRAINT_TOKEN_USER = '$user'
 
+# API tokens
+TOKEN_KEY_LENGTH = 16
+TOKEN_DEFAULT_LENGTH = 40
 TOKEN_CHARSET = string.ascii_letters + string.digits

+ 11 - 1
netbox/users/migrations/0014_users_token_v2.py

@@ -21,6 +21,7 @@ class Migration(migrations.Migration):
         migrations.RunSQL(
             sql="ALTER INDEX IF EXISTS users_token_key_key RENAME TO users_token_plaintext_key",
         ),
+
         # Make plaintext (formerly key) nullable for v2 tokens
         migrations.AlterField(
             model_name='token',
@@ -33,6 +34,7 @@ class Migration(migrations.Migration):
                 validators=[django.core.validators.MinLengthValidator(40)]
             ),
         ),
+
         # Add version field to distinguish v1 and v2 tokens
         migrations.AddField(
             model_name='token',
@@ -40,17 +42,25 @@ class Migration(migrations.Migration):
             field=models.PositiveSmallIntegerField(default=1),  # Mark all existing Tokens as v1
             preserve_default=False,
         ),
+
         # Change the default version for new tokens to v2
         migrations.AlterField(
             model_name='token',
             name='version',
             field=models.PositiveSmallIntegerField(default=2),
         ),
+
         # Add new key, pepper, and hmac_digest fields for v2 tokens
         migrations.AddField(
             model_name='token',
             name='key',
-            field=models.CharField(blank=True, max_length=16, null=True, unique=True),
+            field=models.CharField(
+                blank=True,
+                max_length=16,
+                null=True,
+                unique=True,
+                validators=[django.core.validators.MinLengthValidator(16)]
+            ),
         ),
         migrations.AddField(
             model_name='token',

+ 9 - 10
netbox/users/models/tokens.py

@@ -1,8 +1,6 @@
-import binascii
 import hashlib
 import hmac
 import random
-import os
 
 from django.conf import settings
 from django.contrib.postgres.fields import ArrayField
@@ -16,7 +14,7 @@ from netaddr import IPNetwork
 
 from ipam.fields import IPNetworkField
 from users.choices import TokenVersionChoices
-from users.constants import TOKEN_CHARSET
+from users.constants import TOKEN_CHARSET, TOKEN_DEFAULT_LENGTH, TOKEN_KEY_LENGTH
 from users.utils import get_current_pepper
 from utilities.querysets import RestrictedQuerySet
 
@@ -75,10 +73,11 @@ class Token(models.Model):
     )
     key = models.CharField(
         verbose_name=_('key'),
-        max_length=16,
+        max_length=TOKEN_KEY_LENGTH,
         unique=True,
         blank=True,
         null=True,
+        validators=[MinLengthValidator(TOKEN_KEY_LENGTH)],
         help_text=_('v2 token identification key'),
     )
     pepper = models.PositiveSmallIntegerField(
@@ -148,7 +147,7 @@ class Token(models.Model):
             if self.v1:
                 self.plaintext = value
             elif self.v2:
-                self.key = self.key or self.generate(16)
+                self.key = self.key or self.generate_key()
                 self.update_digest()
 
     def clean(self):
@@ -162,15 +161,15 @@ class Token(models.Model):
 
         return super().save(*args, **kwargs)
 
-    @staticmethod
-    def generate_key():
+    @classmethod
+    def generate_key(cls):
         """
-        DEPRECATED: Generate and return a random 160-bit key expressed in hexadecimal.
+        Generate and return a random alphanumeric key for v2 tokens.
         """
-        return binascii.hexlify(os.urandom(20)).decode()
+        return cls.generate(length=TOKEN_KEY_LENGTH)
 
     @staticmethod
-    def generate(length=40):
+    def generate(length=TOKEN_DEFAULT_LENGTH):
         """
         Generate and return a random token value of the given length.
         """