Просмотр исходного кода

Add a REST API endpoint to provision new tokens using username & password

jeremystretch 4 лет назад
Родитель
Сommit
b038b1f613

+ 5 - 0
netbox/users/api/serializers.py

@@ -70,6 +70,11 @@ class TokenSerializer(ValidatedModelSerializer):
         return super().to_internal_value(data)
 
 
+class TokenProvisionSerializer(serializers.Serializer):
+    username = serializers.CharField()
+    password = serializers.CharField()
+
+
 class ObjectPermissionSerializer(ValidatedModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='users-api:objectpermission-detail')
     object_types = ContentTypeField(

+ 6 - 1
netbox/users/api/urls.py

@@ -1,3 +1,5 @@
+from django.urls import include, path
+
 from netbox.api import OrderedDefaultRouter
 from . import views
 
@@ -19,4 +21,7 @@ router.register('permissions', views.ObjectPermissionViewSet)
 router.register('config', views.UserConfigViewSet, basename='userconfig')
 
 app_name = 'users-api'
-urlpatterns = router.urls
+urlpatterns = [
+    path('tokens/provision/', views.TokenProvisionView.as_view(), name='token_provision'),
+    path('', include(router.urls)),
+]

+ 32 - 0
netbox/users/api/views.py

@@ -1,8 +1,12 @@
+from django.contrib.auth import authenticate
 from django.contrib.auth.models import Group, User
 from django.db.models import Count
+from rest_framework.exceptions import AuthenticationFailed
 from rest_framework.permissions import IsAuthenticated
 from rest_framework.response import Response
 from rest_framework.routers import APIRootView
+from rest_framework.status import HTTP_201_CREATED
+from rest_framework.views import APIView
 from rest_framework.viewsets import ViewSet
 
 from netbox.api.views import ModelViewSet
@@ -56,6 +60,34 @@ class TokenViewSet(ModelViewSet):
         return queryset.filter(user=self.request.user)
 
 
+class TokenProvisionView(APIView):
+    """
+    Non-authenticated REST API endpoint via which a user may create a Token.
+    """
+    permission_classes = []
+    swagger_schema = None  # TODO: Generate a schema
+
+    def post(self, request):
+        serializer = serializers.TokenProvisionSerializer(data=request.data)
+        serializer.is_valid()
+
+        # Authenticate the user account based on the provided credentials
+        user = authenticate(
+            request=request,
+            username=serializer.data['username'],
+            password=serializer.data['password']
+        )
+        if user is None:
+            raise AuthenticationFailed("Invalid username/password")
+
+        # Create a new Token for the User
+        token = Token(user=user)
+        token.save()
+        data = serializers.TokenSerializer(token, context={'request': request}).data
+
+        return Response(data, status=HTTP_201_CREATED)
+
+
 #
 # ObjectPermissions
 #

+ 31 - 0
netbox/users/tests/test_api.py

@@ -106,6 +106,37 @@ class TokenTest(APIViewTestCases.APIViewTestCase):
             },
         ]
 
+    def test_provision_token_valid(self):
+        """
+        Test the provisioning of a new REST API token given a valid username and password.
+        """
+        data = {
+            'username': 'user1',
+            'password': 'abc123',
+        }
+        user = User.objects.create_user(**data)
+        url = reverse('users-api:token_provision')
+
+        response = self.client.post(url, **self.header, data=data)
+        self.assertEqual(response.status_code, 201)
+        self.assertIn('key', response.data)
+        self.assertEqual(len(response.data['key']), 40)
+        token = Token.objects.get(user=user)
+        self.assertEqual(token.key, response.data['key'])
+
+    def test_provision_token_invalid(self):
+        """
+        Test the behavior of the token provisioning view when invalid credentials are supplied.
+        """
+        data = {
+            'username': 'nonexistentuser',
+            'password': 'abc123',
+        }
+        url = reverse('users-api:token_provision')
+
+        response = self.client.post(url, **self.header, data=data)
+        self.assertEqual(response.status_code, 403)
+
 
 class ObjectPermissionTest(APIViewTestCases.APIViewTestCase):
     model = ObjectPermission