-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a user api route. The first route is `/user/me` to retrieve session user info and a JWT access token which could be used by third party application to make REST requests.
- Loading branch information
Showing
7 changed files
with
150 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
# coding: utf-8 | ||
""" | ||
API user endpoints | ||
""" | ||
from __future__ import absolute_import, unicode_literals | ||
|
||
from django.conf.urls import url | ||
|
||
from ..apps import FonzieConfig | ||
from ..views.user import UserSessionView | ||
|
||
app_name = FonzieConfig.name | ||
urlpatterns = [url(r"^me/?$", UserSessionView.as_view(), name="me")] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
# coding: utf-8 | ||
""" | ||
API user views | ||
""" | ||
|
||
from __future__ import absolute_import, unicode_literals | ||
|
||
from datetime import datetime | ||
|
||
import jwt | ||
from rest_framework.permissions import IsAuthenticated | ||
from rest_framework.response import Response | ||
from rest_framework.views import APIView | ||
|
||
from django.conf import settings | ||
|
||
|
||
class UserSessionView(APIView): | ||
"""API endpoint to get the authenticated user information.""" | ||
|
||
permission_classes = [IsAuthenticated] | ||
|
||
# pylint: disable=redefined-builtin | ||
def get(self, request, version, format=None): | ||
""" | ||
Retrieve logged in user, then generate a JWT with a claim containing its | ||
username (unique identifier) and its email. The token's expiration is | ||
synchronized with the user session expiration date. | ||
""" | ||
user = request.user | ||
issued_at = datetime.utcnow() | ||
expired_at = request.session.get_expiry_date() | ||
token = jwt.encode( | ||
{ | ||
"email": user.email, | ||
"username": user.username, | ||
"exp": expired_at, | ||
"iat": issued_at, | ||
}, | ||
getattr(settings, "JWT_PRIVATE_SIGNING_KEY", None), | ||
algorithm="HS256", | ||
) | ||
|
||
return Response( | ||
{ | ||
"access_token": token, | ||
"username": user.username, | ||
} | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
# coding: utf-8 | ||
""" | ||
Tests for the `fonzie` user module. | ||
""" | ||
|
||
# pylint: disable=no-member,import-error | ||
from __future__ import absolute_import, unicode_literals | ||
|
||
import jwt | ||
from rest_framework import status | ||
from rest_framework.test import APITestCase | ||
|
||
from django.conf import settings | ||
from django.core.urlresolvers import reverse | ||
|
||
from student.tests.factories import UserFactory | ||
|
||
|
||
class UserViewTestCase(APITestCase): | ||
"""Tests for the User API endpoint""" | ||
|
||
def setUp(self): | ||
""" | ||
Set common parameters for the test suite. | ||
""" | ||
super(UserViewTestCase, self).setUp() | ||
|
||
self.url = reverse("fonzie:user:me", kwargs={"version": "1.0"}) | ||
|
||
def test_user_me_with_anonymous_user(self): | ||
""" | ||
If user is not authenticated, view should return a 403 status | ||
""" | ||
response = self.client.get(self.url) | ||
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) | ||
|
||
def test_user_me_with_logged_in_user(self): | ||
""" | ||
If user is authenticated through Django session, view should return | ||
a JSON object containing the username and a JWT access token | ||
""" | ||
user = UserFactory(username="fonzie", email="arthur_fonzarelli@fun-mooc.fr") | ||
self.client.force_authenticate(user=user) | ||
|
||
response = self.client.get(self.url) | ||
token = jwt.decode( | ||
response.data["access_token"], | ||
getattr( | ||
settings, | ||
"JWT_PRIVATE_SIGNING_KEY", | ||
"ThisIsAnExampleKeyForDevPurposeOnly", | ||
), | ||
options={"require": ["exp", "iat", "email", "username"]}, | ||
) | ||
|
||
self.assertEqual(response.status_code, status.HTTP_200_OK) | ||
self.assertEqual(response.data["username"], "fonzie") | ||
self.assertEqual(token["username"], "fonzie") | ||
self.assertEqual(token["email"], "arthur_fonzarelli@fun-mooc.fr") | ||
self.assertIsInstance(token["iat"], int) | ||
self.assertIsInstance(token["exp"], int) |