[RFC PATCH] REST: enable token authentication
Andrew Donnellan
andrew.donnellan at au1.ibm.com
Thu May 25 18:47:18 AEST 2017
Token authentication is generally viewed as a more secure option for API
authentication than storing a username and password.
Django REST Framework gives us a TokenAuthentication class and an authtoken
app that we can use to generate random tokens and authenticate to API
endpoints.
Enable DRF's token support and add options to the user profile view to view
or regenerate tokens.
Signed-off-by: Andrew Donnellan <andrew.donnellan at au1.ibm.com>
---
This is an RFC; I haven't written any tests or documentation, UI's a bit
ugly, need to split patches.
---
patchwork/settings/base.py | 6 ++++++
patchwork/signals.py | 10 ++++++++++
patchwork/templates/patchwork/profile.html | 23 +++++++++++++++++++++--
patchwork/urls.py | 4 ++++
patchwork/views/bundle.py | 12 ++++++++----
patchwork/views/user.py | 19 +++++++++++++++++++
6 files changed, 68 insertions(+), 6 deletions(-)
diff --git a/patchwork/settings/base.py b/patchwork/settings/base.py
index 26c75c9..6fd98a7 100644
--- a/patchwork/settings/base.py
+++ b/patchwork/settings/base.py
@@ -143,6 +143,7 @@ try:
INSTALLED_APPS += [
'rest_framework',
+ 'rest_framework.authtoken',
'django_filters',
]
except ImportError:
@@ -158,6 +159,11 @@ REST_FRAMEWORK = {
'rest_framework.filters.SearchFilter',
'rest_framework.filters.OrderingFilter',
),
+ 'DEFAULT_AUTHENTICATION_CLASSES': (
+ 'rest_framework.authentication.SessionAuthentication',
+ 'rest_framework.authentication.BasicAuthentication',
+ 'rest_framework.authentication.TokenAuthentication',
+ ),
'SEARCH_PARAM': 'q',
'ORDERING_PARAM': 'order',
}
diff --git a/patchwork/signals.py b/patchwork/signals.py
index 208685c..f335525 100644
--- a/patchwork/signals.py
+++ b/patchwork/signals.py
@@ -19,6 +19,7 @@
from datetime import datetime as dt
+from django.conf import settings
from django.db.models.signals import post_save
from django.db.models.signals import pre_save
from django.dispatch import receiver
@@ -239,3 +240,12 @@ def create_series_completed_event(sender, instance, created, **kwargs):
if instance.series.received_all:
create_event(instance.series)
+
+
+if settings.ENABLE_REST_API:
+ from rest_framework.authtoken.models import Token
+ @receiver(post_save, sender=settings.AUTH_USER_MODEL)
+ def create_user_created_event(sender, instance=None, created=False,
+ **kwargs):
+ if created:
+ Token.objects.create(user=instance)
diff --git a/patchwork/templates/patchwork/profile.html b/patchwork/templates/patchwork/profile.html
index f976195..c7be044 100644
--- a/patchwork/templates/patchwork/profile.html
+++ b/patchwork/templates/patchwork/profile.html
@@ -133,8 +133,27 @@ address.</p>
</div>
<div class="box">
-<h2>Authentication</h2>
-<a href="{% url 'password_change' %}">Change password</a>
+ <h2>Authentication</h2>
+ <form method="post" action="{%url 'generate_token' %}">
+ {% csrf_token %}
+ <table>
+ <tr>
+ <th>Password:</th>
+ <td><a href="{% url 'password_change' %}">Change password</a>
+ </tr>
+ <tr>
+ <th>API Token:</th>
+ <td>
+ {% if api_token %}
+ {{ api_token }}
+ <input type="submit" value="Regenerate token" />
+ {% else %}
+ <input type="submit" value="Generate token" />
+ {% endif %}
+ </td>
+ </tr>
+ </table>
+ </form>
</div>
</div>
diff --git a/patchwork/urls.py b/patchwork/urls.py
index 1871c9a..aa49b4d 100644
--- a/patchwork/urls.py
+++ b/patchwork/urls.py
@@ -101,6 +101,10 @@ urlpatterns = [
auth_views.password_reset_complete,
name='password_reset_complete'),
+ # token change
+ url(r'^user/generate-token/$', user_views.generate_token,
+ name='generate_token'),
+
# login/logout
url(r'^user/login/$', auth_views.login,
{'template_name': 'patchwork/login.html'},
diff --git a/patchwork/views/bundle.py b/patchwork/views/bundle.py
index 387b7c6..bb331f4 100644
--- a/patchwork/views/bundle.py
+++ b/patchwork/views/bundle.py
@@ -36,17 +36,21 @@ from patchwork.views import generic_list
from patchwork.views.utils import bundle_to_mbox
if settings.ENABLE_REST_API:
- from rest_framework.authentication import BasicAuthentication # noqa
+ from rest_framework.authentication import SessionAuthentication
from rest_framework.exceptions import AuthenticationFailed
+ from rest_framework.settings import api_settings
def rest_auth(request):
if not settings.ENABLE_REST_API:
return request.user
try:
- auth_result = BasicAuthentication().authenticate(request)
- if auth_result:
- return auth_result[0]
+ for auth in api_settings.DEFAULT_AUTHENTICATION_CLASSES:
+ if auth == SessionAuthentication:
+ continue
+ auth_result = auth().authenticate(request)
+ if auth_result:
+ return auth_result[0]
except AuthenticationFailed:
pass
return request.user
diff --git a/patchwork/views/user.py b/patchwork/views/user.py
index 375d3d9..53e2ea8 100644
--- a/patchwork/views/user.py
+++ b/patchwork/views/user.py
@@ -42,6 +42,8 @@ from patchwork.models import Project
from patchwork.models import State
from patchwork.views import generic_list
+if settings.ENABLE_REST_API:
+ from rest_framework.authtoken.models import Token
def register(request):
context = {}
@@ -126,6 +128,11 @@ def profile(request):
.extra(select={'is_optout': optout_query})
context['linked_emails'] = people
context['linkform'] = EmailForm()
+ if settings.ENABLE_REST_API:
+ try:
+ context['api_token'] = Token.objects.get(user=request.user)
+ except Token.DoesNotExist:
+ pass
return render(request, 'patchwork/profile.html', context)
@@ -232,3 +239,15 @@ def todo_list(request, project_id):
context['action_required_states'] = \
State.objects.filter(action_required=True).all()
return render(request, 'patchwork/todo-list.html', context)
+
+ at login_required
+def generate_token(request):
+ if not settings.ENABLE_REST_API:
+ raise RuntimeError('REST API not enabled')
+ try:
+ t = Token.objects.get(user=request.user)
+ t.delete()
+ except Token.DoesNotExist:
+ pass
+ Token.objects.create(user=request.user)
+ return HttpResponseRedirect(reverse('user-profile'))
--
Andrew Donnellan OzLabs, ADL Canberra
andrew.donnellan at au1.ibm.com IBM Australia Limited
More information about the Patchwork
mailing list