mirror of
https://github.com/netbox-community/netbox.git
synced 2025-12-15 04:49:36 -06:00
Enforce a fixed key length for v2 tokens
This commit is contained in:
parent
1ee23ba6fa
commit
5dc48f3a88
@ -10,4 +10,7 @@ OBJECTPERMISSION_OBJECT_TYPES = Q(
|
|||||||
|
|
||||||
CONSTRAINT_TOKEN_USER = '$user'
|
CONSTRAINT_TOKEN_USER = '$user'
|
||||||
|
|
||||||
|
# API tokens
|
||||||
|
TOKEN_KEY_LENGTH = 16
|
||||||
|
TOKEN_DEFAULT_LENGTH = 40
|
||||||
TOKEN_CHARSET = string.ascii_letters + string.digits
|
TOKEN_CHARSET = string.ascii_letters + string.digits
|
||||||
|
|||||||
@ -21,6 +21,7 @@ class Migration(migrations.Migration):
|
|||||||
migrations.RunSQL(
|
migrations.RunSQL(
|
||||||
sql="ALTER INDEX IF EXISTS users_token_key_key RENAME TO users_token_plaintext_key",
|
sql="ALTER INDEX IF EXISTS users_token_key_key RENAME TO users_token_plaintext_key",
|
||||||
),
|
),
|
||||||
|
|
||||||
# Make plaintext (formerly key) nullable for v2 tokens
|
# Make plaintext (formerly key) nullable for v2 tokens
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='token',
|
model_name='token',
|
||||||
@ -33,6 +34,7 @@ class Migration(migrations.Migration):
|
|||||||
validators=[django.core.validators.MinLengthValidator(40)]
|
validators=[django.core.validators.MinLengthValidator(40)]
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
# Add version field to distinguish v1 and v2 tokens
|
# Add version field to distinguish v1 and v2 tokens
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='token',
|
model_name='token',
|
||||||
@ -40,17 +42,25 @@ class Migration(migrations.Migration):
|
|||||||
field=models.PositiveSmallIntegerField(default=1), # Mark all existing Tokens as v1
|
field=models.PositiveSmallIntegerField(default=1), # Mark all existing Tokens as v1
|
||||||
preserve_default=False,
|
preserve_default=False,
|
||||||
),
|
),
|
||||||
|
|
||||||
# Change the default version for new tokens to v2
|
# Change the default version for new tokens to v2
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='token',
|
model_name='token',
|
||||||
name='version',
|
name='version',
|
||||||
field=models.PositiveSmallIntegerField(default=2),
|
field=models.PositiveSmallIntegerField(default=2),
|
||||||
),
|
),
|
||||||
|
|
||||||
# Add new key, pepper, and hmac_digest fields for v2 tokens
|
# Add new key, pepper, and hmac_digest fields for v2 tokens
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='token',
|
model_name='token',
|
||||||
name='key',
|
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(
|
migrations.AddField(
|
||||||
model_name='token',
|
model_name='token',
|
||||||
|
|||||||
@ -1,8 +1,6 @@
|
|||||||
import binascii
|
|
||||||
import hashlib
|
import hashlib
|
||||||
import hmac
|
import hmac
|
||||||
import random
|
import random
|
||||||
import os
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.postgres.fields import ArrayField
|
from django.contrib.postgres.fields import ArrayField
|
||||||
@ -16,7 +14,7 @@ from netaddr import IPNetwork
|
|||||||
|
|
||||||
from ipam.fields import IPNetworkField
|
from ipam.fields import IPNetworkField
|
||||||
from users.choices import TokenVersionChoices
|
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 users.utils import get_current_pepper
|
||||||
from utilities.querysets import RestrictedQuerySet
|
from utilities.querysets import RestrictedQuerySet
|
||||||
|
|
||||||
@ -75,10 +73,11 @@ class Token(models.Model):
|
|||||||
)
|
)
|
||||||
key = models.CharField(
|
key = models.CharField(
|
||||||
verbose_name=_('key'),
|
verbose_name=_('key'),
|
||||||
max_length=16,
|
max_length=TOKEN_KEY_LENGTH,
|
||||||
unique=True,
|
unique=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
|
validators=[MinLengthValidator(TOKEN_KEY_LENGTH)],
|
||||||
help_text=_('v2 token identification key'),
|
help_text=_('v2 token identification key'),
|
||||||
)
|
)
|
||||||
pepper = models.PositiveSmallIntegerField(
|
pepper = models.PositiveSmallIntegerField(
|
||||||
@ -148,7 +147,7 @@ class Token(models.Model):
|
|||||||
if self.v1:
|
if self.v1:
|
||||||
self.plaintext = value
|
self.plaintext = value
|
||||||
elif self.v2:
|
elif self.v2:
|
||||||
self.key = self.key or self.generate(16)
|
self.key = self.key or self.generate_key()
|
||||||
self.update_digest()
|
self.update_digest()
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
@ -162,15 +161,15 @@ class Token(models.Model):
|
|||||||
|
|
||||||
return super().save(*args, **kwargs)
|
return super().save(*args, **kwargs)
|
||||||
|
|
||||||
@staticmethod
|
@classmethod
|
||||||
def generate_key():
|
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
|
@staticmethod
|
||||||
def generate(length=40):
|
def generate(length=TOKEN_DEFAULT_LENGTH):
|
||||||
"""
|
"""
|
||||||
Generate and return a random token value of the given length.
|
Generate and return a random token value of the given length.
|
||||||
"""
|
"""
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user