ungleich-user/dal/views.py

411 lines
18 KiB
Python
Raw Normal View History

2019-01-26 13:02:37 +00:00
# Imports from django
2018-10-09 17:49:47 +00:00
from django.shortcuts import render
2019-02-19 22:16:05 +00:00
from django.views.generic import View, FormView
from django.contrib.auth import authenticate, login, logout
2018-10-09 17:49:47 +00:00
from django.contrib.auth.models import User
from django.http import HttpResponse, HttpResponseRedirect
from django.core.validators import validate_email, ValidationError
2018-10-09 17:49:47 +00:00
from django.urls import reverse_lazy
2018-10-15 15:52:15 +00:00
from django.contrib.auth.tokens import PasswordResetTokenGenerator
from django.core.mail import EmailMessage
from .models import ResetToken
2019-02-19 22:16:05 +00:00
from .forms import LoginForm
2019-02-23 20:29:33 +00:00
from .ungleich_ldap import LdapManager
# Imports for the extra stuff not in django
2019-01-26 13:02:37 +00:00
2018-10-15 15:52:15 +00:00
from base64 import b64encode, b64decode
from datetime import datetime
2019-01-26 13:02:37 +00:00
from random import choice, randint
import string
2018-11-07 12:08:46 +00:00
2019-01-26 16:46:06 +00:00
import os
2019-01-26 14:19:58 +00:00
2019-01-26 16:46:06 +00:00
# Use ldap, like django_auth_backend
import ldap
2019-01-27 12:00:45 +00:00
import ldap.modlist as modlist
2018-10-09 17:49:47 +00:00
2019-01-26 16:46:06 +00:00
from django.conf import settings
2018-10-09 17:49:47 +00:00
2019-02-19 22:16:05 +00:00
class Index(FormView):
template_name = "landing.html"
form_class = LoginForm
2019-02-23 08:20:58 +00:00
success_url = 'useroptions.html'
def form_valid(self, form):
email = form.cleaned_data.get('email')
password = form.cleaned_data.get('password')
user = authenticate(username=email, password=password)
if user is not None:
login(self.request, user)
return render(self.request, 'useroptions.html', { 'user': user } )
return render(self.request, 'loginfailed.html')
2018-10-09 17:49:47 +00:00
class Register(View):
def get(self, request):
return render(request, 'registeruser.html')
# Someone filled out the register page, do some basic checks and throw it at nameko
def post(self, request):
service = 'register an user'
2018-10-09 17:49:47 +00:00
urlname = 'register'
username = request.POST.get('username')
2019-01-26 16:46:06 +00:00
2018-10-14 16:17:59 +00:00
if username == "" or not username:
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': 'Please supply a username.' } )
2019-01-26 14:19:58 +00:00
2018-10-09 17:49:47 +00:00
password1 = request.POST.get('password1')
password2 = request.POST.get('password2')
if password1 != password2:
2019-01-26 14:19:58 +00:00
return render(request, 'error.html', { 'urlname': urlname,
'service': service,
2019-01-26 16:46:06 +00:00
'error': "Passwords don't match." } )
2018-10-23 16:13:25 +00:00
2018-10-09 17:49:47 +00:00
email = request.POST.get('email')
try:
validate_email(email)
except ValidationError:
2019-01-26 16:46:06 +00:00
return render(request, 'error.html', { 'urlname': urlname,
'service': service,
'error': 'The supplied email address is invalid.' } )
2018-10-09 17:49:47 +00:00
firstname = request.POST.get('firstname')
lastname = request.POST.get('lastname')
2019-01-26 16:46:06 +00:00
if not firstname or not lastname:
return render(request, 'error.html', { 'urlname': urlname,
'service': service,
'error': 'Please enter your firstname and lastname.' } )
2018-10-09 17:49:47 +00:00
2019-01-26 14:19:58 +00:00
# so nothing strange happens if there are escapable chars
pwd = r'%s' % password1
2018-10-09 17:49:47 +00:00
2019-01-26 14:19:58 +00:00
try:
2019-02-23 20:29:33 +00:00
ldap_manager = LdapManager()
ldap_manager.create_user(
username, pwd, firstname, lastname, email
)
2019-01-26 14:19:58 +00:00
except Exception as e:
return render(request, 'error.html', { 'urlname': urlname,
'service': service,
'error': e } )
2018-10-09 17:49:47 +00:00
2019-01-26 14:19:58 +00:00
return render(request, 'usercreated.html', { 'user': username } )
2019-01-26 14:19:58 +00:00
class ChangeData(View):
2018-10-09 17:49:47 +00:00
# provide the form for the change request
def get(self, request):
urlname = 'change_data'
service = 'get default data for logged in user'
2018-10-09 17:49:47 +00:00
if not request.user.is_authenticated:
return render(request, 'mustbeloggedin.html')
user = request.user
login(request, user)
2018-10-10 12:13:49 +00:00
# get basic data (firstname, lastname, email)
with get_pool().next() as rpc:
(state, firstname, lastname, email) = rpc.getuserdata.get_data(str(request.user))
# If it throws an error, the errormessage gets put into firstname.. not great naming, but works best this way
if state == "error":
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': firstname } )
2018-10-09 17:49:47 +00:00
# The template puts the old data as standard in the fields
else:
return render(request, 'changeuserdata.html', { 'user': str(request.user), 'firstname': firstname, 'lastname': lastname, 'email': email } )
2018-10-09 17:49:47 +00:00
# get the change request
def post(self, request):
# variables for the error page
service = 'change user data'
2018-10-09 17:49:47 +00:00
urlname = 'change_data'
# Only logged in users may change data
2018-10-09 17:49:47 +00:00
if not request.user.is_authenticated:
return render(request, 'mustbeloggedin.html')
2019-01-26 13:02:37 +00:00
user = str(request.user)
2018-10-09 17:49:47 +00:00
firstname = request.POST.get('firstname')
lastname = request.POST.get('lastname')
email = request.POST.get('email')
2019-01-26 13:02:37 +00:00
2018-10-09 17:49:47 +00:00
# Some sanity checks for the supplied data
if firstname == "":
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': 'Please enter a firstname.' } )
elif lastname == "":
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': 'Please enter a lastname.' } )
elif email == "":
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': 'Please enter an email.' } )
try:
validate_email(email)
except ValidationError:
2018-10-09 17:49:47 +00:00
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': 'The supplied email address is invalid.' } )
2018-10-10 12:13:49 +00:00
# Trying to change the data
with get_pool().next() as rpc:
result = rpc.changeuserdata.change_data(user, firstname, lastname, email)
# Data change worked
if result == True:
2018-10-09 17:49:47 +00:00
return render(request, 'changeddata.html', { 'user': user, 'firstname': firstname, 'lastname': lastname, 'email': email } )
# Data change did not work, display error
else:
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': result } )
2018-10-09 17:49:47 +00:00
class ResetPassword(View):
def get(self, request):
return render(request, 'resetpassword.html')
def post(self, request):
urlname = 'reset_password'
service = 'send a password reset request'
2018-10-09 17:49:47 +00:00
user = request.POST.get('user')
2018-10-15 15:52:15 +00:00
# First, check if the user exists
if not check_user_exists(user):
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': 'The user does not exist.' } )
# user exists, so try to get email
with get_pool().next() as rpc:
(state, tmp1, tmp2, email) = rpc.getuserdata.get_data(user)
# Either error with the datalookup or no email provided
if state == "error" or email == 'No email given' or not email:
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': 'Unable to retrieve email address for user.' } )
# Try to send the email out
emailsend = self.email(user, email)
# Email got sent out
if emailsend == True:
return render(request, 'send_resetrequest.html', { 'user': user } )
# Error while trying to send email
else:
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': emailsend } )
2018-10-15 19:24:53 +00:00
# Sends an email to the user with the 24h active link for a password reset
2018-10-15 15:52:15 +00:00
def email(self, user, email):
2018-10-15 19:24:53 +00:00
# getting epoch for the time now in UTC to spare us headache with timezones
creationtime = int(datetime.utcnow().timestamp())
# Construct the data for the email
2018-11-07 12:08:46 +00:00
email_from = 'Userservice at ungleich <%s>' % config['EMAIL']['EMAILFROM']
to = ['%s <%s>' % (user, email)]
2018-10-15 15:52:15 +00:00
subject = 'Password reset request for %s' % user
2018-10-15 19:24:53 +00:00
link = self.build_reset_link(user, creationtime)
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
2018-10-15 15:52:15 +00:00
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
2018-10-15 19:24:53 +00:00
body += 'The link will remain active for 24 hours.\n'
# 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
2018-10-15 15:52:15 +00:00
2018-10-15 19:24:53 +00:00
# Builds the reset link for the email and puts the token into the database
def build_reset_link(self, user, epochutc):
2019-01-26 13:02:37 +00:00
# set up the data
2018-11-07 12:08:46 +00:00
host = 'account-staging.ungleich.ch'
2018-10-15 19:24:53 +00:00
tokengen = PasswordResetTokenGenerator()
# create some noise for use in the tokengenerator
pseudouser = PseudoUser()
token = tokengen.make_token(pseudouser)
2018-10-15 19:24:53 +00:00
buser = bytes(user, 'utf-8')
userpart = b64encode(buser)
2019-01-26 13:02:37 +00:00
# create entry into the database
2018-10-15 19:24:53 +00:00
newdbentry = ResetToken(user=user, token=token, creation=epochutc)
newdbentry.save()
# set up the link
2018-10-15 15:52:15 +00:00
link = 'https://%s/reset/%s/%s/' % (host, userpart.decode('utf-8'), token)
return link
2018-10-15 19:24:53 +00:00
# Catch the resetrequest URL and check it
2018-10-15 15:52:15 +00:00
class ResetRequest(View):
# Gets the URL with user in b64 and the token, and checks it
# Also cleans the database
def get(self, request, user=None, token=None):
# Cleans up outdated tokens
2018-10-15 19:24:53 +00:00
# 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
2018-10-15 15:52:15 +00:00
self.clean_db()
2018-10-15 19:24:53 +00:00
# If user and token are not supplied by django, it was called from somewhere else, so it's
# invalid
2018-10-15 15:52:15 +00:00
if user == None or token == None:
return HttpResponse('Invalid URL.', status=404)
# extract user from b64 format
tmp_user = bytes(user, 'utf-8')
user = b64decode(tmp_user)
user_clean = user.decode('utf-8')
2018-10-15 19:24:53 +00:00
# set checks_out = True if token is found in database
dbentries = ResetToken.objects.all().filter(user=user_clean)
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
2018-10-15 15:52:15 +00:00
if not checks_out:
return HttpResponse('Invalid URL.', status=404)
2018-10-15 19:24:53 +00:00
# Token was found, supply the form
2018-10-15 15:52:15 +00:00
else:
return render(request, 'resetpasswordnew.html', { 'user': user_clean } )
# Gets the post form with the new password and sets it
def post(self, request):
service = 'reset the password'
2018-10-15 19:24:53 +00:00
# get the supplied passwords
2018-10-15 15:52:15 +00:00
password1 = request.POST.get("password1")
password2 = request.POST.get("password2")
2018-10-15 19:24:53 +00:00
# get the hidden value of user
2018-10-15 15:52:15 +00:00
user = request.POST.get("user")
2018-10-15 19:24:53 +00:00
# some checks over the supplied data
if user == "" or not user or user != self.request.user.username:
2018-10-15 19:24:53 +00:00
return render(request, 'error.html', { 'service': service, 'error': 'Something went wrong. Did you use the supplied form?' } )
2018-10-15 15:52:15 +00:00
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.' } )
2018-10-23 17:41:49 +00:00
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.' } )
2018-10-15 19:24:53 +00:00
# everything checks out, now change the password
ldap_manager = LdapManager()
result = ldap_manager.change_password(
("uid={uid}," + settings.LDAP_CUSTOMER_DN).format(uid=user),
password1
)
# password change successful
if result:
2018-10-15 15:52:15 +00:00
return render(request, 'changedpassword.html', { 'user': user } )
2018-10-15 19:24:53 +00:00
# Something went wrong while changing the password
2018-10-15 15:52:15 +00:00
else:
return render(request, 'error.html', { 'service': service, 'error': result } )
# Cleans up outdated tokens
def clean_db(self):
2018-10-15 19:24:53 +00:00
# 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
2018-10-09 17:49:47 +00:00
# The logged in user can change the password here
class ChangePassword(View):
# Presents the page for a logged in user
def get(self, request):
if not request.user.is_authenticated:
return render(request, 'mustbeloggedin.html')
return render(request, 'changepassword.html', { 'user': request.user } )
2019-01-26 13:02:37 +00:00
2018-10-09 17:49:47 +00:00
# Does some checks on the supplied data and changes the password
def post(self, request):
# Variables for the error page
urlname = 'change_password'
service = 'change the password'
if not request.user.is_authenticated:
return render(request, 'mustbeloggedin.html')
login(request, request.user)
user = str(request.user)
2018-10-09 17:49:47 +00:00
oldpassword = request.POST.get('oldpassword')
check = authenticate(request, username=user, password=oldpassword)
# Is the right password for the user supplied?
if check is None:
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': 'Wrong password for the user.' } )
password1 = request.POST.get('password1')
password2 = request.POST.get('password2')
# Are both passwords from the form the same?
if password1 != password2:
2019-01-26 13:02:37 +00:00
return render(request, 'error.html', { 'urlname': urlname, 'service': service,
2018-10-09 17:49:47 +00:00
'error': 'Please check if you typed the same password both times for the new password' } )
2018-10-23 17:41:49 +00:00
# Check for password length
if len(password1) < 8:
return render(request, 'error.html', { 'urlname': urlname, 'service': service,
'error': 'The password is too short, please use a longer one. At least 8 characters.' } )
2019-02-23 18:50:42 +00:00
from .ungleich_ldap import LdapManager
ldap_manager = LdapManager()
result = ldap_manager.change_password(
("uid={uid}," + settings.LDAP_CUSTOMER_DN).format(uid=user),
password1
)
# Password was changed
2019-02-23 18:50:42 +00:00
if result:
2018-10-09 17:49:47 +00:00
return render(request, 'changedpassword.html', { 'user': user } )
# Password not changed, instead got some kind of error
2018-10-09 17:49:47 +00:00
else:
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': result } )
2018-10-09 17:49:47 +00:00
# Deletes an account
2018-10-09 17:49:47 +00:00
class DeleteAccount(View):
# Show the basic form for deleting an account
2018-10-09 17:49:47 +00:00
def get(self, request):
2018-10-10 12:13:49 +00:00
return render(request, 'deleteaccount.html')
2018-10-09 17:49:47 +00:00
# Reads the filled out form
2018-10-10 12:13:49 +00:00
def post(self, request):
# Variables for error page
urlname = 'account_delete'
service = 'delete an account'
# Does the user exist?
username = request.POST.get('username')
2018-10-10 12:13:49 +00:00
if not check_user_exists(username):
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': 'Unknown user.' } )
# Do user and password match?
password = request.POST.get('password')
2018-10-14 19:40:36 +00:00
pwd = r'%s' % password
check = authenticate(request, username=username, password=pwd)
2018-10-10 12:13:49 +00:00
if check is None:
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': 'Wrong password for user.' } )
2019-01-26 13:02:37 +00:00
2018-10-10 12:13:49 +00:00
# Try to delete the user
with get_pool().next() as rpc:
result = rpc.deleteuser.delete_user(username)
# User deleted
if result == True:
logout(request)
2018-10-10 12:13:49 +00:00
return render(request, 'deleteduser.html', { 'user': username } )
# User not deleted, got some kind of error
else:
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': result } )
2018-10-09 17:49:47 +00:00
2018-10-15 19:24:53 +00:00
# Log out the session
class LogOut(View):
def get(self, request):
logout(request)
return HttpResponse("You have been logged out.", status=200)
2019-01-26 14:19:58 +00:00
# TO be clarified
# 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))