diff --git a/jwt_devices/serializers.py b/jwt_devices/serializers.py index 79ad34c..e20782d 100644 --- a/jwt_devices/serializers.py +++ b/jwt_devices/serializers.py @@ -8,6 +8,7 @@ from jwt_devices.models import Device from jwt_devices.settings import api_settings +from jwt_devices.utils import get_device_details User = get_user_model() @@ -39,15 +40,7 @@ def validate(self, attrs): "user": user } if api_settings.JWT_PERMANENT_TOKEN_AUTH: - headers = self.context["request"].META - device_name = headers.get("HTTP_X_DEVICE_MODEL") - user_agent = headers.get("HTTP_USER_AGENT", "") - if not device_name: - device_name = user_agent - device_details = "" - else: - device_details = user_agent - + device_name, device_details = get_device_details(self.context["request"].META) device = Device.objects.create( user=user, last_request_datetime=timezone.now(), name=device_name, details=device_details) diff --git a/jwt_devices/utils.py b/jwt_devices/utils.py index 8af7f75..48eefa7 100644 --- a/jwt_devices/utils.py +++ b/jwt_devices/utils.py @@ -18,7 +18,8 @@ def jwt_devices_get_secret_key(payload=None): def jwt_devices_payload_handler(user, device=None): payload = jwt_payload_handler(user) - payload["device_id"] = str(device.pk) + if device: + payload["device_id"] = str(device.pk) return payload @@ -61,3 +62,15 @@ def jwt_devices_decode_handler(token): issuer=rfj_settings.JWT_ISSUER, algorithms=[rfj_settings.JWT_ALGORITHM] ) + + +def get_device_details(headers): + device_name = headers.get("HTTP_X_DEVICE_MODEL") + user_agent = headers.get("HTTP_USER_AGENT", "") + if not device_name: + device_name = user_agent + device_details = "" + else: + device_details = user_agent + + return device_name, device_details diff --git a/jwt_devices/views.py b/jwt_devices/views.py index 7bb48fc..83ebed9 100644 --- a/jwt_devices/views.py +++ b/jwt_devices/views.py @@ -2,7 +2,7 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import mixins, status, viewsets -from rest_framework.exceptions import NotFound +from rest_framework.exceptions import NotFound, ValidationError from rest_framework.generics import DestroyAPIView from rest_framework.permissions import AllowAny, IsAuthenticated from rest_framework.response import Response @@ -57,6 +57,7 @@ def post(self, request, *args, **kwargs): class DeviceRefreshJSONWebToken(APIView): """Refresh JWT token API View used to refresh JSON Web Token using permanent token. + The DeviceRefreshJSONWebToken view requires the Permanent-Token header to be set in the request headers. """ serializer_class = DeviceTokenRefreshSerializer permission_classes = [AllowAny] @@ -69,15 +70,17 @@ def post(self, request): class DeviceLogout(DestroyAPIView): - """ - Logout user by deleting Device. + """Logout user by deleting Device. + The DeviceLogout view requires the Device-Id header to be set in the request headers. """ queryset = Device.objects.all() permission_classes = [IsAuthenticated] def get_object(self): try: - return self.get_queryset().get(user=self.request.user, id=self.request.META.get("HTTP_DEVICE_ID")) + return self.get_queryset().get(user=self.request.user, id=self.request.META["HTTP_DEVICE_ID"]) + except KeyError: + raise ValidationError(_("Device-Id header must be present in the request headers.")) except Device.DoesNotExist: raise NotFound(_("Device does not exist.")) diff --git a/tests/test_views.py b/tests/test_views.py index 1f2abf9..1cbc887 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -78,51 +78,62 @@ def setUp(self): self.second_user = User.objects.create_user( self.username + "2", self.email + "2", self.password) - def test_logout_view(self): - client = APIClient() - + def _create_device(self, client, device_name="Test", user_data=None): # create device - headers = {"HTTP_X_DEVICE_MODEL": "Android 123"} + headers = {"HTTP_X_DEVICE_MODEL": device_name} client.credentials(**headers) - response = client.post("/auth-token/", self.data, format="json") + response = client.post("/auth-token/", user_data if user_data else self.data, format="json") self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(Device.objects.all().count(), 1) - device_id = response.data["device_id"] + return response.data - headers["HTTP_AUTHORIZATION"] = "JWT {}".format(response.data["token"]) - headers["HTTP_DEVICE_ID"] = device_id - client.credentials(**headers) - client.login(**self.data) + def test_empty_device_id(self): + client = APIClient() + + token = self._create_device(client)["token"] + client.credentials(HTTP_AUTHORIZATION="JWT {}".format(token)) + self.assertEqual(Device.objects.all().count(), 1) response = client.delete("/device-logout/", format="json") - self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) - self.assertEqual(Device.objects.all().count(), 0) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(Device.objects.all().count(), 1) def test_logout_unknown_device(self): client = APIClient() # create a few devices - headers = {"HTTP_X_DEVICE_MODEL": "Android 123"} - client.credentials(**headers) - response = client.post("/auth-token/", self.data, format="json") - self.assertEqual(response.status_code, status.HTTP_200_OK) - token = response.data["token"] - - headers["HTTP_X_DEVICE_MODEL"] = "Nokia" - client.credentials(**headers) - response = client.post("/auth-token/", {"username": self.second_user.username, "password": self.password}, - format="json") - self.assertEqual(response.status_code, status.HTTP_200_OK) + token = self._create_device(client, "Android 123")["token"] + data = self._create_device( + client, "Nokia", user_data={"username": self.second_user.username, "password": self.password}) self.assertEqual(Device.objects.all().count(), 2) - device_id = response.data["device_id"] + device_id = data["device_id"] - headers["HTTP_AUTHORIZATION"] = "JWT {}".format(token) - headers["HTTP_DEVICE_ID"] = device_id + headers = { + "HTTP_AUTHORIZATION": "JWT {}".format(token), + "HTTP_DEVICE_ID": device_id + } client.credentials(**headers) client.login(**self.data) response = client.delete("/device-logout/", format="json") self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) self.assertEqual(Device.objects.all().count(), 2) + def test_logout_view(self): + client = APIClient() + + # create device + data = self._create_device(client, "Android 123") + self.assertEqual(Device.objects.all().count(), 1) + device_id = data["device_id"] + + headers = { + "HTTP_AUTHORIZATION": "JWT {}".format(data["token"]), + "HTTP_DEVICE_ID": device_id + } + client.credentials(**headers) + client.login(**self.data) + response = client.delete("/device-logout/", format="json") + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + self.assertEqual(Device.objects.all().count(), 0) + class DeviceRefreshTokenViewsTests(BaseTestCase): def setUp(self):