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): def has_permission(self, request, view):
# User must be authenticated
if not request.user.is_authenticated:
return False
# Enforce Token write ability # Enforce Token write ability
if isinstance(request.auth, Token) and not self._verify_write_permission(request): if isinstance(request.auth, Token) and not self._verify_write_permission(request):
return False return False

View File

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

View File

@ -3,10 +3,18 @@ from django.contrib.contenttypes.models import ContentType
from rest_framework import serializers from rest_framework import serializers
from netbox.api import ContentTypeField, SerializedPKRelatedField, ValidatedModelSerializer from netbox.api import ContentTypeField, SerializedPKRelatedField, ValidatedModelSerializer
from users.models import ObjectPermission from users.models import ObjectPermission, Token
from .nested_serializers import * from .nested_serializers import *
__all__ = (
'GroupSerializer',
'ObjectPermissionSerializer',
'TokenSerializer',
'UserSerializer',
)
class UserSerializer(ValidatedModelSerializer): class UserSerializer(ValidatedModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='users-api:user-detail') url = serializers.HyperlinkedIdentityField(view_name='users-api:user-detail')
groups = SerializedPKRelatedField( groups = SerializedPKRelatedField(
@ -47,6 +55,21 @@ class GroupSerializer(ValidatedModelSerializer):
fields = ('id', 'url', 'display', 'name', 'user_count') 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): class ObjectPermissionSerializer(ValidatedModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='users-api:objectpermission-detail') url = serializers.HyperlinkedIdentityField(view_name='users-api:objectpermission-detail')
object_types = ContentTypeField( object_types = ContentTypeField(

View File

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

View File

@ -7,7 +7,7 @@ from rest_framework.viewsets import ViewSet
from netbox.api.views import ModelViewSet from netbox.api.views import ModelViewSet
from users import filtersets from users import filtersets
from users.models import ObjectPermission, UserConfig from users.models import ObjectPermission, Token, UserConfig
from utilities.querysets import RestrictedQuerySet from utilities.querysets import RestrictedQuerySet
from utilities.utils import deepmerge from utilities.utils import deepmerge
from . import serializers from . import serializers
@ -37,6 +37,25 @@ class GroupViewSet(ModelViewSet):
filterset_class = filtersets.GroupFilterSet 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 # ObjectPermissions
# #

View File

@ -3,7 +3,7 @@ from django.contrib.auth.models import Group, User
from django.db.models import Q from django.db.models import Q
from netbox.filtersets import BaseFilterSet from netbox.filtersets import BaseFilterSet
from users.models import ObjectPermission from users.models import ObjectPermission, Token
__all__ = ( __all__ = (
'GroupFilterSet', '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): class ObjectPermissionFilterSet(BaseFilterSet):
user_id = django_filters.ModelMultipleChoiceFilter( user_id = django_filters.ModelMultipleChoiceFilter(
field_name='users', field_name='users',

View File

@ -216,7 +216,8 @@ class Token(BigIDModel):
self.key = self.generate_key() self.key = self.generate_key()
return super().save(*args, **kwargs) return super().save(*args, **kwargs)
def generate_key(self): @staticmethod
def generate_key():
# Generate a random 160-bit key expressed in hexadecimal. # Generate a random 160-bit key expressed in hexadecimal.
return binascii.hexlify(os.urandom(20)).decode() 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.contrib.contenttypes.models import ContentType
from django.urls import reverse from django.urls import reverse
from users.models import ObjectPermission from users.models import ObjectPermission, Token
from utilities.testing import APIViewTestCases, APITestCase from utilities.testing import APIViewTestCases, APITestCase
from utilities.utils import deepmerge from utilities.utils import deepmerge
@ -75,6 +75,38 @@ class GroupTest(APIViewTestCases.APIViewTestCase):
Group.objects.bulk_create(users) 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): class ObjectPermissionTest(APIViewTestCases.APIViewTestCase):
model = ObjectPermission model = ObjectPermission
brief_fields = ['actions', 'display', 'enabled', 'groups', 'id', 'name', 'object_types', 'url', 'users'] brief_fields = ['actions', 'display', 'enabled', 'groups', 'id', 'name', 'object_types', 'url', 'users']