Django

Auth

1.  Prokousávání se

Mám již funkční aplikaci a chci jí rozšířit o autentifikaci. Použiji dekorátor login_required:

 from django.contrib.auth.decorators import login_required

 @login_required
 def nejake_view(request):

Nyní při přístupu k danému view je vyžadováno, aby byl přihlášený uživatel. Pochopitelně není, tak se objeví chyba, že nemůže najít url accounts/login/ - tj. nejí-li uživatel přihlášen, je přesměrován na toto url a my si ho musíme přidat do našich url (adresu lze změnit v settings.LOGIN_URL - tahle je výchozí).

 #urls.py
 import django.contrib.auth.views
 ...
 url(r'^accounts/login/$', 'django.contrib.auth.views.login'),

Takže jsme se dostali do vestavěného view "login" a posouváme se o jednu chybu dále: Nemáme šablonu 'registration/login.html'. Tu umístíme buď to templates pro projekt, nebo do templates pro aplikaci.

Příklad šablony najdeme u popisu funkce "login" dole na této stránce. Obshuje položky pro username a password a skryté pole pro položku "next". Odesílá se (action=) sobě - tj login. Při úspěšném přihlášení (děje se samo) se provede dekorované view. Při neúspěchu uvidíme opět stránku s přihlašovacím formulářem a chybovou hláškou.

No a to je kupodivu všechno! :-O :-) (pro začátek).

2.  Autentifikace

Django má vestavěný systém pro autentifikaci uživatelů, včetně formulářů atd. Výchozí nastavení už obsahuje v souboru settings.py vše potřebné.

 django.contrib.auth

Vestavěný systém je minimalistický. Existují nástroje třetích stran s bohatší výbavou.

Základním vestavěným prvkem je model User

 from django.contrib.auth.models import User

2.1  atributy třídy User

povinné:

  • username
  • password

nepovinné:

  • first_name (30zn.)
  • last_name (30zn.)
  • email
  • groups - ManyToMany(Groups)
  • user_permissions - ManyToMany(Permissions)
  • is_staff (bool)
  • is_active (bool) - je účet platný? (heslo o.k., ale zneplatněn)
  • is_superuser (bool)
  • last_login (autom.)
  • date_joined (autom.)

Heslo nejde nastavit napřímo (tedy jde ale nebude fungovat). Používat metodu set_password(). Hesla jsou ukládána šifrovaně.

2.2  metody instance

  • get_username()
  • is_anonymous()
  • is_authenticated()
  • get_full_name()
  • set_password(raw_password)
  • check_password(raw_password)
  • set_unusable_password()
  • has_usable_password()
  • get_group_permissions(obj=None)
  • get_all_permissions(obj=None)
  • has_perm(perm, obj=None)
  • has_perms(perm_list, obj=None)
  • has_module_perms(package_name)
  • email_user(subject, message, from_email=None)

2.3  metody manageru (třídy)

models.UserManager

  • create_user(username, email=None, password=None, **extra_fields)
  • create_superuser(self, username, email, password, **extra_fields)
 User.objects.create_user('jenda', 'jenda@email.com', 'jen-heslo')

Extra_fields jsou nepovinné položky modelu User.

models.AnonymousUser

Tato třída je jako normální User, ale přednastaví :

  • id na None
  • is_staff, is_superuser = False
  • is_active = False
  • groups, user_permissions - prázdné
  • is_anonymous = True
  • is_authenticated = False
  • set_password(), check_password(), save() a delete() vyhodí NotImplementedError.

2.4  Autentifikace uživatele

 django.contrib.auth import authenticate

 user = authenticate(username='nekdo', password='heslo')

Vrátí objekt uživatele při úspěšném ověření, jinak None.

 from django.contrib.auth import authenticate
 user = authenticate(username='john', password='secret')
 if user is not None:
     # the password verified for the user
     if user.is_active:
         print("User is valid, active and authenticated")
     else:
         print("The password is valid, but the account has been disabled!")
 else:
     # the authentication system was unable to verify the username and password
     print("The username and password were incorrect.")

2.5  Permissions (oprávnění) a autorizace

Django má vlastní systém oprávnění, který používá ve svém Admin rozhraní, ale je volně k dispozici. Oprávnění je možné udělovat na úrovni uživatele nebo skupiny (uživatele pak do skupin přidáváme).

Ve výchozím stavu se Django postará, aby ke každému modelu byla vytvořena oprávnění pro přidání, změnu a smazání. Tato oprávnění jsou v tabulce auth_permissions. Jsou to názvy oprávnění, přidělit je uživatalům a skupinám se teprve musí. Je to tedy jakýsi "číselník" oprávnění k jednotlivým modelům.

Oprávnění můžeme testovat na instanci modelu User:

 user.has_perm('model_add')
 user.has_perm('model_change')
 user.has_perm('model_delete')

Vlastní oprávnění

Můžeme definovat na úrovni modelu v části Meta, nebo přímo v programu:

 from myapp.models import BlogPost
 from django.contrib.auth.models import Group, Permission
 from django.contrib.contenttypes.models import ContentType

 content_type = ContentType.objects.get_for_model(BlogPost)
 permission = Permission.objects.create(codename='can_publish',
                                        name='Can Publish Posts',
                                        content_type=content_type)

2.6  Groups - skupiny

 django.contrib.auth.models.Group

Uživatel může patřit do více skupin (M:N). Uživatel automaticky získává všechna oprávnění přidělená skupině v níž se nachází.

2.7  Autentifikace ve Web Requests

Objekty request jsou propojeny s autentifikačním systémem protřednictvím sessions a middleware. Request obsahuje atribut user, který představuje aktuálního uživatele: request.user

Není-li uživatel přihlášen, je request.user nastaven na AnonymousUser. Je-li, pak je to instace třídy User.

 if request.user.is_authenticated():
     # Do something for authenticated users.
 else:
     # Do something for anonymous users.

Přihlášeni a odhlášení uživatele

Autentifikovaného uživatele můžeme vložit do session pomocí funkce login:

from django.contrib.auth import authenticate, login

 def my_view(request):
     username = request.POST['username']
     password = request.POST['password']
     user = authenticate(username=username, password=password)
     if user is not None:
         if user.is_active:
             login(request, user)
             # Redirect to a success page.
         else:
             # Return a 'disabled account' error message
     else:
         # Return an 'invalid login' error message.

Před voláním login je vždy nutné volat authenticate! To nastaví u uživatele atribut s informací, který backend ho autentifikoval. Bez toho to nejede :-)

Odhlášení děláme funkcí logout:

 from django.contrib.auth import logout

 def logout_view(request):
     logout(request)
     # Redirect to a success page.

Logout nevyhodí žádnou výjimku, pokud uživatel přihlášený nebyl. Logout vymaže všechna data vztahující se k uživateli ze session.

2.8  Omezení přístupu na přihlášené uživatele

Na hrubo:

def my_view(request):
    if not request.user.is_authenticated():
        return redirect('/login/?next=%s' % request.path)

Použití dekorátoru

from django.contrib.auth.decorators import login_required

 login_required([redirect_field_name=REDIRECT_FIELD_NAME, login_url=None])

 @login_required
 def my_view(request):
     ...

Je-li uživatel je přihlášen, pak se view normálně provede.

Není-li uživatel přihlášen, pak dojde k přesměrování na settings.LOGIN_URL (výchozí je 'accounts/login'. Do proměnné "next" dotazu je vložena cesta aktuálního view/url pro možnost vrátit se do view, které uživatele na stránku s přihlášením odeslalo ('/accounts/login?next=/současné/url').

Pokud se nám nelíbí jméno "next", můžeme to změnit prvním parametrem redirect_field_name. Druhý parametr "login_url" je snad jasný.

Další dekorátory:

  • user_passes_test(func[, login_url=None]) - zavolá funkci, předá jí User jako parametr a očekává True/False.
  • permission_required([login_url=None, raise_exception=False]). Příklad: permission_required('polls.may_vote'). Je-li raise_exception=True, pak místo přesměrování na přihlašovací stránku se zavolá se view pro chybu 403.

Není-li vůbec přihlášený, uplatní se nepovinný parametr login_url s chováním jako u login_required.

Máme-li class based view (class, ne def), pak dekorátor umístíme do definice třídy před metodu dispatch (viz orig. dokumentace class based views. Příklad:

 from django.contrib.auth.decorators import login_required
 from django.utils.decorators import method_decorator
 from django.views.generic import TemplateView

 class ProtectedView(TemplateView):
     template_name = 'secret.html'

     @method_decorator(login_required)
     def dispatch(self, *args, **kwargs):
         return super(ProtectedView, self).dispatch(*args, **kwargs)

2.9  Views pro autentifikaci

Jedná se o vestavěné pomůcky. Nestačí-li, můžeme se dostat o level níže s vestavěnými autentifikačními forms. Všechna vestavěná views vracejí instanci TemplateResponse, což usnadňuje úpravu dat před jejich zobrazením.

  • login(request[, template_name, redirect_field_name, authentication_form, current_app, extra_context])
  • logout(request[, next_page, template_name, redirect_field_name, current_app, extra_context])
  • logout_then_login(request[, login_url, current_app, extra_context])
  • password_change(request[, template_name, post_change_redirect, password_change_form, current_app, extra_context])
  • password_change_done(request[, template_name, current_app, extra_context])
  • password_reset(request[, is_admin_site, template_name, email_template_name, password_reset_form, token_generator, post_reset_redirect, from_email, current_app, extra_context])
  • password_reset_done(request[, template_name, current_app, extra_context])
  • password_reset_confirm(request[, uidb36, token, template_name, token_generator, set_password_form, post_reset_redirect, current_app, extra_context])
  • password_reset_complete(request[, template_name, current_app, extra_context])

login()

login(request[, template_name, redirect_field_name, authentication_form, current_app, extra_context])

jméno URL: login

  • template_name - default "registration/login.html".
  • redirect_field_name - default "next".
  • authentication_form - typicky jméno třídy formuláře, default "AuthenticationForm".
  • current_app - ?
  • extra_context - slovník s dodatečnými daty

Tyto proměnné předáváme v url dispatcheru. Např:

 (r'^accounts/login/$', 
     'django.contrib.auth.views.login', 
     {'template_name': 'myapp/login.html'}),

Je-li zavolána metodou:

  • GET (tedy z dekorátoru), pak zobrazí přihlašovací formulář, který má action nasměrován opět na sebe sama (login).
  • POST (tedy z formuláře s přihlašovacími daty), pokusí se uživatele přihlásit. Zadaří-li se, pak přesměruje na url v proměnné "next" (default "accounts/profile", nebo url funkce, kde byl dekorátor). Při neúspěchu o přihlášení je znovu zobrazen přihlašovací formulář.

Šablonu login.html si musíme napsat sami. Šablona obdrží tyto kontextové proměnné:

  • form - Form objekt představující AutentificationForm
  • next - url kam se přesměrovat po úspěšném přihlášení
  • site -
  • site_name -

Příklad šablony:

 {% extends "base.html" %}

 {% block content %}

 {% if form.errors %}
 <p>Your username and password didn't match. Please try again.</p>
 {% endif %}

 <form method="post" action="{% url 'django.contrib.auth.views.login' %}">
 {% csrf_token %}
 <table>
 <tr>
     <td>{{ form.username.label_tag }}</td>
     <td>{{ form.username }}</td>
 </tr>
 <tr>
     <td>{{ form.password.label_tag }}</td>
     <td>{{ form.password }}</td>
 </tr>
 </table>

 <input type="submit" value="login" />
 <input type="hidden" name="next" value="{{ next }}" />
 </form>

 {% endblock %}

Chceme-li si to uzpůsobit, koukneme do orig. dokumentace :-)

logout()

3.  Můj vlastní příklad a postup

 urls.py
     import django.contrib.auth.views
     url(r'^accounts/login/$', django.contrib.auth.views.login, name='login'),
     url(r'^accounts/logout/$', django.contrib.auth.views.logout, name='logout'),

 settings.py
     # Máme-li aplikaci v nějakém poadresáři web serveru,
     #   například http//www.aplik.com/podadreář/
     #   vzniklý jako WSGIScriptAlias /podadresář /home/...:
     LOGIN_URL = '/podadresář/accounts/login'
     LOGOUT_URL = '/podadresář/accounts/logout'
     PROFILE_URL = '/podadresář/accounts/profile'

 base.html
     # podmíněné přidání odkazu pro odhlášení
     {% if user.is_authenticated %}
     <a href="{% url 'logout' %}">odhlásit se</a>
     {% endif %}

 my_app/templates/registration/logged_out.html
     {% extends 'my_app/base.html' %}
     {% block title %} odhlášení {% endblock %}
     {% block obsah %}
     <p>Odhlášení proběhlo úspěšně. 
         <a href="{% url 'index' %}">Zpět na přihlášení.</a>
     </p>
     {% endblock %}

 my_app/templates/registration/login.html
     {% extends 'my_app/base.html' %}
     {% block title %} přihlášení {% endblock %}
     {% block obsah %}

     <form method="post" action="{% url 'login' %}">
     {% csrf_token %}
     <table>
     # v oficiální dokumentaci je následující tabulka rozepsaná, ale tohle je jednodušší.;)
     # navíc nám to zobrazí í případné chybové hlášky (v dokumentaci si je tam zobrazují ručně).    
     {{ form.as_table }}
         <tr>
             <td> </td>
             <td>
                 <input type="submit" value="přihlásit" />
             </td>
         </tr>
     </table>

     <input type="hidden" name="next" value="{{ next }}" />
     </form>
     {% endblock %}

 my_app/views.py
     from django.contrib.auth.decorators import login_required

     # a pro každé view:
     @login_required
     def funkce(request):
     ...

 *** konec příkladu ***