-
-
Notifications
You must be signed in to change notification settings - Fork 20
Expand file tree
/
Copy pathmodels.py
More file actions
103 lines (84 loc) · 3.17 KB
/
models.py
File metadata and controls
103 lines (84 loc) · 3.17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
#
# Copyright (c) nexB Inc. and others. All rights reserved.
# DejaCode is a trademark of nexB Inc.
# SPDX-License-Identifier: AGPL-3.0-only
# See https://github.com/aboutcode-org/dejacode for support or download.
# See https://aboutcode.org for more information about AboutCode FOSS projects.
#
import secrets
from django.apps import apps as django_apps
from django.conf import settings
from django.contrib.auth.hashers import check_password
from django.contrib.auth.hashers import make_password
from django.core.exceptions import ImproperlyConfigured
from django.db import models
class AbstractAPIToken(models.Model):
"""
API token using a lookup prefix and PBKDF2 hash for secure verification.
The full key is never stored. Only a short plain-text prefix is kept for
DB lookup, and a hashed version of the full key is stored for verification.
The plain key is returned once at generation time and must be stored safely
by the client.
"""
PREFIX_LENGTH = 8
key_hash = models.CharField(
max_length=128,
)
user = models.OneToOneField(
settings.AUTH_USER_MODEL,
related_name="api_token",
on_delete=models.CASCADE,
)
prefix = models.CharField(
max_length=PREFIX_LENGTH,
unique=True,
db_index=True,
)
created = models.DateTimeField(
auto_now_add=True,
db_index=True,
)
class Meta:
abstract = True
def __str__(self):
return f"APIToken {self.prefix}... ({self.user})"
@classmethod
def generate_key(cls):
"""Generate a plain (not encrypted) key."""
return secrets.token_hex(32)
def set_key(self, plain_key):
"""Set the prefix and hashed key from a plain key. Does not save."""
self.prefix = plain_key[: self.PREFIX_LENGTH]
self.key_hash = make_password(plain_key)
@classmethod
def create_token(cls, user):
"""Generate a new token for the given user and return the plain key once."""
plain_key = cls.generate_key()
token = cls(user=user)
token.set_key(plain_key)
token.save()
return plain_key
@classmethod
def verify(cls, plain_key):
"""Return the token instance if the plain key is valid, None otherwise."""
if not plain_key:
return
prefix = plain_key[: cls.PREFIX_LENGTH]
token = cls.objects.filter(prefix=prefix).select_related("user").first()
if token and check_password(plain_key, token.key_hash):
return token
@classmethod
def regenerate(cls, user):
"""Delete any existing token instance for the user and generate a new one."""
cls.objects.filter(user=user).delete()
return cls.create_token(user)
@classmethod
def revoke(cls, user):
"""Delete any existing token instance for the user."""
return cls.objects.filter(user=user).delete()
def get_api_token_model():
"""Return the concrete APIToken model from the API_TOKEN_MODEL setting."""
try:
return django_apps.get_model(settings.API_TOKEN_MODEL)
except (ValueError, LookupError):
raise ImproperlyConfigured("API_TOKEN_MODEL is not properly defined.")