ungleich-user/dal/dal/views.py

355 lines
16 KiB
Python

from django.shortcuts import render
from django.views.generic import View
from django.contrib.auth import authenticate, login, logout
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 base64 import b64encode, b64decode
from datetime import datetime
# Check to see if the username is already taken
# Helper function, not to be set up as a view
# Check the LDAP if the user exists
def check_user_exists(username):
with get_pool().next() as rpc:
return rpc.userlookup.lookup(username)
# 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
class Index(View):
# Basic binary choice, if it is an authenticated user, go straight to the options page,
# if not, then show the landing page
def get(self, request):
if request.user.is_authenticated:
return render(request, 'useroptions.html', { 'user': request.user } )
return render(request, 'landing.html')
# Basically does the same as the GET request, just with trying to login the user beforehand
# Shows an errorpage if authentication fails, since just looping to the landing page
# would be frustrating
def post(self, request):
username = request.POST.get('username')
password = request.POST.get('password')
pwd = r'%s' % password
user = authenticate(request, username=username, password=pwd)
if user is not None:
login(request, user)
return render(request, 'useroptions.html', { 'user': user } )
return render(request, 'loginfailed.html')
# Registering a user
class Register(View):
# Someone wants to register, throw up the page for that
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):
# message for the error template
service = 'register an user'
# urlname for 'go back' on the errorpage
urlname = 'register'
username = request.POST.get('username')
if username == "" or not username:
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': 'Please supply a username.' } )
# Check to see if username is already taken
# isalnum() may be a bit harsh, but is the most logical choice to make sure it's a username we
# can use
if not username.isalnum():
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': 'Username has to be alphanumeric.' } )
elif check_user_exists(username):
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': 'User already exists.' } )
password1 = request.POST.get('password1')
password2 = request.POST.get('password2')
# check if the supplied passwords match
if password1 != password2:
return render(request, 'error.html', { 'urlname': urlname, 'service': service,
'error': 'Your passwords did not match. Please supply the same password twice.' } )
email = request.POST.get('email')
# Is the emailaddress valid?
try:
validate_email(email)
except ValidationError:
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': 'The supplied email address is invalid.' } )
firstname = request.POST.get('firstname')
lastname = request.POST.get('lastname')
if firstname == "" or not firstname or lastname == "" or not lastname:
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': 'Please enter your firstname and lastname.' } )
# throw it to nameko to create the user
with get_pool().next() as rpc:
# so nothing strange happens if there are escapable chars
pwd = r'%s' % password1
result = rpc.createuser.create_user(username, pwd, firstname, lastname, email)
if result == True:
return render(request, 'usercreated.html', { 'user': username } )
else:
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': result } )
# Change user data for logged in users
class ChangeData(View):
# provide the form for the change request
def get(self, request):
urlname = 'change_data'
service = 'get default data for logged in user'
if not request.user.is_authenticated:
return render(request, 'mustbeloggedin.html')
user = request.user
login(request, user)
# 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 } )
# 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 } )
# get the change request
def post(self, request):
# variables for the error page
service = 'change user data'
urlname = 'change_data'
if not request.user.is_authenticated:
return render(request, 'mustbeloggedin.html')
user = str(request.user)
firstname = request.POST.get('firstname')
lastname = request.POST.get('lastname')
email = request.POST.get('email')
# 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:
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': 'The supplied email address is invalid.' } )
# 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:
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 } )
# Resets the password for a user
# Will need to send a confirmation email to the user and we will need a backend
# to confirm the request came from someone who has access to the email
# Out of scope except for creating the workflow
class ResetPassword(View):
# Presents the form with some information
def get(self, request):
return render(request, 'resetpassword.html')
# gets the data from confirming the reset request and checks if it was not a misclick
# (by having the user type in his username
def post(self, request):
urlname = 'reset_password'
service = 'send a password reset request'
user = request.POST.get('user')
# 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 } )
def email(self, user, email):
#TODO figure out how to send email
email_from = 'Userservice at ungleich <userservice@ungleich.ch>'
to = '%s <%s>' % (user, email)
subject = 'Password reset request for %s' % user
no-reply = True
link = self.build_reset_link(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 += '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
def build_reset_link(self, user):
host = 'localhost:8000'
x = PasswordResetTokenGenerator()
token = x.make_token(user)
buser = bytes(user, 'utf-8')
userpart = b64encode(buser)
d = datetime.now()
# TODO Make Model und put it into the database
link = 'https://%s/reset/%s/%s/' % (host, userpart.decode('utf-8'), token)
return link
# Catch the resetrequest and check it
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
self.clean_db()
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')
d = datetime.now()
#TODO write the model and check if token is still active and belongs to the user
# set checks_out = True if yes
if not checks_out:
return HttpResponse('Invalid URL.', status=404)
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'
password1 = request.POST.get("password1")
password2 = request.POST.get("password2")
user = request.POST.get("user")
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.' } )
with get_pool().next() as rpc:
pwd = r'%s' % password1
result = rpc.changepassword.change_password(user, pwd)
if result == True:
return render(request, 'changedpassword.html', { 'user': user } )
else:
return render(request, 'error.html', { 'service': service, 'error': result } )
# Cleans up outdated tokens
def clean_db(self):
# TODO write the model and use this to clean tokens > 24h old
# 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 } )
# 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)
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:
return render(request, 'error.html', { 'urlname': urlname, 'service': service,
'error': 'Please check if you typed the same password both times for the new password' } )
with get_pool().next() as rpc:
# Trying to change the password
pwd = r'%s' % password1
result = rpc.changepassword.change_password(user, pwd)
# Password was changed
if result == True:
return render(request, 'changedpassword.html', { 'user': user } )
# Password not changed, instead got some kind of error
else:
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': result } )
# Deletes an account
class DeleteAccount(View):
# Show the basic form for deleting an account
def get(self, request):
return render(request, 'deleteaccount.html')
# Reads the filled out form
def post(self, request):
# Variables for error page
urlname = 'account_delete'
service = 'delete an account'
# Does the user exist?
username = request.POST.get('username')
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')
pwd = r'%s' % password
check = authenticate(request, username=username, password=pwd)
if check is None:
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': 'Wrong password for user.' } )
# Try to delete the user
with get_pool().next() as rpc:
result = rpc.deleteuser.delete_user(username)
# User deleted
if result == True:
logout(request)
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 } )
class LogOut(View):
def get(self, request):
logout(request)
return HttpResponse("You have been logged out.", status=200)