From c9f0d60e7cd0e54f65317a7f483594eb7939dd21 Mon Sep 17 00:00:00 2001 From: "Pavel \"LEdoian\" Turinsky" Date: Wed, 12 May 2021 01:48:43 +0200 Subject: [PATCH] =?UTF-8?q?J=C3=A1dro=20implementace=20view=20na=20doregis?= =?UTF-8?q?traci?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- seminar/views/registrace.py | 136 +++++++++++++++++++++++++++++++----- 1 file changed, 117 insertions(+), 19 deletions(-) diff --git a/seminar/views/registrace.py b/seminar/views/registrace.py index 26e9056e..6e1472fb 100644 --- a/seminar/views/registrace.py +++ b/seminar/views/registrace.py @@ -4,32 +4,119 @@ 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. -Proto všechno kromě view začíná podtržítkem, aby se to nenatáhlo jako součást -seminar.views +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.generic.edit import FormView +from django.views import View +from django.views.generic.base import TemplateResponseMixin import hmac +from typing import Optional -from django.conf.settings import AUTH_USER_MODEL, SECRET_KEY +from django.conf.settings import SECRET_KEY from seminar.models import Osoba -class DodatecnaRegistraceUzivateleView(FormView): - form = _RegistraceUzivateleForm +# Složitější class-based views mi neumožňují vracet chyby. +class DodatecnaRegistraceUzivateleView(TemplateResponseMixin, View): template_name = ... - success_url_pattern = ... - - def form_valid(self, form): - pass + 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 = AUTH_USER_MODEL +class RegistraceUzivateleForm(Form): + #model = User # Zkopírováno z přihlášky :-) username = forms.CharField(label='Přihlašovací jméno', max_length=256, @@ -47,21 +134,32 @@ class _RegistraceUzivateleForm(Form): widget=forms.PasswordInput()) # Dodatečné fieldy + token… - 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' -def _gen_token(usecase: str, data: dict[str, object]): +# Token kóduje všechno v sobě +def gen_token(usecase: _UseCase, data: dict[str, str]) -> str: ... -def _verify_token(usecase: str, data: dict[str, object]): - ... - -def _invite(osoba: Osoba): +def verify_token(usecase: _UseCase, token: str) -> Optional[dict[str, str]]: """ - Pošle dané osobě e-mail s odkazem na registraci. + 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í. """ + ... +