Files
netbox/netbox/users/api/serializers_/tokens.py
Martin Hauser 513b11450d
Some checks failed
CI / build (20.x, 3.12) (push) Has been cancelled
CI / build (20.x, 3.13) (push) Has been cancelled
CodeQL / Analyze (${{ matrix.language }}) (none, actions) (push) Has been cancelled
CodeQL / Analyze (${{ matrix.language }}) (none, javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (${{ matrix.language }}) (none, python) (push) Has been cancelled
Closes #20834: Add support for enabling/disabling Tokens (#20864)
* feat(users): Add support for enabling/disabling Tokens

Introduce an `enabled` flag on the `Token` model to allow temporarily
revoking API tokens without deleting them. Update forms, serializers,
and views to expose the new field.
Enforce the `enabled` flag in token authentication.
Add model, API, and authentication tests for the new behavior.

Fixes #20834

* Fix authentication test

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
2025-11-26 17:15:14 -05:00

97 lines
2.9 KiB
Python

from django.contrib.auth import authenticate
from rest_framework import serializers
from rest_framework.exceptions import AuthenticationFailed, PermissionDenied
from netbox.api.fields import IPNetworkSerializer
from netbox.api.serializers import ValidatedModelSerializer
from users.models import Token
from .users import *
__all__ = (
'TokenProvisionSerializer',
'TokenSerializer',
)
class TokenSerializer(ValidatedModelSerializer):
token = serializers.CharField(
required=False,
default=Token.generate,
)
user = UserSerializer(
nested=True
)
allowed_ips = serializers.ListField(
child=IPNetworkSerializer(),
required=False,
allow_empty=True,
default=[]
)
class Meta:
model = Token
fields = (
'id', 'url', 'display_url', 'display', 'version', 'key', 'user', 'description', 'created', 'expires',
'last_used', 'enabled', 'write_enabled', 'pepper_id', 'allowed_ips', 'token',
)
read_only_fields = ('key',)
brief_fields = ('id', 'url', 'display', 'version', 'key', 'enabled', 'write_enabled', 'description')
def get_fields(self):
fields = super().get_fields()
# Make user field read-only if updating an existing Token.
if self.instance is not None:
fields['user'].read_only = True
return fields
def validate(self, data):
# If the Token is being created on behalf of another user, enforce the grant_token permission.
request = self.context.get('request')
token_user = data.get('user')
if token_user and token_user != request.user and not request.user.has_perm('users.grant_token'):
raise PermissionDenied("This user does not have permission to create tokens for other users.")
return super().validate(data)
class TokenProvisionSerializer(TokenSerializer):
user = UserSerializer(
nested=True,
read_only=True
)
username = serializers.CharField(
write_only=True
)
password = serializers.CharField(
write_only=True
)
last_used = serializers.DateTimeField(
read_only=True
)
key = serializers.CharField(
read_only=True
)
class Meta:
model = Token
fields = (
'id', 'url', 'display_url', 'display', 'version', 'user', 'key', 'created', 'expires', 'last_used', 'key',
'enabled', 'write_enabled', 'description', 'allowed_ips', 'username', 'password', 'token',
)
def validate(self, data):
# Validate the username and password
username = data.pop('username')
password = data.pop('password')
user = authenticate(request=self.context.get('request'), username=username, password=password)
if user is None:
raise AuthenticationFailed("Invalid username/password")
# Inject the user into the validated data
data['user'] = user
return data