reset function added, some minor debugging/fixes

This commit is contained in:
downhill 2018-10-16 20:25:50 +02:00
parent 2eea482d80
commit dfd537177e
6 changed files with 57 additions and 36 deletions

View file

@ -10,5 +10,6 @@ class ResetToken(models.Model):
# should be <100, but big usernames make bigger tokens # should be <100, but big usernames make bigger tokens
# if I read that correctly # if I read that correctly
token = models.CharField(max_length=255) token = models.CharField(max_length=255)
# Just so we are save for the next few decades ;) # creation time in epoch (UTC)
# BigInt just so we are save for the next few decades ;)
creation = models.BigIntegerField() creation = models.BigIntegerField()

View file

@ -99,6 +99,7 @@ MIDDLEWARE = [
AUTHENTICATION_BACKENDS = ( AUTHENTICATION_BACKENDS = (
'django_auth_ldap.backend.LDAPBackend', 'django_auth_ldap.backend.LDAPBackend',
# we only use LDAP for this service, so no auth against the standard DB
# 'django.contrib.auth.backends.ModelBackend', # 'django.contrib.auth.backends.ModelBackend',
) )

View file

@ -2,9 +2,8 @@
<h2> Password reset </h2> <h2> Password reset </h2>
<br><br> <br><br>
To reset your password, please enter your username below. You will get an email asking you to confirm this and after confirmation an email with your To reset your password, please enter your username below. You will get an email with a link to change your password.
temporary password. Please remember to change it immediately after logging in. <br><br>
<br>
<form action={% url 'reset_password' %} method="post"> <form action={% url 'reset_password' %} method="post">
{% csrf_token %} {% csrf_token %}
Username:<br> Username:<br>

View file

@ -1,27 +1,12 @@
"""dal URL Configuration # The different URLs this service does use
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/1.10/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: url(r'^$', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home')
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.urls import path from django.urls import path
from django.conf.urls import url from django.conf.urls import url
from django.contrib import admin from django.contrib import admin
# Import the classes for the views
from .views import Register, ChangeData, ChangePassword, ResetPassword, DeleteAccount, Index, LogOut, ResetRequest from .views import Register, ChangeData, ChangePassword, ResetPassword, DeleteAccount, Index, LogOut, ResetRequest
urlpatterns = [ urlpatterns = [
# path('admin/', admin.site.urls),
path('register/', Register.as_view(), name="register"), path('register/', Register.as_view(), name="register"),
path('changedata/', ChangeData.as_view(), name="change_data"), path('changedata/', ChangeData.as_view(), name="change_data"),
path('resetpassword/', ResetPassword.as_view(), name="reset_password"), path('resetpassword/', ResetPassword.as_view(), name="reset_password"),

View file

@ -1,3 +1,4 @@
# Imports from django
from django.shortcuts import render from django.shortcuts import render
from django.views.generic import View from django.views.generic import View
from django.contrib.auth import authenticate, login, logout from django.contrib.auth import authenticate, login, logout
@ -5,11 +6,17 @@ from django.contrib.auth.models import User
from django.http import HttpResponse, HttpResponseRedirect from django.http import HttpResponse, HttpResponseRedirect
from django.core.validators import validate_email, ValidationError from django.core.validators import validate_email, ValidationError
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django_nameko import get_pool
from django.contrib.auth.tokens import PasswordResetTokenGenerator from django.contrib.auth.tokens import PasswordResetTokenGenerator
from django.core.mail import EmailMessage
from .models import ResetToken
# Imports for the extra stuff not in django
# django_nameko is an extra module, so gets put in here
from base64 import b64encode, b64decode from base64 import b64encode, b64decode
from datetime import datetime from datetime import datetime
from .models import ResetToken from django_nameko import get_pool
from random import choice, randint
import string
# 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
@ -18,6 +25,24 @@ def check_user_exists(username):
with get_pool().next() as rpc: with get_pool().next() as rpc:
return rpc.userlookup.lookup(username) return rpc.userlookup.lookup(username)
# To trick the tokengenerator to work with us, because we don't really
# have the expected user Class since we are reading the user from a form
# We store the tokens and don't have to use the check function,
# some one time data works fine.
class LastLogin():
def replace(self, microsecond=0, tzinfo=None):
return randint(1,100000)
class PseudoUser():
# easiest way to handle the check for lastlogin
last_login = LastLogin()
# random alphanumeric strings for primary key and password, just used for token generation
pk = ''.join(choice(string.ascii_letters + string.digits) for _ in range(20))
password = ''.join(choice(string.ascii_letters + string.digits) for _ in range(30))
# The index page # The index page
# If there's a session open, it will give the user the options he/she/it can do, if not, # If there's a session open, it will give the user the options he/she/it can do, if not,
# it will show a landing page explaining what this is and prompt them to login # it will show a landing page explaining what this is and prompt them to login
@ -127,6 +152,7 @@ class ChangeData(View):
service = 'change user data' service = 'change user data'
urlname = 'change_data' urlname = 'change_data'
# Only logged in users may change data
if not request.user.is_authenticated: if not request.user.is_authenticated:
return render(request, 'mustbeloggedin.html') return render(request, 'mustbeloggedin.html')
@ -194,26 +220,38 @@ class ResetPassword(View):
def email(self, user, email): def email(self, user, email):
# getting epoch for the time now in UTC to spare us headache with timezones # getting epoch for the time now in UTC to spare us headache with timezones
creationtime = int(datetime.utcnow().timestamp()) creationtime = int(datetime.utcnow().timestamp())
#TODO figure out how to send email # Construct the data for the email
email_from = 'Userservice at ungleich <userservice@ungleich.ch>' email_from = 'Userservice at ungleich <root@localhost>'
to = '%s <%s>' % (user, email) to = ['%s <%s>' % (user, email)]
subject = 'Password reset request for %s' % user subject = 'Password reset request for %s' % user
noreply = True
link = self.build_reset_link(user, creationtime) 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. Please do not reply to this email.\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 # Build the email
return link mail = EmailMessage(
subject=subject,
body=body,
from_email=email_from,
to=to
)
try:
mail.send()
result = True
except:
result = "An error occurred while trying to send the mail."
return result
# Builds the reset link for the email and puts the token into the database # Builds the reset link for the email and puts the token into the database
def build_reset_link(self, user, epochutc): def build_reset_link(self, user, epochutc):
# set up the data # set up the data
host = 'localhost:8000' host = 'localhost:8000'
tokengen = PasswordResetTokenGenerator() tokengen = PasswordResetTokenGenerator()
token = tokengen.make_token(user) # create some noise for use in the tokengenerator
pseudouser = PseudoUser()
token = tokengen.make_token(pseudouser)
buser = bytes(user, 'utf-8') buser = bytes(user, 'utf-8')
userpart = b64encode(buser) userpart = b64encode(buser)
# create entry into the database # create entry into the database

View file

@ -46,12 +46,10 @@ def user_or_customer(uid):
conn = Connection(server) conn = Connection(server)
conn.bind() conn.bind()
search_customers = conn.search('ou=customers,dc=ungleich,dc=ch', '(%s)' % uid) search_customers = conn.search('ou=customers,dc=ungleich,dc=ch', '(%s)' % uid)
# if conn.search('ou=customers,dc=ungleich,dc=ch', '(%s)' % uid):
if search_customers: if search_customers:
conn.unbind() conn.unbind()
return '%s,ou=customers,dc=ungleich,dc=ch' % uid return '%s,ou=customers,dc=ungleich,dc=ch' % uid
search_users = conn.search('ou=customers,dc=ungleich,dc=ch', '(%s)' % uid) search_users = conn.search('ou=customers,dc=ungleich,dc=ch', '(%s)' % uid)
# elif conn.search('ou=customers,dc=ungleich,dc=ch', '(%s)' % uid):
if search_users: if search_users:
conn.unbind() conn.unbind()
return '%s,ou=customers,dc=ungleich,dc=ch' % uid return '%s,ou=customers,dc=ungleich,dc=ch' % uid
@ -72,11 +70,10 @@ class UserLookUp(object):
conn = Connection(server) conn = Connection(server)
conn.bind() conn.bind()
# Strange result. It keeps complaining LDAP_UID not set if I try to directly # Strange result. It keeps complaining LDAP_UID not set if I try to directly
# substitute x and y to the if, see comment above the if x or y: # substitute x and y to the if
# Searches for user in ou=customers and ou=users
x = conn.search('ou=customers,dc=ungleich,dc=ch', '(%s)' % LDAP_UID) x = conn.search('ou=customers,dc=ungleich,dc=ch', '(%s)' % LDAP_UID)
y = conn.search('ou=users,dc=ungleich,dc=ch', '(%s)' % LDAP_UID) y = conn.search('ou=users,dc=ungleich,dc=ch', '(%s)' % LDAP_UID)
# Search ou=users and ou=customers
#if conn.search('ou=customers,dc=ungleich,dc=ch', '(%s)' % LDAP_UID) or conn.search('ou=users,dc=ungleich,dc=ch', '(%s)' % LPAD_UID):
if x or y: if x or y:
# return conn.entries[0] for first search result since we can assume uid is unique # return conn.entries[0] for first search result since we can assume uid is unique
self.dispatch('ldap', '%s [Info: UserLookUp] Searched for %s and found it\n' % (datetime.now(), LDAP_UID) ) self.dispatch('ldap', '%s [Info: UserLookUp] Searched for %s and found it\n' % (datetime.now(), LDAP_UID) )