wrote a resetlink function
This commit is contained in:
parent
0842679b55
commit
2eea482d80
3 changed files with 70 additions and 25 deletions
|
@ -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
14
dal/dal/models.py
Normal 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()
|
|
@ -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):
|
||||||
|
|
Loading…
Reference in a new issue