[RFC PATCH] REST: enable token authentication

Russell Currey ruscur at russell.cc
Tue May 30 16:26:07 AEST 2017


On Thu, 2017-05-25 at 22:26 +0100, Stephen Finucane wrote:
> On Thu, 2017-05-25 at 18:47 +1000, Andrew Donnellan wrote:
> > 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>
> 

FWIW it looks good to me.

> Yup, totally onboard with this, even for 2.0. I'd be in favor of
> removing Basic Auth if we add this. I can take documentation if you
> want to handle the other items. I imagine tests shouldn't be too
> arduous either.

What exactly does BA get used for at the moment?

- Russell

> 
> Think this is something you could wire up in the next few days? :)
> 
> Stephen
> 
> > ---
> > 
> > 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'))
> 
> _______________________________________________
> Patchwork mailing list
> Patchwork at lists.ozlabs.org
> https://lists.ozlabs.org/listinfo/patchwork



More information about the Patchwork mailing list