This commit is contained in:
David Todd 2018-09-27 01:59:57 +00:00 committed by GitHub
commit 22f994aec7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 453 additions and 2 deletions

0
account/__init__.py Normal file
View File

6
account/admin.py Normal file
View File

@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.contrib import admin
# Register your models here.

8
account/apps.py Normal file
View File

@ -0,0 +1,8 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.apps import AppConfig
class AccountConfig(AppConfig):
name = 'account'

View File

@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2018-03-09 04:52
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Account',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('first_login', models.BooleanField(default=True)),
('accepted_terms', models.DateTimeField(blank=True, null=True)),
('user', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
],
),
]

View File

51
account/models.py Normal file
View File

@ -0,0 +1,51 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.contrib.auth.models import User
from django.core.signing import Signer
from django.utils import timezone
from django.conf import settings
from django.db import models
import uuid
# Foreign key to logged-in users
AUTH_USER_MODEL = getattr(settings, 'AUTH_USER_MODEL', 'auth.User')
class Account(models.Model):
id = models.UUIDField(primary_key=True,
default=uuid.uuid4,
editable=False)
user = models.OneToOneField(AUTH_USER_MODEL,
on_delete=models.PROTECT)
first_login = models.BooleanField(default=True)
accepted_terms = models.DateTimeField(auto_now=False,
auto_now_add=False,
blank=True,
null=True)
def __str__(self):
return "<%s:%s>" % (self.id, self.user.username)
def get_meta(self):
meta = {
'user': self.user.username,
'first_login': self.first_login,
'accepted_terms': self.accepted_terms
}
return meta
def create_user(self, username, first_name, last_name, email, password):
# Create a user
usr_obj = User.objects.get_or_create(username=username,
first_name=first_name,
last_name=last_name,
email=email)
usr_obj.set_password(password)
usr_obj.save()
self.user = usr_obj
self.save()
return self

31
account/tests.py Normal file
View File

@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.test import TestCase
from account.models import Account
from django.contrib.auth.models import User
# Create your tests here.
class AccountTestCase(TestCase):
def setUp(self):
usr1 = User.objects.create(username='usr1',
first_name='test',
last_name='1',
email='test1@example.com')
usr2 = User.objects.create(username='usr2',
first_name='test',
last_name='2',
email='test2@example.com')
Account.objects.create(user=usr1)
Account.objects.create(user=usr2)
def test_user_meta_data(self):
""" Should return some info about these user accounts """
usr1 = User.objects.get(username='usr1')
usr_acct1 = Account.objects.get(user=usr1)
usr2 = User.objects.get(username='usr2')
usr_acct2 = Account.objects.get(user=usr2)
self.assertEqual(usr_acct1.get_meta(), {'user': 'usr1', 'first_login': True, 'accepted_terms': None})
self.assertEqual(usr_acct2.get_meta(), {'user': 'usr2', 'first_login': True, 'accepted_terms': None})

15
account/urls.py Normal file
View File

@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.index, name='index'),
url(r'^login$', views.login, name='login'),
url(r'^logout$', views.logout, name='logout'),
url(r'^terms$', views.terms, name='terms'),
url(r'^reset/(?P<key>[a-zA-Z0-9:_-]*)$', views.reset, name='reset'),
url(r'^firstlogin/(?P<key>[a-zA-Z0-9:_-]*)$', views.firstlogin, name='firstlogin'),
url(r'^create$', views.create, name='reset'),
]

303
account/views.py Normal file
View File

@ -0,0 +1,303 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.shortcuts import render
from django.template import loader
from gallery.utility import _ResponseTemplate, _ForceLogout
from django.conf import settings
from django.contrib import auth
from django.views.decorators.csrf import csrf_exempt
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseNotFound, HttpResponseNotAllowed
from django.utils import timezone, dateparse
from rest_framework import views, response, status
from rest_framework.renderers import JSONRenderer
from django.core.exceptions import ObjectDoesNotExist
import json
from .models import *
_reset_salt = 'portalpwreset'
def _resetPassword(key, email, password):
signer = Signer(salt="portalpwreset")
key = signer.unsign(key)
user = auth.models.User.objects.get(pk=key,email=email)
user.set_password(password)
user.save()
return user
def index(request):
if not request.user.is_authenticated():
return _ForceLogout(request, 'Please sign in')
if hasattr(request.user, 'account'):
user_account = request.user.account
if user_account.first_login:
return _ForceLogout(request,
'For your security, please login again and change your password')
else:
return _ForceLogout(request,
'Your user account is not connected to Account meta-data. Please contact support')
context = {
'user_name': user_account.user.username,
'account_meta': user_account,
'message': None
}
return _ResponseTemplate('account/index.html',
request,
context=context)
def login(request):
if request.method == 'POST':
username = request.POST['username']
password = request.POST['password']
logintype = request.POST['logintype']
if logintype in ('admin', 'user'):
user = auth.authenticate(username=username,
password=password)
if user is not None:
if user.is_active:
auth.login(request, user)
try:
user_account = user.account
# Is it this user's first login?
if user_account.first_login:
signer = Signer(salt=_reset_salt)
key = signer.sign(user.id)
# Make them change their password
return HttpResponseRedirect('/account/firstlogin/' + key)
# Has the user accepted the TOS?
accepted_terms = user_account.accepted_terms
if accepted_terms is None:
# Make them read it
return _ResponseTemplate('account/terms',
request)
# Has the TOS been updated since they accepted it?
if accepted_terms is not None and accepted_terms < __get_terms_update():
# Make them read it
return _ResponseTemplate('account/terms',
request)
# No Account One2One user key
except ObjectDoesNotExist:
return _ForceLogout(request,
'Your user account is not connected to Account meta-data. Please contact support')
# Successful login
# TODO: Redirect to the page they were on before loggin in, if any
return HttpResponseRedirect('/account/')
else:
return _ForceLogout(request,
'Your user account is not active')
else:
return _ForceLogout(request,
'Incorrect Username/Password combination')
elif logintype in ('http-api', 'ssl-api'):
# TODO: Certificate based and/or API key authentication
return _ForceLogout(request,
'API Login is not yet supported')
elif logintype in ('google-auth', 'telegram-auth'):
# TODO: Third Party login
return _ForceLogout(request,
'Third party login is not yet supported')
else:
return _ForceLogout(request,
'Invalid Login Type')
else:
# Show the login page
return _ResponseTemplate('/account/login.html',
request)
def logout(request):
return _ForceLogout(request,
'You have been logged out')
# Allows the terms to be updated and presented
def __get_terms_update():
template = loader.get_template('account/terms.html')
template = template.template.nodelist[0]
if '<WithNode>' == repr(template):
template = template.extra_context['updated'].var
return dateparse.parse_datetime(template)
def terms(request):
if hasattr(request.user, 'account'):
user_account = request.user.account
else:
return _ForceLogout(request,
'Your user account is not connected to Account meta-data. Please contact support')
if request.method == 'POST':
if request.POST['accept_terms'] == 'yes':
user_account.accepted_terms = timezone.now()
user_account.save()
mail.terms(user_account.user.username)
return HttpResponseRedirect('/account/')
else:
return _ForceLogout(request,
'You have chosen to not accept the terms. You are now logged out.')
elif request.method == 'GET':
terms = user_account.accepted_terms
terms = terms.isoformat() if terms else '1970'
context = {
'user_name': user_account.user.username,
'last_accepted_terms': terms
}
return _ResponseTemplate('account/terms.html',
request,
context=context)
else:
return HttpResponseNotAllowed(['POST', 'GET'])
def _send_reset_email(user, password=False, signup=False):
signer = Signer(salt=_reset_salt)
key = signer.sign(user.pk)
mail.passwordreset(user.email, key, password, signup and user.username)
def reset(request, key):
signer = Signer(salt=_reset_salt)
if request.method == 'GET':
if not key:
try:
key = signer.sign(request.user.id)
except IndexError, ValueError:
return _ForceLogout(request,
'Your password reset key was missing. Please try again')
key = signer.unsign(key)
context = {
'key': signer.sign(key)
}
return _ResponseTemplate('account/reset.html',
request,
context=context)
elif request.method == 'POST':
if key:
password = request.POST['password']
email = request.POST['email']
if not email or not email:
return _ResponseTemplate('account/reset.html',
request,
message='You did not provide a valid email or password')
# Reset the password
key, user = _resetUserPassword(key, password)
if not user:
return _ForceLogout(request, 'Unknown User')
if not hasattr(request.user, 'account'):
return _ForceLogout(request,
'Your user account is not connected to Account meta-data. Please contact support')
return _ForceLogout(request,
'Password changed. Please sign in again')
else:
data = json.loads(request.body)
email = data['email']
try:
user = auth.models.User.objects.get(email=email)
except auth.models.User.DoesNotExist:
return _ForceLogout(request,
'Invalid email address')
_send_reset_email(user)
return _ForceLogout(request,
'A password reset link has been sent to your email')
else:
return HttpResponseNotAllowed(['POST', 'GET'])
def firstlogin(request, key):
if request.method == 'GET':
# They should not be here without a key
if not key:
return _ForceLogout(request,
'Your password reset key was missing. Please try again')
signer = Signer(salt=_reset_salt)
username = auth.models.User.objects.get(pk=signer.unsign(key))
context = {
'key': key,
'username': username
}
return _ResponseTemplate('account/firstlogin.html',
request,
context=context)
elif request.method == 'POST':
if not key:
return _ForceLogout(request,
'Your password reset key was missing. Please try again')
# Reset the password
password = request.POST['password']
email = request.POST['email']
if not email or not email:
return _ResponseTemplate('account/firstlogin.html',
request,
message='You did not provide a valid email or password')
key, user =_resetUserPassword(key, email, password)
if not user:
return _ForceLogout(request,
'Unknown User')
try:
user.account.first_login = False
user.account.save()
except ObjectDoesNotExist:
return _ForceLogout(request,
'Your user account is not connected to Account meta-data. Please contact support')
# Reset complete
return _ForceLogout(request,
'Password changed. Please sign in again')
else:
return HttpResponseNotAllowed(['POST', 'GET'])
def create(request):
if request.method != 'POST':
return HttpResponseNotAllowed(['POST'])
keys = {}
try:
fields = [
'firstname',
'lastname',
'email',
'username',
'password'
]
req = request.POST
# Create a signup system
keys = {x:req[x] for x in fields}
user_account = Account.objects.create_user(**keys)
return _ForceLogout(request,
'Account Created. Please sign in')
except KeyError, e:
context = {
'error': {
'code': 'missing_fields',
'message': json.dumps(repr(e))
}
}
return _ForceLogout(request,
context=context)

View File

@ -44,6 +44,7 @@ EMAIL_HOST_PASSWORD = ''
# Application definition
INSTALLED_APPS = [
'account.apps.AccountConfig',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',

View File

@ -13,11 +13,12 @@ Including another URLconf
1. Import the include() function: from django.conf.urls import url, include
2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
"""
from django.conf.urls import url
from django.conf.urls import include, url
from django.contrib import admin
from django.conf import settings
urlpatterns = [
url(r'^account/', include('account.urls')),
url(r'^admin/', admin.site.urls),
]
if settings.DEBUG:

View File

@ -1,6 +1,7 @@
from django.http import HttpResponse
from django.template import loader
from django.contrib import auth
import logging
# Usage:
# template - relative path to the template you wish to render
@ -15,15 +16,18 @@ def _ResponseTemplate(template, request, message='', context=None):
(context.get('message', False)):
context['message'] += ' -- ' + str(message)
else:
logging.warning('[_ResponseTemplate]: context was %s, not a dictionary like expected. Ignoring provided context' % type(context))
context = {
'message': str(message)
}
template = loader.get_template(str(template))
return HttpResponse(template.render(context, request))
# Logs out a user, sending them to the login screen with an optional message and context
# Same arguments as _ResponseTemplate, except for the template
def _ForceLogout(request, message='', context=None):
if request.user:
logging.debug('Logged out %s' % request.user)
auth.logout(request)
return _ResponseTemplate('account/login.html', request, message, context)

View File

@ -8,9 +8,11 @@ https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/
"""
import os
import gallery.gzip
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "gallery.settings")
application = get_wsgi_application()
application = gallery.gzip.UnzipRequestMiddleware(application)