You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

285 lines
8.2 KiB

# -*- 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)