286 lines
8.2 KiB
Python
286 lines
8.2 KiB
Python
# -*- encoding: utf-8 -*-
|
|
import hashlib
|
|
from datetime import datetime
|
|
import logging
|
|
import math
|
|
|
|
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()
|
|
|
|
|
|
def send_mail_safe(subject, message, from_email, recipient_list):
|
|
""" Posle mail. Vyskytne-li se pri odesilani chyba, vypise ji do logu. Vrati uspesnost. """
|
|
try:
|
|
send_mail(subject, message, from_email, recipient_list)
|
|
except Exception as e:
|
|
logger.error("send_mail failed with error: %s" % str(e))
|
|
return False
|
|
return True
|
|
|
|
|
|
class EmailTokenAuthBackend:
|
|
"""
|
|
Provides authorization via email workflow.
|
|
The user is verified by a token previously sent by email.
|
|
Zde se take vytvari skutecny uzivatel (model AppUser) z registrovaneho (model AppRegEmail)
|
|
"""
|
|
|
|
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.phone = reg.phone
|
|
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()
|
|
|
|
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_safe(
|
|
'Piráti - nalodění - přihlašovací odkaz',
|
|
emailBody.format(
|
|
emailToken=emailToken,
|
|
baseUrl=appSettings.BASE_URL),
|
|
"nalodeni@pirati.cz",
|
|
[user.email]
|
|
)
|
|
|
|
|
|
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()
|
|
|
|
# TODO: tohle je jen QuickFix. Validovat zde settings je dost pozde,
|
|
# Melo by spadnout pri startu: predelat ten bordylek v main/settings.py
|
|
# pote predelat vsude kde se pouziva konstrukce int(TOKEN_VALID_SEC)
|
|
try:
|
|
token_validity = int(appSettings.TOKEN_VALID_SEC)
|
|
except:
|
|
token_validity = 60*60*24*3
|
|
logger.error("invalid seting NALODENI_TOKEN_VALID_SEC - should be string")
|
|
|
|
word_days = "dní"
|
|
if token_validity < 60*60*24*5:
|
|
word_days = "dny"
|
|
if token_validity <= 60*60*24*1:
|
|
word_days = "den"
|
|
|
|
emailBody = """\
|
|
Vítej na palubě!
|
|
|
|
Pro přihlášení do Nalodění klikni na následující odkaz:
|
|
|
|
{baseUrl}/prihlaseni/?t={emailToken}
|
|
|
|
Odkaz je možné použít pouze jednou, v případě potřeby si nech zaslat nový.
|
|
Odkaz platí {days} {word}.
|
|
|
|
S pozdravem
|
|
Piráti
|
|
"""
|
|
send_mail_safe(
|
|
'Piráti - nalodění - registrační odkaz',
|
|
emailBody.format(
|
|
emailToken=emailToken,
|
|
baseUrl=appSettings.BASE_URL,
|
|
days=int(math.floor(token_validity / (60*60*24))),
|
|
word=word_days),
|
|
"nalodeni@pirati.cz",
|
|
[rt.email]
|
|
)
|
|
|
|
|
|
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()
|
|
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_safe(
|
|
'Piráti - nalodění - ověření kontaktního emailu',
|
|
emailBody.format(
|
|
email_contact = ecw,
|
|
emailToken=emailToken,
|
|
baseUrl=appSettings.BASE_URL),
|
|
"nalodeni@pirati.cz",
|
|
[ecw]
|
|
)
|
|
|
|
return True
|
|
|
|
|
|
#TODO:: osetreni transakci (pro pripad utoku typu DDoS)
|