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