Skip to content

Commit 4882283

Browse files
committed
Resolves #67, ads a page to enforce a registration token; and a page to create the registration URL
1 parent 539b5fa commit 4882283

File tree

5 files changed

+350
-35
lines changed

5 files changed

+350
-35
lines changed

dbdb/core/forms.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,24 @@ class CreateUserForm(forms.ModelForm):
158158
widget=InvisibleReCaptchaWidget
159159
)
160160

161+
def __init__(self, *args, **kwargs):
162+
super(CreateUserForm, self).__init__(*args, **kwargs)
163+
164+
self.initial_email = None
165+
166+
initial = getattr(self, 'initial', None)
167+
if initial and 'email' in initial and initial['email']:
168+
self.initial_email = initial['email']
169+
self.fields['email'].widget.attrs['readonly'] = True
170+
pass
171+
172+
return
173+
174+
def clean_email(self):
175+
if self.initial_email:
176+
return self.initial_email
177+
return self.cleaned_data['email']
178+
161179
def clean_password2(self):
162180
if self.cleaned_data['password2'] == self.cleaned_data['password']:
163181
return self.cleaned_data['password2']

dbdb/core/urls.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@
2727
url(r'^stats[/]?$', views.StatsView.as_view(), name='stats'),
2828
url(r'^stats(?:/(?P<stats_type>[\w]+))$', views.StatsView.as_view(), name='stats_detailed'),
2929

30-
url(r'^user/create$', views.CreateUser.as_view(), name='create_user'),
31-
url(r'^user/create/$', RedirectView.as_view(pattern_name='create_user')),
30+
path('user/create', views.CreateUserView.as_view(), name='create_user'),
31+
path('user/create/', RedirectView.as_view(pattern_name='create_user')),
32+
path('user/setup', views.SetupUserView.as_view(), name='setup_user'),
3233

3334
path('counter', views.CounterView.as_view(), name='counter'),
3435
path('sitemap.xml', views.SitemapView.as_view(), name='sitemap'),

dbdb/core/views.py

Lines changed: 166 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
# stdlib imports
22
from functools import reduce
3+
from pprint import pprint
34
import collections
45
import datetime
5-
import operator
66
import json
7+
import operator
78
import time
89
import urllib.parse
9-
from pprint import pprint
1010
# django imports
1111
from django.conf import settings
1212
from django.contrib.auth import get_user_model
1313
from django.contrib.auth.decorators import login_required
1414
from django.contrib.auth.mixins import LoginRequiredMixin
15+
from django.contrib.auth.mixins import UserPassesTestMixin
1516
from django.db import transaction
1617
from django.db.models import Q, Count, Max, Min
1718
from django.forms import HiddenInput
@@ -55,6 +56,10 @@
5556
from dbdb.core.models import SystemVisit
5657
from dbdb.core.models import SystemRecommendation
5758

59+
60+
UserModel = get_user_model()
61+
62+
5863
# constants
5964

6065
FILTERGROUP_VISIBLE_LENGTH = 3
@@ -132,6 +137,15 @@ def get_removal_url(self):
132137
pass
133138

134139

140+
# helper functions
141+
142+
def staff_check(user):
143+
return user.is_staff
144+
145+
def super_user_check(user):
146+
return user.is_superuser
147+
148+
135149
# class based views
136150

137151
# ==============================================
@@ -753,8 +767,8 @@ def post(self, request):
753767
try:
754768
payload = jwt.decode(
755769
token.encode('utf-8'),
756-
settings.SECRET_KEY,
757-
algorithms=['HS256']
770+
settings.SECRET_KEY,
771+
algorithms=['HS256']
758772
)
759773

760774
iss = payload.get('iss')
@@ -796,42 +810,112 @@ def post(self, request):
796810
pass
797811

798812
# ==============================================
799-
# CreateUser
813+
# CreateUserView
800814
# ==============================================
801-
class CreateUser(View):
815+
class CreateUserView(View):
816+
817+
TOKEN_QUERY_NAME = 'token'
802818

803819
template_name = 'registration/create_user.html'
804820

821+
def decode_token(self, request):
822+
token = request.GET.get(CreateUserView.TOKEN_QUERY_NAME)
823+
824+
if not token:
825+
return None
826+
827+
try:
828+
payload = jwt.decode(
829+
token.encode('utf-8'),
830+
settings.SECRET_KEY,
831+
algorithms=['HS256'],
832+
verify=True
833+
)
834+
pass
835+
except jwt.exceptions.ExpiredSignatureError:
836+
payload = False
837+
except:
838+
payload = None
839+
840+
return payload
841+
805842
def get(self, request, *args, **kwargs):
806-
context = {
807-
'form': CreateUserForm(auto_id='%s'),
843+
expired_token = False
844+
initial = { }
845+
846+
reg_info = self.decode_token(request)
847+
if reg_info == False:
848+
expired_token = True
849+
pass
850+
elif reg_info and 'sub' in reg_info:
851+
initial['email'] = reg_info['sub']
852+
853+
form = CreateUserForm(auto_id='%s', initial=initial)
854+
855+
return render(request, self.template_name, {
856+
'title': 'User Registration',
857+
858+
'expired_token': expired_token,
859+
'form': form,
808860
'recaptcha_key': getattr(settings, 'NORECAPTCHA_SITE_KEY'),
809-
}
810-
return render(request, context=context, template_name=self.template_name)
861+
})
811862

812863
def post(self, request, *args, **kwargs):
813-
form = CreateUserForm(request.POST, auto_id='%s')
814-
User = get_user_model()
864+
expired_token = False
865+
initial = { }
866+
867+
# check for a registration info
868+
reg_info = self.decode_token(request)
869+
# if the registration expired `False` then return to login page
870+
if reg_info == False:
871+
return redirect(settings.LOGIN_URL + '?status=failed')
872+
pass
873+
# if the registration included a subject, use as email address
874+
elif reg_info and 'sub' in reg_info:
875+
initial['email'] = reg_info['sub']
876+
pass
877+
878+
# create form class (it handles enforcing initial email)
879+
form = CreateUserForm(request.POST, auto_id='%s', initial=initial)
815880

816881
if form.is_valid():
817-
User.objects.create_user(
818-
username=form.cleaned_data['username'],
819-
email=form.cleaned_data['email'],
820-
password=form.cleaned_data['password']
821-
)
822-
return redirect('/login/?status=success')
882+
with transaction.atomic():
883+
# create user with provided info
884+
user = UserModel.objects.create_user(
885+
username=form.cleaned_data['username'],
886+
email=form.cleaned_data['email'],
887+
password=form.cleaned_data['password']
888+
)
889+
890+
# associate the user with various systems if specified in registration info
891+
if reg_info and 'systems' in reg_info:
892+
system_ids = list( map(int, reg_info['systems']) )
893+
894+
# NOTE: if registration contained no longer valid system IDs, this will error out
895+
SystemACL.objects.bulk_create([
896+
SystemACL(
897+
system_id=system_id,
898+
user_id=user.id
899+
)
900+
for system_id in system_ids
901+
])
902+
pass
903+
pass
823904

824-
return render(request, context={
905+
# end successfully with a redirect to login page
906+
return redirect(settings.LOGIN_URL + '?status=success')
907+
908+
return render(request, self.template_name, {
825909
'form': form,
826910
'recaptcha_key': getattr(settings, 'NORECAPTCHA_SITE_KEY'),
827-
}, template_name=self.template_name)
911+
})
828912

829913
pass
830914

831915
# ==============================================
832916
# DatabasesEditView
833917
# ==============================================
834-
class DatabasesEditView(View, LoginRequiredMixin):
918+
class DatabasesEditView(LoginRequiredMixin, View):
835919

836920
template_name = 'core/databases-edit.html'
837921

@@ -1218,6 +1302,59 @@ def get(self, request):
12181302

12191303
pass
12201304

1305+
# ==============================================
1306+
# SetupUserView
1307+
# ==============================================
1308+
class SetupUserView(UserPassesTestMixin, View):
1309+
1310+
TOKEN_QUERY_NAME = 'token'
1311+
1312+
template_name = 'registration/setup_user.html'
1313+
1314+
def build_token(self, email, systems):
1315+
payload = {
1316+
'exp': datetime.datetime.utcnow() + datetime.timedelta(days=7),
1317+
'iss': 'setup_user',
1318+
'sub': email,
1319+
'nbf': datetime.datetime.utcnow(),
1320+
'systems': list( map(int, systems) ),
1321+
}
1322+
1323+
s = jwt.encode(payload, settings.SECRET_KEY, algorithm='HS256')
1324+
s = s.decode('utf-8')
1325+
1326+
return s
1327+
1328+
def get(self, request, *args, **kwargs):
1329+
if request.GET.get('action') == 'url' and request.GET.get('email') and request.GET.getlist('systems'):
1330+
email = request.GET.get('email').lower().strip()
1331+
systems = request.GET.getlist('systems')
1332+
1333+
response = None
1334+
1335+
if UserModel.objects.filter(email=email).exists():
1336+
response = { 'error':'Email already exists' }
1337+
pass
1338+
else:
1339+
url = reverse('create_user') + '?' + urllib.parse.urlencode({ SetupUserView.TOKEN_QUERY_NAME:self.build_token(email, systems) })
1340+
url = request.build_absolute_uri(url)
1341+
1342+
response = { 'url':url }
1343+
pass
1344+
1345+
return JsonResponse(response)
1346+
1347+
return render(request, self.template_name, {
1348+
'title': 'User Registration Setup',
1349+
1350+
'systems': System.objects.all(),
1351+
})
1352+
1353+
def test_func(self):
1354+
return super_user_check(self.request.user)
1355+
1356+
pass
1357+
12211358
# ==============================================
12221359
# StatsView
12231360
# ==============================================
@@ -1408,18 +1545,14 @@ def get(self, request, slug):
14081545
system_version = system.current()
14091546
system_features = SystemFeature.objects.filter(system=system_version).select_related('feature').order_by('feature__label')
14101547

1411-
# If they are logged in, check whether they are allowed to edit
1412-
user_can_edit = False
1413-
if request.user.is_authenticated:
1414-
if request.user.is_superuser:
1415-
user_can_edit = True
1416-
else:
1417-
try:
1418-
SystemACL.objects.get(system=system, user=request.user)
1419-
user_can_edit = True
1420-
except SystemACL.DoesNotExist:
1421-
pass
1422-
## IF
1548+
# if they are logged in, check whether they are allowed to edit
1549+
if not request.user.is_authenticated:
1550+
user_can_edit = False
1551+
elif request.user.is_superuser:
1552+
user_can_edit = True
1553+
else:
1554+
user_can_edit = SystemACL.objects.filter(system=system, user=request.user).exists()
1555+
pass
14231556

14241557
# Compatible Systems
14251558
compatible = [

templates/registration/create_user.html

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@
2121
<form method="POST" id="registration">
2222
<h1>User Registration</h1>
2323

24+
{% if expired_token %}
25+
<div class="alert alert-danger mb-0" role="alert">
26+
<p><strong>The registration link provided to you has expired.</strong></p>
27+
<p>Please request a new one.</p>
28+
</div>
29+
{% else %}
2430
{% csrf_token %}
2531
{% bootstrap_form form %}
2632
{% buttons %}
@@ -34,6 +40,7 @@ <h1>User Registration</h1>
3440
Cancel
3541
</a>
3642
{% endbuttons %}
43+
{% endif %}
3744
</form>
3845
</div>
3946
</div>

0 commit comments

Comments
 (0)