From d07ff15f69da1ced383ce96f22568f988e217af0 Mon Sep 17 00:00:00 2001 From: William Colmenares Date: Thu, 2 May 2019 17:29:01 -0400 Subject: [PATCH] Add validation email before creation --- dal/templates/confirm_email.html | 29 ++++++ dal/urls.py | 4 +- dal/views.py | 171 +++++++++++++++++++++---------- 3 files changed, 151 insertions(+), 53 deletions(-) create mode 100644 dal/templates/confirm_email.html diff --git a/dal/templates/confirm_email.html b/dal/templates/confirm_email.html new file mode 100644 index 0000000..729af28 --- /dev/null +++ b/dal/templates/confirm_email.html @@ -0,0 +1,29 @@ +{% extends "base_short.html" %} +{% load i18n staticfiles bootstrap3 %} + +{% block title %} + Verify your email. +{% endblock %} + + + +{% block content %} +
+
+
+
+
+

{% trans " Check your email " %}

+

{% trans "In order to complete the sign up process, please check your email and follow the activation instructions." %}

+
+
+ +
+
+
+
+
+
+{% endblock %} diff --git a/dal/urls.py b/dal/urls.py index 1dab47a..394a8ba 100644 --- a/dal/urls.py +++ b/dal/urls.py @@ -13,7 +13,8 @@ from .views import ( Index, LogOut, ResetRequest, - UserCreateAPI + UserCreateAPI, + ActivateAccount ) urlpatterns = [ @@ -26,6 +27,7 @@ urlpatterns = [ path('index/', Index.as_view(), name="index"), path('logout/', LogOut.as_view(), name="logout"), path('reset///', ResetRequest.as_view()), + path('activate///////', ActivateAccount.as_view()), path('reset/', ResetRequest.as_view(), name="reset"), path('', Index.as_view(), name="login_index"), ] \ No newline at end of file diff --git a/dal/views.py b/dal/views.py index 3f04ca2..568207a 100644 --- a/dal/views.py +++ b/dal/views.py @@ -31,6 +31,47 @@ from django.conf import settings from django.contrib.auth.mixins import LoginRequiredMixin +def activate_account_link(base_url, user, pwd, firstname, lastname, email, epochutc): + tokengen = PasswordResetTokenGenerator() + pseudouser = PseudoUser() + token = tokengen.make_token(pseudouser) + buser = bytes(user, 'utf-8') + bpwd = bytes(pwd, 'utf-8') + bfirstname = bytes(firstname, 'utf-8') + blasttname = bytes(lastname, 'utf-8') + bemail = bytes(email, 'utf-8') + userpart = b64encode(buser) + pwdpart = b64encode(bpwd) + fnpart = b64encode(bfirstname) + lnpart = b64encode(blasttname) + mailpart = b64encode(bemail) + # create entry into the database + newdbentry = ResetToken(user=user, token=token, creation=epochutc) + newdbentry.save() + # set up the link + link = "{base_url}/activate/{user}/{pwd}/{fn}/{ln}/{mail}/(token)/".format( + base_url=base_url, user=userpart.decode('utf-8'), + pwd=pwdpart.decode('utf-8'), + fn=fnpart.decode('utf-8'), + ln=lnpart.decode('utf-8'), + mail=mailpart.decode('utf-8'), + token=token + ) + return link + + +def clean_db(): + """Revoves outdated tokens""" + # 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 + class Index(FormView): template_name = "landing.html" form_class = LoginForm @@ -92,34 +133,30 @@ class Register(View): pwd = r'%s' % password1 try: - ldap_manager = LdapManager() - ldap_manager.create_user( - username, pwd, firstname, lastname, email + creationtime = int(datetime.utcnow().timestamp()) + base_url = "{0}://{1}".format(self.request.scheme, + self.request.get_host()) + link = activate_account_link(base_url, username, pwd, firstname, lastname, email, creationtime) + email_from = settings.EMAIL_FROM_ADDRESS + to = ['%s <%s>' % (username, email)] + subject = 'Activate your ungleich account'.format(firstname) + body = 'You can activate your ungleich account account by clicking here.' \ + ' You can also copy and paste the following link into the address bar of your browser and follow' \ + ' the link in order to activate your account.\n\n{link}'.format(link=link) + # Build the email + mail = EmailMessage( + subject=subject, + body=body, + from_email=email_from, + to=to ) + mail.send() + except Exception as e: return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': e } ) - # Finally, we send the send user credentials via email - creationtime = int(datetime.utcnow().timestamp()) - # Construct the data for the email - email_from = settings.EMAIL_FROM_ADDRESS - to = ['%s <%s>' % (username, email)] - subject = '{}, Welcome to datacenterlight'.format(firstname) - body = 'The username {} was successfully created.\n'.format(username) - # Build the email - mail = EmailMessage( - subject=subject, - body=body, - from_email=email_from, - to=to - ) - try: - mail.send() - except Exception as e: - print(e) - pass - return render(request, 'usercreated.html', { 'user': username } ) + return render(request, 'confirm_email.html') class ChangeData(LoginRequiredMixin, View): login_url = reverse_lazy('login_index') @@ -297,7 +334,7 @@ class ResetRequest(View): # 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() + 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: @@ -336,10 +373,10 @@ class ResetRequest(View): 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.' } ) 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.'}) if len(password1) < 8: - return render(request, 'error.html', { 'service': service, 'error': 'The password is too short, please use a longer one. At least 8 characters.' } ) - # everything checks out, now change the password + return render(request, 'error.html', {'service': service, + 'error': 'The password is too short, please use a longer one. At least 8 characters.'}) ldap_manager = LdapManager() result = ldap_manager.change_password( @@ -353,17 +390,7 @@ class ResetRequest(View): else: return render(request, 'error.html', { 'service': service, 'error': result } ) - # Cleans up outdated tokens - def clean_db(self): - # 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 @@ -488,6 +515,47 @@ class PseudoUser(): pk = ''.join(choice(string.ascii_letters + string.digits) for _ in range(20)) password = ''.join(choice(string.ascii_letters + string.digits) for _ in range(30)) + +class ActivateAccount(View): + + def get(self, request, user=None, pwd=None, firstname=None, lastname=None, email=None, token=None): + clean_db() + if token is None: + return HttpResponse('Invalid URL', status=404) + elem_list = [user, pwd, firstname, lastname, email] + clean_list = [] + for value in elem_list: + try: + value_temp = bytes(value, 'utf-8') + value_decode = b64decode(value_temp) + value_clean = value_decode.decode('utf-8') + clean_list.append(value_clean) + except Exception as e: + return HttpResponse('Invalid URL', status=404) + checks_out = False + dbentries = ResetToken.objects.all().filter(user=clean_list[0]) + 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: + return HttpResponse('Invalid URL.', status=404) + # Token was found, create user + try: + ldap_manager = LdapManager() + ldap_manager.create_user( + clean_list[0], clean_list[1], clean_list[2], clean_list[3], clean_list[4] + ) + #Send welcome email + except Exception as e: + return render(request, 'error.html', {'urlname': 'register', + 'service': 'register an user', + 'error': e}) + return render(request, 'usercreated.html', { 'user': clean_list[0] } ) + + class UserCreateAPI(APIView): def post(self, request): @@ -508,25 +576,24 @@ class UserCreateAPI(APIView): pwd = r'%s' % User.objects.make_random_password() - try: - ldap_manager = LdapManager() - ldap_manager.create_user( - username, pwd, firstname, lastname, email - ) - except Exception as e: - return Response('While trying to create the user, an error was encountered: %s' % e, 400) - - # send user credentials via email + base_url = "{0}://{1}".format(self.request.scheme, + self.request.get_host()) creationtime = int(datetime.utcnow().timestamp()) + link = activate_account_link(base_url, username, pwd, firstname, lastname, email. creationtime) + # Construct the data for the email email_from = settings.EMAIL_FROM_ADDRESS to = ['%s <%s>' % (username, email)] - subject = 'Your datacenterlight credentials' - body = 'Your user was successfully created.\n' + subject = 'Ungleich account creation.' + body = 'A request has been sent to our servers to register you as a ungleich user.\n' + body += 'In order to complete the registration process you must ' \ + 'click here or copy & paste the following link into the address bar of ' \ + 'your browser.\n{link}\n'.format(link=link) body += 'Your credentials are:\n' body += 'Username: %s\n\n' % username body += 'Password: %s\n\n' % pwd - body += 'We strongly recommend you to after log in change your password.\n' + body += 'We strongly recommend after the activation to log in and change your password.\n' + body += 'This link will remain active for 24 hours.\n' # Build the email mail = EmailMessage( subject=subject, @@ -537,5 +604,5 @@ class UserCreateAPI(APIView): try: mail.send() except: - return Response('User was created, but failed to send the email', 201) - return Response('User successfully created', 200) + return Response('Failed to send the email', 201) + return Response('Email with activation link successfully sent', 200)