wrote a resetlink function

This commit is contained in:
downhill 2018-10-15 21:24:53 +02:00
parent 0842679b55
commit 2eea482d80
3 changed files with 70 additions and 25 deletions

View file

@ -22,5 +22,6 @@ from .rpc import
``` ```
till it's fixed\. till it's fixed\.
Config options are in nameko\.conf and of course the dal/dal/settings\.py Config options are in nameko\.conf and of course the dal/dal/settings\.py
Don't forget to do python manage\.py makemigrations dal and then migrate
The standard settings there work fine with a LDAP server set up with the information on our wiki\. Except the manager password, The standard settings there work fine with a LDAP server set up with the information on our wiki\. Except the manager password,
set that to what you choose\. set that to what you choose\.

14
dal/dal/models.py Normal file
View file

@ -0,0 +1,14 @@
from django.db import models
# Basic DB to correlate tokens, users and creation time
class ResetToken(models.Model):
# users wouldn't use usernames >100 chars
user = models.CharField(max_length=100)
# Not so sure about tokens, better make it big
# should be <100, but big usernames make bigger tokens
# if I read that correctly
token = models.CharField(max_length=255)
# Just so we are save for the next few decades ;)
creation = models.BigIntegerField()

View file

@ -9,6 +9,7 @@ from django_nameko import get_pool
from django.contrib.auth.tokens import PasswordResetTokenGenerator from django.contrib.auth.tokens import PasswordResetTokenGenerator
from base64 import b64encode, b64decode from base64 import b64encode, b64decode
from datetime import datetime from datetime import datetime
from .models import ResetToken
# Check to see if the username is already taken # Check to see if the username is already taken
# Helper function, not to be set up as a view # Helper function, not to be set up as a view
@ -157,9 +158,7 @@ class ChangeData(View):
# Resets the password for a user # Resets the password for a user
# Will need to send a confirmation email to the user and we will need a backend # Sends email to the user with a link to reset the password
# to confirm the request came from someone who has access to the email
# Out of scope except for creating the workflow
class ResetPassword(View): class ResetPassword(View):
@ -168,7 +167,7 @@ class ResetPassword(View):
return render(request, 'resetpassword.html') return render(request, 'resetpassword.html')
# gets the data from confirming the reset request and checks if it was not a misclick # gets the data from confirming the reset request and checks if it was not a misclick
# (by having the user type in his username # (by having the user type in his username)
def post(self, request): def post(self, request):
urlname = 'reset_password' urlname = 'reset_password'
service = 'send a password reset request' service = 'send a password reset request'
@ -191,52 +190,69 @@ class ResetPassword(View):
else: else:
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': emailsend } ) return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': emailsend } )
# Sends an email to the user with the 24h active link for a password reset
def email(self, user, email): def email(self, user, email):
#TODO figure out how to send email # getting epoch for the time now in UTC to spare us headache with timezones
email_from = 'Userservice at ungleich <userservice@ungleich.ch>' creationtime = int(datetime.utcnow().timestamp())
#TODO figure out how to send email
email_from = 'Userservice at ungleich <userservice@ungleich.ch>'
to = '%s <%s>' % (user, email) to = '%s <%s>' % (user, email)
subject = 'Password reset request for %s' % user subject = 'Password reset request for %s' % user
no-reply = True noreply = True
link = self.build_reset_link(user) link = self.build_reset_link(user, creationtime)
body = 'This is an automated email which was triggered by a reset request for the user %s.\n' % user body = 'This is an automated email which was triggered by a reset request for the user %s.\n' % user
body += 'If you received this email in error, please disregard it. If you get multiple emails like this, please contact us to look into potential abuse.\n' body += 'If you received this email in error, please disregard it. If you get multiple emails like this, please contact us to look into potential abuse.\n'
body += 'To reset your password, please follow the link below:\n' body += 'To reset your password, please follow the link below:\n'
body += '%s\n\n' % link body += '%s\n\n' % link
body += 'The link will remain active for 24 hours.\n' body += 'The link will remain active for 24 hours.\n'
# For debug # For debug
return link return link
def build_reset_link(self, user): # Builds the reset link for the email and puts the token into the database
def build_reset_link(self, user, epochutc):
# set up the data
host = 'localhost:8000' host = 'localhost:8000'
x = PasswordResetTokenGenerator() tokengen = PasswordResetTokenGenerator()
token = x.make_token(user) token = tokengen.make_token(user)
buser = bytes(user, 'utf-8') buser = bytes(user, 'utf-8')
userpart = b64encode(buser) userpart = b64encode(buser)
d = datetime.now() # create entry into the database
# TODO Make Model und put it into the database newdbentry = ResetToken(user=user, token=token, creation=epochutc)
newdbentry.save()
# set up the link
link = 'https://%s/reset/%s/%s/' % (host, userpart.decode('utf-8'), token) link = 'https://%s/reset/%s/%s/' % (host, userpart.decode('utf-8'), token)
return link return link
# Catch the resetrequest and check it # Catch the resetrequest URL and check it
class ResetRequest(View): class ResetRequest(View):
# Gets the URL with user in b64 and the token, and checks it # Gets the URL with user in b64 and the token, and checks it
# Also cleans the database # Also cleans the database
def get(self, request, user=None, token=None): def get(self, request, user=None, token=None):
# Cleans up outdated tokens # Cleans up outdated tokens
# If we expect quite a bit of old tokens, maybe somewhere else is better,
# but for now we don't really expect many unused tokens
self.clean_db() self.clean_db()
# If user and token are not supplied by django, it was called from somewhere else, so it's
# invalid
if user == None or token == None: if user == None or token == None:
return HttpResponse('Invalid URL.', status=404) return HttpResponse('Invalid URL.', status=404)
# extract user from b64 format # extract user from b64 format
tmp_user = bytes(user, 'utf-8') tmp_user = bytes(user, 'utf-8')
user = b64decode(tmp_user) user = b64decode(tmp_user)
user_clean = user.decode('utf-8') user_clean = user.decode('utf-8')
d = datetime.now() # set checks_out = True if token is found in database
#TODO write the model and check if token is still active and belongs to the user dbentries = ResetToken.objects.all().filter(user=user_clean)
# set checks_out = True if yes for entry in dbentries:
if entry.token == token:
# found the token, now delete it since it's used
checks_out = True
entry.delete()
# No token was found
if not checks_out: if not checks_out:
return HttpResponse('Invalid URL.', status=404) return HttpResponse('Invalid URL.', status=404)
# Token was found, supply the form
else: else:
return render(request, 'resetpasswordnew.html', { 'user': user_clean } ) return render(request, 'resetpasswordnew.html', { 'user': user_clean } )
@ -244,26 +260,40 @@ class ResetRequest(View):
# Gets the post form with the new password and sets it # Gets the post form with the new password and sets it
def post(self, request): def post(self, request):
service = 'reset the password' service = 'reset the password'
# get the supplied passwords
password1 = request.POST.get("password1") password1 = request.POST.get("password1")
password2 = request.POST.get("password2") password2 = request.POST.get("password2")
# get the hidden value of user
user = request.POST.get("user") user = request.POST.get("user")
# some checks over the supplied data
if user == "" or not user:
return render(request, 'error.html', { 'service': service, 'error': 'Something went wrong. Did you use the supplied form?' } )
if password1 == "" or not password1 or password2 == "" or not password2: if password1 == "" or not password1 or password2 == "" or not password2:
return render(request, 'error.html', { 'service': service, 'error': 'Please supply a password and confirm it.' } ) return render(request, 'error.html', { 'service': service, 'error': 'Please supply a password and confirm it.' } )
if password1 != password2: if password1 != password2:
return render(request, 'error.html', { 'service': service, 'error': 'The supplied passwords do not match.' } ) return render(request, 'error.html', { 'service': service, 'error': 'The supplied passwords do not match.' } )
# everything checks out, now change the password
with get_pool().next() as rpc: with get_pool().next() as rpc:
pwd = r'%s' % password1 pwd = r'%s' % password1
result = rpc.changepassword.change_password(user, pwd) result = rpc.changepassword.change_password(user, pwd)
# password change successfull
if result == True: if result == True:
return render(request, 'changedpassword.html', { 'user': user } ) return render(request, 'changedpassword.html', { 'user': user } )
# Something went wrong while changing the password
else: else:
return render(request, 'error.html', { 'service': service, 'error': result } ) return render(request, 'error.html', { 'service': service, 'error': result } )
# Cleans up outdated tokens # Cleans up outdated tokens
def clean_db(self): def clean_db(self):
# TODO write the model and use this to clean tokens > 24h old # cutoff time is set to 24h hours
# using utcnow() to have no headache with timezones
cutoff = int(datetime.utcnow().timestamp()) - (24*60*60)
# Get all tokens older than 24 hours
oldtokens = ResetToken.objects.all().filter(creation__lt=cutoff)
for token in oldtokens:
# delete all tokens older than 24 hours
token.delete()
return True
# The logged in user can change the password here # The logged in user can change the password here
@ -346,7 +376,7 @@ class DeleteAccount(View):
else: else:
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': result } ) return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': result } )
# Log out the session
class LogOut(View): class LogOut(View):
def get(self, request): def get(self, request):