From 5fb6911960fe8e1803d2d6c05eb536482a8b49ae Mon Sep 17 00:00:00 2001 From: Evert Date: Mon, 13 Nov 2017 19:38:51 +0200 Subject: [PATCH] Authentication - Rewrite auth system to use Django's user model --- EpisodesCommunity/settings.py | 5 +++ LandingPage/admin.py | 2 +- LandingPage/backends.py | 64 +++++++++++++++++++++++++++++++++ LandingPage/models.py | 24 +++++++------ LandingPage/templates/base.html | 4 +-- LandingPage/views.py | 54 +++++++--------------------- 6 files changed, 98 insertions(+), 55 deletions(-) create mode 100644 LandingPage/backends.py diff --git a/EpisodesCommunity/settings.py b/EpisodesCommunity/settings.py index 8b9743e..6dd7465 100644 --- a/EpisodesCommunity/settings.py +++ b/EpisodesCommunity/settings.py @@ -42,6 +42,11 @@ ALLOWED_HOSTS = [] # Application definition +AUTHENTICATION_BACKENDS = ( + 'LandingPage.backends.OAuthBackend', + 'django.contrib.auth.backends.ModelBackend', +) + INSTALLED_APPS = [ 'LandingPage.apps.LandingpageConfig', 'Show.apps.ShowConfig', diff --git a/LandingPage/admin.py b/LandingPage/admin.py index 77fe1f1..5515ecf 100644 --- a/LandingPage/admin.py +++ b/LandingPage/admin.py @@ -3,7 +3,7 @@ from .models import * # Register your models here. admin.site.register(Show) -admin.site.register(User) +admin.site.register(ExternalUser) admin.site.register(Admin) admin.site.register(Ban) admin.site.register(ShowModerator) diff --git a/LandingPage/backends.py b/LandingPage/backends.py new file mode 100644 index 0000000..be13a4c --- /dev/null +++ b/LandingPage/backends.py @@ -0,0 +1,64 @@ +import requests +import hashlib +import json +import logging +from django.conf import settings +from django.contrib.auth import get_user_model +from django.contrib.auth.backends import ModelBackend +from .models import ExternalUser + +class OAuthBackend(ModelBackend): + def authenticate(self, code=None): + resp = requests.post( + settings.AUTH_TOKEN_ENDPOINT+"token", + data={ + 'grant_type':'authorization_code', + 'code':code, + 'redirect_uri':settings.AUTH_REDIRECT_URL, + 'client_id':settings.AUTH_CLIENT_ID + }, + headers = { + 'Authorization':'Basic %s'%settings.AUTH_B64 + } + ) + resp_json = resp.json() + if 'error' in resp_json: + logging.warn('OAuth server returned an error: %s'%json.dumps(resp_json)) + else: + user_info = requests.get( + settings.AUTH_TOKEN_ENDPOINT+"user", + headers = { + 'Authorization': 'Bearer ' + resp_json['access_token'] + } + ).json() + + usermodel = get_user_model() + matches = usermodel.objects.filter(externaluser__icy_id=user_info['uuid']) + match = None + + if not len(matches): + user = usermodel.objects.create_user( + username = user_info['username'], + email = user_info['email'], + ) + + if 'privilege' in user_info: + priv = user_info['privilege'] + user.is_superuser = (priv == 5) + user.is_staff = (priv > 0) + + user.save() + user.externaluser = ExternalUser( + user = user, + icy_id = user_info['uuid'], + display_name = user_info['display_name'] + ) + user.externaluser.save() + match = user + else: + match = matches[0] + + match.access_token = resp_json['access_token'] + + return match + return None diff --git a/LandingPage/models.py b/LandingPage/models.py index e03a920..309acf8 100644 --- a/LandingPage/models.py +++ b/LandingPage/models.py @@ -1,4 +1,5 @@ from django.db import models +from django.contrib.auth.models import User from django.core.files.storage import FileSystemStorage from django.conf import settings import os @@ -71,14 +72,17 @@ class Show(TimestampedModel): def __str__(self): return '%s [%s]'%(self.name,self.abbr) -class User(TimestampedModel): - user_id = models.CharField( +class ExternalUser(TimestampedModel): + user = models.OneToOneField( + User, + on_delete=models.CASCADE, + help_text='The internal Django user', + verbose_name="User" + ) + icy_id = models.CharField( max_length=36, help_text='The UUID assigned to this user by IcyNet\'s auth servers' ) - email = models.EmailField( - help_text='This user\'s email address' - ) display_name=models.CharField( max_length=20, help_text="The name shown to other users", @@ -95,7 +99,7 @@ class User(TimestampedModel): through='Watch' ) def __str__(self): - return self.email + return 'External for %s (%s)'%(self.user.email, self.display_name) class Admin(User): pass @@ -285,7 +289,7 @@ class Submission(TimestampedModel): verbose_name='Submitted For' ) user = models.ForeignKey( - 'User', + User, on_delete=models.SET_NULL, null=True, related_name='submissions', @@ -309,7 +313,7 @@ class SubmissionVote(TimestampedModel): help_text='What this submission was cast on' ) user = models.ForeignKey( - 'User', + User, on_delete=models.CASCADE, related_name='votes', help_text='The user who cast this vote' @@ -322,7 +326,7 @@ class SubmissionVote(TimestampedModel): class Favorite(TimestampedModel): user = models.ForeignKey( - User, + ExternalUser, on_delete=models.CASCADE ) episode = models.ForeignKey( @@ -334,7 +338,7 @@ class Favorite(TimestampedModel): class Watch(TimestampedModel): user = models.ForeignKey( - User, + ExternalUser, on_delete=models.CASCADE ) episode = models.ForeignKey( diff --git a/LandingPage/templates/base.html b/LandingPage/templates/base.html index 9d3499a..d729237 100644 --- a/LandingPage/templates/base.html +++ b/LandingPage/templates/base.html @@ -20,8 +20,8 @@
- {% if request.session.user_id %} - {{ request.session.disp_name }} + {% if user.is_authenticated %} + {{ user.externaluser.display_name }} {% else %} Log in {% endif %} diff --git a/LandingPage/views.py b/LandingPage/views.py index 84eeb7f..0c277f2 100644 --- a/LandingPage/views.py +++ b/LandingPage/views.py @@ -1,6 +1,7 @@ from django.shortcuts import render from django.views import View from django.views.generic.base import TemplateView +from django.contrib.auth import login as auth_login, authenticate from django.conf import settings from django.http import HttpResponse from django.http import HttpResponseRedirect @@ -29,52 +30,21 @@ class LoginRedirect(View): userstate = generateState(req) if userstate == req.GET['state']: code = req.GET['code'] - resp = requests.post( - settings.AUTH_TOKEN_ENDPOINT+"token", - data={ - 'grant_type':'authorization_code', - 'code':code, - 'redirect_uri':settings.AUTH_REDIRECT_URL, - 'client_id':settings.AUTH_CLIENT_ID - }, - headers = { - 'Authorization':'Basic %s'%settings.AUTH_B64 - } - ) - resp_json = resp.json() - if 'error' in resp_json: - r = HttpResponse('

OAuth Error

%s
'%json.dumps(resp_json)) - r.status = 500 - return r - else: - user_info = requests.get( - settings.AUTH_TOKEN_ENDPOINT+"user", - headers = { - 'Authorization': 'Bearer ' + resp_json['access_token'] - } - ).json() - req.session['user_id'] = user_info['uuid'] - matches = User.objects.filter(user_id=user_info['uuid']) - match = None - if not len(matches): - user = User( - user_id = user_info['uuid'], - email = user_info['email'], - display_name = user_info['display_name'] - ) - user.save() - match = user - else: - match = matches[0] - req.session['token'] = resp_json['access_token'] - req.session['disp_name'] = match.display_name + + user = authenticate(code=code) + + if user is not None and user.is_active: + auth_login(req, user) + return HttpResponseRedirect('/') - else: - return HttpResponse('

Unmatching state tokens


It looks like the request to login wasn\'t started by you. Try going back to the home page and logging in again.

', status=400) + + return HttpResponse('

Error


It looks like something went wrong while trying to authenticate you. Please try again later.

', status=500) + + return HttpResponse('

Unmatching state tokens


It looks like the request to login wasn\'t started by you. Try going back to the home page and logging in again.

', status=400) class Login(View): def get(self, req): - url = '%sauthorize?response_type=code&client_id=%s&redirect_uri=%s&scope=email&state=%s'%(settings.AUTH_TOKEN_ENDPOINT,settings.AUTH_CLIENT_ID,settings.AUTH_REDIRECT_URL, generateState(req)) + url = '%sauthorize?response_type=code&client_id=%s&redirect_uri=%s&scope=email privilege&state=%s'%(settings.AUTH_TOKEN_ENDPOINT,settings.AUTH_CLIENT_ID,settings.AUTH_REDIRECT_URL, generateState(req)) response = HttpResponse("Redirecting you to the IcyNet auth page...") response.status_code = 302 response['Location'] = url