1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Initial work on REST API endpoint for tokens

This commit is contained in:
jeremystretch
2021-06-11 15:14:19 -04:00
parent d87ec82fe3
commit 48b4bf1683
8 changed files with 108 additions and 6 deletions

View File

@ -59,6 +59,10 @@ class TokenPermissions(DjangoObjectPermissions):
def has_permission(self, request, view):
# User must be authenticated
if not request.user.is_authenticated:
return False
# Enforce Token write ability
if isinstance(request.auth, Token) and not self._verify_write_permission(request):
return False

View File

@ -3,11 +3,12 @@ from django.contrib.contenttypes.models import ContentType
from rest_framework import serializers
from netbox.api import ContentTypeField, WritableNestedSerializer
from users.models import ObjectPermission
from users.models import ObjectPermission, Token
__all__ = [
'NestedGroupSerializer',
'NestedObjectPermissionSerializer',
'NestedTokenSerializer',
'NestedUserSerializer',
]
@ -28,6 +29,14 @@ class NestedUserSerializer(WritableNestedSerializer):
fields = ['id', 'url', 'display', 'username']
class NestedTokenSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='users-api:token-detail')
class Meta:
model = Token
fields = ['id', 'url', 'display', 'key', 'write_enabled']
class NestedObjectPermissionSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='users-api:objectpermission-detail')
object_types = ContentTypeField(

View File

@ -3,10 +3,18 @@ from django.contrib.contenttypes.models import ContentType
from rest_framework import serializers
from netbox.api import ContentTypeField, SerializedPKRelatedField, ValidatedModelSerializer
from users.models import ObjectPermission
from users.models import ObjectPermission, Token
from .nested_serializers import *
__all__ = (
'GroupSerializer',
'ObjectPermissionSerializer',
'TokenSerializer',
'UserSerializer',
)
class UserSerializer(ValidatedModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='users-api:user-detail')
groups = SerializedPKRelatedField(
@ -47,6 +55,21 @@ class GroupSerializer(ValidatedModelSerializer):
fields = ('id', 'url', 'display', 'name', 'user_count')
class TokenSerializer(ValidatedModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='users-api:token-detail')
key = serializers.CharField(min_length=40, max_length=40, allow_blank=True, required=False)
user = NestedUserSerializer()
class Meta:
model = Token
fields = ('id', 'url', 'display', 'user', 'created', 'expires', 'key', 'write_enabled', 'description')
def to_internal_value(self, data):
if 'key' not in data:
data['key'] = Token.generate_key()
return super().to_internal_value(data)
class ObjectPermissionSerializer(ValidatedModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='users-api:objectpermission-detail')
object_types = ContentTypeField(

View File

@ -9,6 +9,9 @@ router.APIRootView = views.UsersRootView
router.register('users', views.UserViewSet)
router.register('groups', views.GroupViewSet)
# Tokens
router.register('tokens', views.TokenViewSet)
# Permissions
router.register('permissions', views.ObjectPermissionViewSet)

View File

@ -7,7 +7,7 @@ from rest_framework.viewsets import ViewSet
from netbox.api.views import ModelViewSet
from users import filtersets
from users.models import ObjectPermission, UserConfig
from users.models import ObjectPermission, Token, UserConfig
from utilities.querysets import RestrictedQuerySet
from utilities.utils import deepmerge
from . import serializers
@ -37,6 +37,25 @@ class GroupViewSet(ModelViewSet):
filterset_class = filtersets.GroupFilterSet
#
# REST API tokens
#
class TokenViewSet(ModelViewSet):
queryset = RestrictedQuerySet(model=Token).prefetch_related('user')
serializer_class = serializers.TokenSerializer
filterset_class = filtersets.TokenFilterSet
def get_queryset(self):
"""
Limit the non-superusers to their own Tokens.
"""
queryset = super().get_queryset()
if self.request.user.is_superuser:
return queryset
return queryset.filter(user=self.request.user)
#
# ObjectPermissions
#

View File

@ -3,7 +3,7 @@ from django.contrib.auth.models import Group, User
from django.db.models import Q
from netbox.filtersets import BaseFilterSet
from users.models import ObjectPermission
from users.models import ObjectPermission, Token
__all__ = (
'GroupFilterSet',
@ -60,6 +60,17 @@ class UserFilterSet(BaseFilterSet):
)
class TokenFilterSet(BaseFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
)
class Meta:
model = Token
fields = ['id', 'user', 'created', 'expires', 'key', 'write_enabled']
class ObjectPermissionFilterSet(BaseFilterSet):
user_id = django_filters.ModelMultipleChoiceFilter(
field_name='users',

View File

@ -216,7 +216,8 @@ class Token(BigIDModel):
self.key = self.generate_key()
return super().save(*args, **kwargs)
def generate_key(self):
@staticmethod
def generate_key():
# Generate a random 160-bit key expressed in hexadecimal.
return binascii.hexlify(os.urandom(20)).decode()

View File

@ -2,7 +2,7 @@ from django.contrib.auth.models import Group, User
from django.contrib.contenttypes.models import ContentType
from django.urls import reverse
from users.models import ObjectPermission
from users.models import ObjectPermission, Token
from utilities.testing import APIViewTestCases, APITestCase
from utilities.utils import deepmerge
@ -75,6 +75,38 @@ class GroupTest(APIViewTestCases.APIViewTestCase):
Group.objects.bulk_create(users)
class TokenTest(APIViewTestCases.APIViewTestCase):
model = Token
brief_fields = ['display', 'id', 'key', 'url', 'write_enabled']
bulk_update_data = {
'description': 'New description',
}
def setUp(self):
super().setUp()
tokens = (
# We already start with one Token, created by the test class
Token(user=self.user),
Token(user=self.user),
)
# Use save() instead of bulk_create() to ensure keys get automatically generated
for token in tokens:
token.save()
self.create_data = [
{
'user': self.user.pk,
},
{
'user': self.user.pk,
},
{
'user': self.user.pk,
},
]
class ObjectPermissionTest(APIViewTestCases.APIViewTestCase):
model = ObjectPermission
brief_fields = ['actions', 'display', 'enabled', 'groups', 'id', 'name', 'object_types', 'url', 'users']