""" Registrace uživatelů k existujícím osobám V tomto souboru bude asi všechno, co je relevantní (kromě template), protože to je dostatečně malá a jednorázová věc. Importovat prosím jen ty dva Views, ve výjimečných případech invite, nic dalšího. """ #TODO: Logování (tohle logovat chce skoro určitě) #TODO: Omezení počtu pokusů (per token -- města / údaje se bruteforcit dají, na rozdíl od tokenů) from enum import Enum from django import forms from django.contrib.auth.models import User, Permission from django.forms import Form from django.views import View from django.views.generic.base import TemplateResponseMixin import hmac from typing import Optional from django.conf.settings import SECRET_KEY from seminar.models import Osoba # Složitější class-based views mi neumožňují vracet chyby. class DodatecnaRegistraceUzivateleView(TemplateResponseMixin, View): template_name = ... form = RegistraceUzivateleForm def get(self, request, url_token): # Ověřit token tok_data = verify_token(UseCase.email, url_token) if tok_data is None: return render_to_response( context={ 'error': 'Token není platný', }, status_code=400, ) # Zkontrolovat, že to není moc staré poslání now = datetime.now() token_generation = datetime.fromisoformat(tok_data['timestamp']) delta = now - token_generation if delta >= timedelta(weeks=10): return render_to_response( context={ 'error': 'Vypršela časová platnost tokenu', }, status_code=400, ) # Najít osobu osoba_id = int(tok_data['osoba']) # Pokud tam není, tak jsme vygenerovali špatný token my (byl validní). osoba = m.Osoba.objects.get(id=osoba_id) if osoba.user is not None: return render_to_response( context={ 'error': 'Už máte uživatele', }, status_code=400, ) # Vrátit view s formulářem a formulářovým tokenem form_token = gen_token(UseCase.form, data={ 'osoba': str(osoba_id), 'timestamp': datetime.now().isoformat(), }) return render_to_response( context={ 'form': self.form(initial={ 'token': form_token, }), } ) def post(self, request, url_token): # Zkontrolovat formulář form = self.form(self.request.POST) if not form.is_valid(): return render_to_response( context={ 'error': 'Chyba ve formuláři', # TODO: Umíme dostat konkrétní detaily? # TODO: Formulář pro zkusení znovu? }, status_code=400, ) form_data = form.cleaned_data # Zkontrolovat token token_data = verify_token(UseCase.form, form_data['token']) if token_data is None: return render_to_response( context={ 'error': 'Neplatný token', }, status_code=400, ) osoba_id = int(token_data['osoba']) osoba = m.Osoba.objects.get(id=osoba_id) # Zkontrolovat verifikační field ... # Vyrobit uživatele u = User.objects.create_user( username=form_data['username'], password=form_data['password'], email=osoba.email, #first_name=o.jmeno, #last_name=o.prijmeni, ) u.user_permissions.add(Permission.objects.get(codename__exact='resitel')) # Přesměrovat na kontrolu údajů return ... class KontrolaUdajuASouhlasyView(FormView): ... class RegistraceUzivateleForm(Form): #model = User # Zkopírováno z přihlášky :-) username = forms.CharField(label='Přihlašovací jméno', max_length=256, required=True, help_text='Tímto jménem se následně budeš přihlašovat pro odevzdání řešení a další činnosti v semináři') password = forms.CharField( label='Heslo', max_length=256, required=True, widget=forms.PasswordInput()) password_check = forms.CharField( label='Ověření hesla', max_length=256, required=True, widget=forms.PasswordInput()) # Dodatečné fieldy + token… token = forms.CharField(widget=forms.HiddenInput(), required=True) verifikace_TODO = ... # TODO: Co verifikovat # TODO: clean_username, verifikace … def invite(osoba: Osoba): """ Pošle dané osobě e-mail s odkazem na registraci. """ ... # Pozor, tokeny existují dva: jeden do URL do mailu, druhý do hidden položky ve formuláři. class UseCase(Enum): email = 'email' form = 'form' # Token kóduje všechno v sobě def gen_token(usecase: _UseCase, data: dict[str, str]) -> str: ... def verify_token(usecase: _UseCase, token: str) -> Optional[dict[str, str]]: """ Vrací slovník dat, pokud je token validní, jinak None. Inspirováno OSMO, je díky tomu jednoduché zároveň předat dekódovaná data a výsledek ověření. """ ...