nalodeni.pirati.cz/nalodeni/auth.py

263 lines
7.4 KiB
Python

# -*- encoding: utf-8 -*-
import hashlib
from datetime import datetime
import logging
from django.contrib.auth import get_user_model
from django.utils.crypto import get_random_string
from django.core.mail import send_mail
from django.core.validators import validate_email
from django.core.exceptions import ValidationError
from django.conf import settings as appSettings
from . import models
# Get an instance of a logger
logger = logging.getLogger(__name__)
# Our user model
USER_MODEL = get_user_model()
class EmailTokenAuthBackend:
"""
Provides authorization via email workflow.
The user is verified by a token previously sent by email.
"""
def get_user(self, user_id):
try:
return USER_MODEL.objects.get(pk=user_id)
except USER_MODEL.DoesNotExist:
return None
def authenticate(self, request, emailToken=None):
if emailToken is None:
return None
# do we have a user with this valid token ?
try:
u = USER_MODEL.objects.get(emailToken=emailToken)
if u.etStamp and (datetime.now() - u.etStamp).total_seconds() < int(appSettings.TOKEN_VALID_SEC):
u.emailToken = None
u.etStamp = None
u.save()
return u
else:
u.emailToken = None
u.etStamp = None
u.save()
raise ValidationError("Přihlašovací odkaz vypršel, nechte si poslat nový.")
except USER_MODEL.DoesNotExist:
pass
# is this a first-time login, so the user has to be created ?
rslt = models.AppRegEmail.objects.filter(emailToken=emailToken)
if len(rslt) == 0:
return None
elif len(rslt) == 1:
reg = rslt[0]
if reg.etStamp and (datetime.now() - reg.etStamp).total_seconds() < int(appSettings.TOKEN_VALID_SEC):
rsltUsers = models.AppUser.objects.filter(username=reg.email)
if len(rsltUsers) != 0 :
raise ValidationError("Uživatelské jméno je již obsazeno.")
rsltUsers = models.AppUser.objects.filter(email=reg.email)
if len(rsltUsers) != 0 :
raise ValidationError("E-mailová adresa je již obsazena.")
# token valid, create the user
u = USER_MODEL()
u.username = reg.email
u.email = reg.email
u.postcode = reg.postcode
u.kind = reg.kind
u.interestedIn = reg.interestedIn
u.userform = reg.userform
u.dc_stamp = datetime.now()
u.save()
# remove the approved email from registration
reg.userform = None
reg.save()
reg.delete()
return u
else:
raise ValidationError("Registrační odkaz vypršel, nechte si poslat nový.")
else:
# multiple tokens, which should not happen
raise Exception("Multiple records with the same token.")
return None
def sendLoginToken(user):
"""
Generate and send a token to the user.
"""
emailToken = get_random_string(120)
user.emailToken = emailToken
user.etStamp = datetime.now()
user.save()
emailSubj = 'Piráti - nalodění - přihlašovací odkaz'
emailBody = """\
Dobrý den,
níže zasíláme přihlašovací odkaz do aplikace Pirátů "Nalodění":
{baseUrl}/prihlaseni/?t={emailToken}
Přihlásíte se kliknutím na odkaz, nebo jeho překopírováním do prohlížeče internetových stránek.
Odkaz je možné použít pouze jednou, v případě potřeby si nechte zaslat nový odkaz.
S pozdravem
Piráti
"""
send_mail(
emailSubj,
emailBody.format(
emailToken=emailToken,
baseUrl=appSettings.BASE_URL),
"nalodeni@pirati.cz",
[user.email], # email to ...
fail_silently=False,
)
def sendRegisterToken(f_email):
"""
Generate and send a token to the registered email.
"""
# Check for user non-existence with this email
f_email = f_email.strip().lower()
rslt = models.AppUser.objects.filter(email__iexact=f_email)
if len(rslt) == 0:
pass
elif len(rslt) == 1:
# send login token instead
return sendLoginToken(rslt[0])
else:
logger.error("More AppUser objects with the same email.")
return
# check registrations
rslt = models.AppRegEmail.objects.filter(email__iexact=f_email)
if len(rslt) == 1:
rt = rslt[0]
elif len(rslt) == 0:
rt = models.AppRegEmail()
rt.email = f_email
else:
logger.error("More AppRegEmail objects with the same email.")
return
sendRegisterTokenReg(rt)
def sendRegisterTokenReg(rt):
""" Send registration email to an already existing AppRegEmail instance 'rt'. """
# create token
emailToken = get_random_string(120)
rt.emailToken = emailToken
rt.etStamp = datetime.now()
rt.save()
emailSubj = 'Piráti - nalodění - registrační odkaz'
emailBody = """\
Dobrý den,
níže Vám zasíláme registrační odkaz do aplikace Pirátů "Nalodění":
{baseUrl}/prihlaseni/?t={emailToken}
V registraci pokračujte kliknutím na odkaz, nebo jeho překopírováním do prohlížeče internetových stránek.
Odkaz je možné použít pouze jednou, v případě potřeby si nechte zaslat nový odkaz.
S pozdravem
Piráti
"""
send_mail(
emailSubj,
emailBody.format(
emailToken=emailToken,
baseUrl=appSettings.BASE_URL),
"nalodeni@pirati.cz",
[rt.email], # email to ...
fail_silently=False,
)
def sendEmailContactVerificationToken(user):
""" Send validation email to user.email_contact. """
# The wanted new value, save it here so we do not accidentally change it,
# or someone else through the user-object.
ecw = user.email_contact
if ecw is None or ecw == "":
raise ValidationError("Chybí kontaktní email.")
# ValidationError se odchytava vyse
validate_email(ecw)
# create token
rand_str = get_random_string(120)
user.email_contact_token = "%s-%s" % ( int(datetime.now().timestamp()), rand_str )
user.email_contact_verified = False
user.save()
sha256hash = hashlib.sha256()
sha256hash.update(rand_str.encode('utf-8'))
sha256hash.update(ecw.encode('utf-8'))
emailToken = sha256hash.hexdigest()
emailSubj = 'Piráti - nalodění - ověření kontaktního emailu'
emailBody = """\
Dobrý den,
níže Vám zasíláme odkaz pro ověření platnosti kontaktní e-mailové adresy.
Změna adresy na {email_contact} byla vyžádána v aplikace Pirátů "Nalodění".
Adresu ověříte přihlášením se do aplikace Nalodění
(https://nalodeni.pirati.cz/prihlaseni/) a následným kliknutím na odkaz:
{baseUrl}/ja-pirat/profil/?t={emailToken}
Pokud jste tuto změnu nežádali, můžete tuto zprávu smazat.
S pozdravem
Piráti
"""
send_mail(
emailSubj,
emailBody.format(
email_contact = ecw,
emailToken=emailToken,
baseUrl=appSettings.BASE_URL),
"nalodeni@pirati.cz",
[ecw], # email to ...
fail_silently=False,
)
return True
#TODO:: osetreni transakci (pro pripad utoku typu DDoS)