From dfd537177e19de5bc6b4fd01d32eb6b848b05520 Mon Sep 17 00:00:00 2001 From: downhill Date: Tue, 16 Oct 2018 20:25:50 +0200 Subject: [PATCH] reset function added, some minor debugging/fixes --- dal/dal/models.py | 3 +- dal/dal/settings.py | 1 + dal/dal/templates/resetpassword.html | 5 +-- dal/dal/urls.py | 19 +-------- dal/dal/views.py | 58 +++++++++++++++++++++++----- nameko-func.py | 7 +--- 6 files changed, 57 insertions(+), 36 deletions(-) diff --git a/dal/dal/models.py b/dal/dal/models.py index e197640..7dee319 100644 --- a/dal/dal/models.py +++ b/dal/dal/models.py @@ -10,5 +10,6 @@ class ResetToken(models.Model): # 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 time in epoch (UTC) + # BigInt just so we are save for the next few decades ;) creation = models.BigIntegerField() diff --git a/dal/dal/settings.py b/dal/dal/settings.py index 7ee1070..efa4353 100644 --- a/dal/dal/settings.py +++ b/dal/dal/settings.py @@ -99,6 +99,7 @@ MIDDLEWARE = [ AUTHENTICATION_BACKENDS = ( 'django_auth_ldap.backend.LDAPBackend', +# we only use LDAP for this service, so no auth against the standard DB # 'django.contrib.auth.backends.ModelBackend', ) diff --git a/dal/dal/templates/resetpassword.html b/dal/dal/templates/resetpassword.html index 06ba5a9..e55e4e8 100644 --- a/dal/dal/templates/resetpassword.html +++ b/dal/dal/templates/resetpassword.html @@ -2,9 +2,8 @@

Password reset



-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 -temporary password. Please remember to change it immediately after logging in. -
+To reset your password, please enter your username below. You will get an email with a link to change your password. +

{% csrf_token %} Username:
diff --git a/dal/dal/urls.py b/dal/dal/urls.py index 8f53d1e..10875b6 100644 --- a/dal/dal/urls.py +++ b/dal/dal/urls.py @@ -1,27 +1,12 @@ -"""dal URL Configuration - -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')) -""" - +# The different URLs this service does use from django.urls import path from django.conf.urls import url from django.contrib import admin +# Import the classes for the views from .views import Register, ChangeData, ChangePassword, ResetPassword, DeleteAccount, Index, LogOut, ResetRequest urlpatterns = [ -# path('admin/', admin.site.urls), path('register/', Register.as_view(), name="register"), path('changedata/', ChangeData.as_view(), name="change_data"), path('resetpassword/', ResetPassword.as_view(), name="reset_password"), diff --git a/dal/dal/views.py b/dal/dal/views.py index 6af0c32..3719ea3 100644 --- a/dal/dal/views.py +++ b/dal/dal/views.py @@ -1,3 +1,4 @@ +# Imports from django from django.shortcuts import render from django.views.generic import View 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.core.validators import validate_email, ValidationError from django.urls import reverse_lazy -from django_nameko import get_pool 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 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 # 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: 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 # 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 @@ -127,6 +152,7 @@ class ChangeData(View): service = 'change user data' urlname = 'change_data' + # Only logged in users may change data if not request.user.is_authenticated: return render(request, 'mustbeloggedin.html') @@ -194,26 +220,38 @@ class ResetPassword(View): def email(self, user, email): # getting epoch for the time now in UTC to spare us headache with timezones creationtime = int(datetime.utcnow().timestamp()) - #TODO figure out how to send email - email_from = 'Userservice at ungleich ' - to = '%s <%s>' % (user, email) + # Construct the data for the email + email_from = 'Userservice at ungleich ' + to = ['%s <%s>' % (user, email)] subject = 'Password reset request for %s' % user - noreply = True 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 += 'To reset your password, please follow the link below:\n' body += '%s\n\n' % link body += 'The link will remain active for 24 hours.\n' - # For debug - return link + # Build the email + 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 def build_reset_link(self, user, epochutc): # set up the data host = 'localhost:8000' 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') userpart = b64encode(buser) # create entry into the database diff --git a/nameko-func.py b/nameko-func.py index 035f69d..3e3b8a5 100644 --- a/nameko-func.py +++ b/nameko-func.py @@ -46,12 +46,10 @@ def user_or_customer(uid): conn = Connection(server) conn.bind() 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: conn.unbind() return '%s,ou=customers,dc=ungleich,dc=ch' % 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: conn.unbind() return '%s,ou=customers,dc=ungleich,dc=ch' % uid @@ -72,11 +70,10 @@ class UserLookUp(object): conn = Connection(server) conn.bind() # 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) 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: # 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) )