Inici de sessió d'usuari


Capítol 12: Sessions, usuaris i registre


És hora de fer una confessió: hem estat ignorant expressament una aspecte increïblement important en el desenvolupament web. Fins ara només ens hem preocupat d'un transit que visitava els nostres llocs sense personalitat, massa anònima, només preocupant-nos de les pàgines dissenyades.

Això no és cert, per descomptat; els navegadors que fan clic en els nostres llocs són persones reals (algunes vegades, com a mínim). Això és una gran cosa que s'ha d'ignorar: Internet és molt millor quan serveix per connectar a la gent, no a màquines. Si hem de desenvolupar llocs de veritat, en algun moment haurem de tenir en compte els cossos que hi ha darrera dels navegadors.

Desafortunadament, no és tant fàcil. L'HTTP està dissenyat per no tenir estat; és a dir, cada petició succeeix en la buidor. No hi ha persistència entre una petició i la pròxima, i no podem comptar en cap aspecte de la petició (adreça IP, agent, etc.) per consistentment indicar que successives peticions són de la mateixa persona.

Els desenvolupadors de navegadors van reconèixer fa temps que la falta de l'estat en l'HTTP plantejava un greu problema per als desenvolupadors web, i així van néixer les cookies. Una galeta és una petita peça d'informació que els navegadors guarden per ajudar als servidors web; cada cop que un navegador fa una petició d'una pàgina des d'un cert servidor, aquest envia la galeta que ell ha rebut inicialment.

Cookies

Fes una ullada per mirar com funciona. Quan obres el teu navegador i escrius google.com, el teu navegador envia una petició HTTP a Google que comença amb alguna cosa així:

GET / HTTP/1.1
Host: google.com
...

Quan Google respon, la resposta HTTP serà quelcom així:

HTTP/1.1 200 OK
Content-Type: text/html
Set-Cookie: PREF=ID=5b14f22bdaf1e81c:TM=1167000671:LM=1167000671;
            expires=Sun, 17-Jan-2038 19:14:07 GMT;
            path=/; domain=.google.com
Server: GWS/2.1
...

Fixa't amb la capçalera Set-Cookie. El teu navegador guardarà aquest valor de galeta (PREF=ID=5b14f22bdaf1e81c:TM=1167000671:LM=1167000671) i retornar-lo a Google cada cop que accedeixis al lloc. Així el pròxim cop que accedeixis a Google, el teu navegador enviarà una petició com aquesta:

GET / HTTP/1.1
Host: google.com
Cookie: PREF=ID=5b14f22bdaf1e81c:TM=1167000671:LM=1167000671
...

Google llavors pot utilitzar aquest valor de Cookie per conèixer que ets la mateixa persona que ha accedit al lloc anteriorment. Aquest valor pot, per exemple, ser una clau dins una base de dades que guarda informació de l'usuari; Google podria (i ho fa) usar això per mostrar el teu nom en la pàgina.

Obtenir i establir galetes

Quan treballem amb persistència a Django, la majoria de les vegades voldràs usar l'alt nivell de la sessió d'usuari, del qual en parlarem després. Tanmateix, ens aturarem per mirar com llegir i escriure galetes a Django. Això t'ajudarà a entendre com funcionen la resta de peces que tractarem en aquest capítol, i t'anirà bé si mai necessites treballar directament amb galetes.

Llegir galetes que ja existeixen és increïblement senzill: cada objecte de petició té l'objecte COOKIES que actua com un diccionari; pots usar-lo per llegir qualsevol galeta que el navegador enviï a la vista:

def show_color(request):
    if "favorite_color" in request.COOKIES:
        return HttpResponse("Your favorite color is %s" % \
            request.COOKIES["favorite_color"])
    else:
        return HttpResponse("You don't have a favorite color.")

Escriure galetes és lleugerament més complexe; necessites usar el mètode set_cookie() de l'objecte HttpResponse. Aquí hi ha un exemple que activa la galeta favorite_color basant-nos en el paràmetre GET:

def set_color(request):
    if "favorite_color" in request.GET:

        # Create an HttpResponse object...
        response = HttpResponse("Your favorite color is now %s" % \
            request.GET["favorite_color"])

        # ... and set a cookie on the response
        response.set_cookie("favorite_color",
                            request.GET["favorite_color"])

    else:
        return HttpResponse("You didn't give a favorite color.")

També pots passar un número d'arguments opcionals a request.set_cookie() que controla els aspectes de la galeta:

Paràmetre Defecte Descrició
max_age None Edat (en segons) que la galeta es mantindrà. Si val None, la galeta només es mantindrà fins que es tanqui el navegador.
expires None La data i hora actual quan ha d'expirar la galeta. Ha d'estar en el format "Wdy, DD-Mth-YY HH:MM:SS GMT". Si es dona, sobre-escriu el paràmetre max_age.
path "/" El prefix del camí per la qual la galeta és vàlida. Els navegadors només retornaran la galeta per les pàgines que hi hagi per sota d'aquest prefix, així pots utilitzar-lo per evitar que les galetes s'enviïn a altres seccions del teu lloc web.
Això és especialment útil quan no controles el primer nivell del domini del teu lloc.
domain None El domini on aquesta galeta és vàlida. Pots usar-lo per a crear una galeta entre dominis. Per exemple, domini=".exemple.com" activarà una galeta que es podrà llegir pels dominis www.exemple.com, www2.exemple.com i un.altre.sub.domini.exemple.com.
Si val None, una galeta només es podrà llegir pel domini que l'ha activat.
secure False Si s'activa a True, això indicarà al navegador que només retornarà aquesta galeta en pàgines accessibles sota HTTPS.

La beneïda barreja de galetes

T'hauràs fixat en els potencials problemes que poden haver-hi al treballar amb galetes. Mira algunes de les mésimportants:

  • Les galetes són essencialment voluntàries; els navegadors no garanteixen que es guardin les galetes. De fet, cada navegador del planeta té la seva política de control a l'hora d'acceptar les galetes. Si vols veure com de vitals són les galetes en les web, intenta d'establir l'opció en el teu navegador "missatge per acceptar cada galeta". Fins i tot el BIG BLUE s'ompliria de totes aquestes galetes!

    Això significa que les galetes són la definició de la falta de fiabilitat; els desenvolupadors hauran de comprovar que un usuari actualment accepta galetes abans d'enviar-les-hi.

    Encara més important, mai has de guardar dades importants en les galetes. La web està plena d'històries d'horror de desenvolupadors que han guardat dades irrecuperables en les galetes del navegador només perquè van eliminar les galetes del navegador.
  • Els galetes no són de cap manera segures. Perquè les dades HTTPS s'envien en text clar, les galetes són extremàdament vulnerables a atacs espies. És a dir, un espia de la xarxa podria interceptar una galeta i llegir-la. Això significa que mai guardis informació sensible en una galeta.

    Hi ha fins i tot, atacs més incisius com l'atac de "l'home en el mig", on l'espia intercepta la galeta i l'usa per fer-se passar per l'altre usuari. En el capítol 20 parlarem dels atacs d'aquesta naturalesa en profunditat, a part de saber com evitar-los.
  • Les galetes no estan segures en els seus recipients. La majoria de navegadors proporcionen formes fàcils de modificar-ne el contingut de galetes individuals, i usuaris experimentats poden utilitzar eines per crear peticions HTTP a mà.

    Així, no guardis dades en les galetes que siguin sensibles de ser forçades. L'error típic en aquest escenari seria guardar quelcom com IsLoggedIn=1 en una galeta quan un usuari s'identifiqui. Et sorprendries del munt de llocs que tenen coses d'aquestes; només cal un segon per trencar els sistemes de seguretat d'aquests llocs.

Framework de sessió de Django

Amb totes aquestes limitacions i els potencials forats de seguretat, és obvi que les galetes i les sessions persistens són un dels punt forts del desenvolupament web. Per descomptat, l'objectiu de Django és ser un mata-preocupacions efectiu, així Django ve amb una framework de sessió dissenyada per ocupar-se d'aquestes dificultats per a tu.

Aquesta framework de sessió et permet guardar i retornar dades arbitràries en base als visitants dels llocs. Aquesta guarda les dades en el costat del servidor i abstrau l'enviament i la recepció de galetes. Les galetes només usa un ID de sessió -- no les pròpies dades -- així et protegim de la majoria dels problemes de les galetes.

Activant les sessions

Els sessions estan implementades via una peça d'intermediari (mireu el Capítol 16) i un model de Django. Per activar les sessions, necessitaràs fer:

  • Editar el teu paràmetre de configuració MIDDLEWARE_CLASSES i assegura't que conté el 'django.contrib.sessions.middleware.SessionMiddleware'.
  • Assegura't que 'django.contrib.sessions' hi és en el paràmtre INSTALLED_APPS (i executa manage.py syncdb si no l'havies afegit).

L'esquelet de configuració per defecte creat pel startproject té ambdós d'aquests elements instal·lts, així, excepte que els hagis tret, probablement no hauràs de canviar res per tenir sessions.

Si no vols usar sessions, hauràs d'eliminar la línia SessionMiddleware del paràmetre MIDDLEWARE_CLASSES i 'django.contrib.sessions' del paràmetre INSTALLED_APPS. Només t'estalviaràs una mica de processat, però tot compta.

Utilitzant sessions en vistes

Quan SessionMiddleware s'activa, cada objecte HttpRequest -- el primer argument de totes les funcions vista -- tindran l'atribut session, que és un objecte de tipus diccionari. Podràs llegir-lo i escriure-hi de la mateixa manera que ho faries en un diccionari. Per exemple, en una vista podries fer això:

# Set a session value:
request.session["fav_color"] = "blue"

# Get a session value -- this could be called in a different view,
# or many requests later (or both):
fav_color = request.session["fav_color"]

# Clear an item from the session:
del request.session["fav_color"]

# Check if the session has a given key:
if "fav_color" in request.session:
    ...

També pots usar altres mètodes d'assignació com keys() i items() sobre request.session.

Hi ha unes regles per usar les sessions de Django amb efectivitat:

  • Utilitza cadenes de text normals de Python com a claus de diccionari sobre request.session (a diferència dels enters, objectes, etc.). Això és més una convenció que una regla, però és bo seguir-la.
  • Les claus dels diccionari de sessió que comencen amb un guió baix són reservades per l'ús intern per Django. En la pràctica la framework només utilitza un petit nombre de variables de sessió que comencen amb guions baixos, però excepte que coneguis quins són, no les utiltizis per evtiar interferir amb Django.
  • No sobre-escriguis request.session amb un nou objecte, i no accedeixis o activis aquests atributs. Utilitza un diccionari Python.

Fes una ullada a aquests petits exemple. Aquesta vista simplista possar una variable has_commented a True després que un usuari posi un comentari. Aquest no permet a un usuari posar més d'un comentari:

def post_comment(request, new_comment):
    if request.session.get('has_commented', False):
        return HttpResponse("You've already commented.")
    c = comments.Comment(comment=new_comment)
    c.save()
    request.session['has_commented'] = True
    return HttpResponse('Thanks for your comment!')

Aquesta vista simplista registra un "membre" del lloc:

def login(request):
    m = members.get_object(username__exact=request.POST['username'])
    if m.password == request.POST['password']:
        request.session['member_id'] = m.id
        return HttpResponse("You're logged in.")
    else:
        return HttpResponse("Your username and password didn't match.")

I aquest registre del membre, va d'acord amb el login() anterior:

def logout(request):
    try:
        del request.session['member_id']
    except KeyError:
        pass
    return HttpResponse("You're logged out.")
Nota
En la pràctica, aquesta és una forma punyetera de registrar als usuaris. La framework d'autenticació tractada després utilitza una forma més robusta; aquests exemples són només exemples per entendre-ho.

Comprovar si s'activen les galetes

Com s'ha mencionat anteriorment, no pots obligar que tots els navegadors acceptin galetes. Així, com quelcom convenient, Django proporciona una forma senzilla per comprovar si el navegador de l'usuari accepta galetes. Només et cal cridar a request.session.set_test_cookie() en una vista, i comprovar amb request.session.text_cookie_worked() en la següent vista -- no en la mateixa crida.

Aquesta divisió emprenyadora entre set_text_cookie() i text_cookie_worked() és necessària d'acord amb la forma de funcionar de les galetes. Quan actives una galeta, no pots saber si el navegador l'ha acceptat fins que el navegador torni a fer una petició.

És una bona pràctica utilitzar delete_test_cookie() per netejar-la després. Fes-ho després que verifiquis que el test de la galeta ha funcionat.

Aquí hi ha un exemple típic:

def login(request):

    # If we submitted the form...
    if request.method == 'POST':

        # Check that the test cookie worked (we set it below):
        if request.session.test_cookie_worked():

            # The test cookie worked, so delete it.
            request.session.delete_test_cookie()

            # In practice, we'd need some logic to check username/password
            # here, but since this is an example...
            return HttpResponse("You're logged in.")

        # The test cookie failed, so display an error message. If this
        # was a real site we'd want to display a more friendly message.
        else:
            return HttpResponse("Please enable cookies and try again.")

    # If we didn't post, send the test cookie along with the login form.
    request.session.set_test_cookie()
    return render_to_response('foo/login_form.html')
Nota
Una altra vegada, les funcions internes de login i logout realitzaran aquesta comprovació per tu.

Utilitzant les sessions fora de les vistes

Internament, cada sessió està definit en el model Django django.contrib.sessions.models. Perquè és un model normal, pots accedir sessions utilitzant la API de la base de dades Django:

>>> from django.contrib.sessions.models import Session
>>> s = Session.objects.get_object(pk='2b1189a188b44ad18c35e113ac6ceead')
>>> s.expire_date
datetime.datetime(2005, 8, 20, 13, 35, 12)

Necessitaràs cridar a get_decoded() per obtenir les dades de la sessió actual. Això és necessari perquè el diccionari es guarda codificat:

>>> s.session_data
'KGRwMQpTJ19hdXRoX3VzZXJfaWQnCnAyCkkxCnMuMTExY2ZjODI2Yj...'
>>> s.get_decoded()
{'user_id': 42}

Quan les sessions s'han guardat

Per defecte, Django només guarda a la base de dades de la sessió quan la sessió s'ha modificat -- això és si algun valor del diccionari s'ha assignat o s'ha esborrat:

# Session is modified.
request.session['foo'] = 'bar'

# Session is modified.
del request.session['foo']

# Session is modified.
request.session['foo'] = {}

# Gotcha: Session is NOT modified, because this alters
# request.session['foo'] instead of request.session.
request.session['foo']['bar'] = 'baz'

Per canviar aquesta característica per defecte, activa el paràmetre de configuració SESSION_SAVE_EVERY_REQUEST a True. Si SESSION_SAVE_EVERY_REQUEST val True, django guardarà la sessió a la base de dades en cada petició, encara que no hagi canviat.

Fixa't que la galeta de la sessió només s'envia quan una sessió s'ha creat o ha canviat. Si SESSION_SAVE_EVERY_REQUEST val True, la galeta de sessió s'enviarà en cada petició.

De forma semblant, la part expires d'una galeta de sessió és modifica cada cop que la galeta de sessió s'envia.

Sessions persistents vers sessions de navegador

Potser t'hauràs adonat que les galetes que envia Google contenen un expires=Sun, 17-Jan-2038 19:14:07 GMT;. Les galetes poden contenir una data de caducitat que avisen al navegador quan han d'eliminar la galeta. Si una galeta no conté un valor de caducitat, el navegador l'elimina quan es tanca la finestra del navegador. Pots controlar aquesta característica de sessió activant SESSION_EXPIRE_AT_BROWSER_CLOSE.

Per defecte, SESSION_EXPIRE_AT_BROWSER_CLOSE val False, que significa que les galetes de sessió es guarden en els navegadors SESSION_COOKIE_AGE segons (que per defecte són 2 setmanes -- 1209600 segons). Utilitza aquest paràmetre si vols que la gent estigui sempre identificada al l'obrir el navegador.

Si SESSION_EXPIRE_AT_BROWSER_CLOSE val True, Django utilitza les galetes de sessió del navegador.

Altres paràmetres de sessió

Més enllà dels paràmetres vistos anteriorment, n'hi ha un quants que influencien en com la framework de sessió de Django utilitza les galetes:

Paràmetre Explicació Defecte
SESSION_COOKIE_DOMAIN El domini a usar per les galetes de sessió. Possa'l a alguna cosa com ".lawrence.com" per a galetes entre dominis, o utilitza None per a galetes estàndard. None
SESSION_COOKIE_NAME El nom de la galeta a usar en les sessions. Pot ser qualsevol cadena de text. "sessionid"
SESSION_COOKIE_SECURE Si utilitzes una galeta "segura" per la galeta de sessió. Si aquest val True, la galeta serà marcada com a "segura", de manera que els navegadors només l'enviaran en connexions HTTPS. False

Detalls Tècnics
Per als curiosos, aquí hi ha unes quantes notes tècniques sobre el treball intern de la framework de sessió:

  • El diccionari de sessió accepta qualsevol objecte Python que pugui passar-se a la classe pickle. Mireu la documentació del mòdul pickle de Python per saber-ne més de com funciona.
  • Les dades de les sessions es guarden en una taula de base de dades anomenades django_session.
  • Les dades de les sessions són realment "mandroses": Si mai accedeixes a request.session, Django no mirarà a la taula de la base de dades.
  • Django només envia una galeta si cal. Si no s'activa cap sessió, no s'enviarà cap galeta (excepte que SESSION_SAVE_EVERY_REQUEST valgui True)
  • La framework de sessions es completament, i només, basat en galetes. I no cau en volar les IDs de les sessions en les URL com a últim recurs, com fan altres eines (PHP, JSP).

    Aquest és una decisió intencionada de disseny. Posar les sessions en les URL no només genera URL lletges, sinó que fa que el lloc sigui vulnerables per certes formes de substracció de la personalitat.

Si en tens curiositat, la font directa és mirar dins del codi django.contrib.sessions.

Usuaris i Identificació

Ara mirarem de relacionar els navegadors directament amb gent real. Les sessions són una forma de guardar dades entre múltiples peticions d'un navegador; la segona part de l'equació és utilitzar aquestes sessions per a identificar l'usuari. Per descomptat, no podem assegurar que els usuaris són els que diuen que són, així que necessitarem identificar-los d'alguna forma.

Naturalment, Django proporciona eines per realitzar aquesta tasca comuna (i moltes altres). El sistema d'identificació de Django tracta els comptes, els grups i permisos d'usuari de les sessions basats en galetes. Aquest sistema sovint es refereix com un sistema "auth/auth" -- autenticació i autorització. Aquest nom reconeix que tractar amb usuaris és sovint un procés en dues passes; necessitem:

  • verificar (autenticar) que un usuari és qui ella diu ser (normalment comprovant un nom d'usuari i una contrasenya en una base de dades d'usuaris), i llavors
  • verificar que l'usuari està autoritzat a executar alguna operació (normalment comprovant en una altra taula de permisos).

Seguint aquestes necessitats, el sistema auth/auth de Django consisteix en un serie de parts:

  • Usuaris
  • Permissos: banderes binaries (si/no) indicant si un usuari pot executar certes tasques.
  • Grups: una forma genèrica d'aplicar etiquetes i permisos a més d'un usuari.
  • Missatges: una forma simple que el sistema encui i mostri missatges a l'usuari.
  • Perfils: un mecanisme per estendre un objecte usuari amb camps específics.

Si has utilitzat l'eina d'administració (Capítol 6), ja hauràs vista moltes d'aquestes eines, i i si has editar usuaris o grups en l'administrador hauràs editat dades de les taules de la base de dades del sistema d'identificació.

Instal·lació

Com les eines de sessió, el suport d'identificació es tractant com una aplicació Django a django.contrib que cal isntal·lar. Com el sistema de sessió també ve instal·lat per defecte, però si l'has elimipnat necessites seguir aquests passos per instal·lar-lo:

  • Assegurar-se que la framework de sessió està instal·lada (mireu-ho anteriorment). Mantenir la traça dels usuaris òbviament necessita les galetes i aquestes són en la framework de sessió.
  • Posa 'django.contrib.auth' en el paràmetre de configuració INSTALLED_APPS i executar manage.py syncdb.
  • Assegura't que 'django.contrib.auth.middleware.AuthenticatioinMiddleware' és en el paràmetre MIDDLEWARE_CLASSES -- després de SessionMiddleware.

Amb la instal·lació feta, estem a punt de treballar amb els usuaris i les funcions vista. La interfície principal que usaràs per accedir als usuaris dins una vista és request.user; aquest és un objecte que representa l'usuari actualment identificat. Si l'usuari no està identificat, aquest serà un objecte AnonymousUser (explicat amb més detalls després).

Pots fàcilment comprovar si un usuari està identificat amb el mètode is_authenticated():

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

Utilitzant usuaris

Un cop tens un usuari -- sovint des de request.user, però també amb un dels altres mètodes que us mostrem més avall -- tens un nombre de mètodes disponibles en aquests objecte. Els objectes AnonymousUser emulen algunes d'aquests camps i mètodes, però no tots ells. Així pots comprovar si l'usuari està identificat amb user.is_authenticated() abans d'intentar accedir a dades d'un usuari.

Camps dels objectes User

Camp Descripció
username Obligatori. 30 caràcters o menys. Només caràcters alfanumèrics (lletres, digits i guions baixos).
first_name Opcional. 30 caràcters o menys.
last_name Opcional. 30 caràcters o menys.
email Opcional. Adreça de correu electrònic.
password Obligatori. Una hash de la contrasenya amb metadates sobre el tipus de hash (Django no guarda contrasenyes directament). Mira las secció de "Passwords" per saber-ne més sobre aquest valor.
is_staff Booleà. Indica si aquest usuari pot accedir al lloc d'administració.
is_active Booleà. Indica si aquest compte pot usar-se per identificar-se. Posar aquesta bandera a False en comptes d'eliminar comptes.
is_superuser Booleà. Indica que aquest usuari té totes els permisos sense haver-los-hi d'assignar explícitament.
last_login Una data i hora de l'últim cop que l'usuari s'ha identificat. Per defecte es posa la data i hora actual.
date_joined Una data i hora indicant quan s'ha creat el compte. Per defecte es posarà a l'hora actual quan el compte es crea.

Mètodes dels objectes User

Mètode Descripció
is_authenticated() Sempre retorna True per a objecte User reals. Aquesta és la forma de comprovar si un usuari ha esta identificat. Aquest mètode no implica cap permís, i no comprava que l'usuari sigui actiu -- només indica que l'usuari s'ha identificat.
is_anonymous() Retorna True només per a objectes AnonymousUser (i False per a objectes User reals). Generalment, preferiràs usar is_authenticated() en comptes d'aquest mètode.
get_full_name() Retorna el first_name més el last_name, amb un espai entremig.
set_password(passwd) Activa la constrasenya d'usuari a partir d'una cadena de text, vigilant de crear-ne un hash. Aquest encara no guarda l'objecte User
check_password(passwd) Retorna True si una cadena es la contrasenya correcta de l'usuari. Primer fa un hash de la contrasenya per després fer la comparació.
get_group_permissions() Retorna un llistat dels permisos que l'usuari té sobre els grups als que pertany.
get_all_permissions() Retorna una llista de tots els permisos que té l'usuari, tant els dels grups com els personals.
has_perm(perm) Retorna True si l'usuari te un permís en especial, on el permis està en format package.codename. Si l'usuari no és actiu, aquest mètode sempre retorna False
has_perms(perm_list) Retorna True si l'usuari té tots els permisos especificats. si l'usuari no és actiu, aquest mètode sempre retorna False.
has_module_perms(appname) Retorna True si l'usuari té qualsevol permís en l'aplicació appname. Si l'usuari no és actiu, aquest mètode sempre retornarà False.
get_and_delete_messages() Retorna una llista d'objectes Message en la cua d'usuari i esborra els missatges de la cua.
email_user(subj,msg) Senvia un correu-e a l'usuari. Aquest correu-e s'envia des de l'adreça indicada en el paràmetre DEFAULT_FROM_EMAIL. També pots passar-li un 3r argument, ffrom_email, per canviar aquesta addreça de correu-e.
get_profile() Retorna un perfil específic del lloc per a aquest usuari; mira la secció de perfils, per saber-ne més.

Finalment, els objecte User tenen dos camps molts-a-molts: groups i permissions. Els objectes User accedeixen a aquestes objectes de la mateixa manera que qualsevol altre camp molts-a-molts:

# Set a users groups:
myuser.groups = group_list

# Add a user to some groups:
myuser.groups.add(group1, group2,...)

# Remove a user from some groups:
myuser.groups.remove(group1, group2,...)

# Remove a user from all groups:
myuser.groups.clear()

# Permissions work the same way
myuser.permissions = permission_list
myuser.permissions.add(permission1, permission2, ...)
myuser.permissions.remove(permission1, permission2, ...)
myuser.permissions.clear()

Identificar-se i sortir

Django proporciona funcions vista internes per realitzar l'identificació i la sortida (i alguns altres trucs), però abans mirem com s'identifiquen els usuaris i surten. Django proporciona dues funcions per executar aquestes accions a django.contrib.auth: authenticate() i login().

Per identificar un usuari i contrasenya, utilitza authenticate(). Aquest agafa dos argument, username i password, i retornar un objecte User si la contrasenya és vàlida per a l'usuari indicat. si la contrasenya és invàlida, authenticate() retorna None:

>>> from django.contrib import auth authenticate
>>> user = auth.authenticate(username='john', password='secret')
>>> if user is not None:
...     print "Correct!"
... else:
...     print "Oops, that's wrong!"
Oops, that's wrong!

Per identificar un usuari, en una vista, utilitza login(). Aquesta agafa un objecte HttpRequest i un objecte User i guarda l'ID de l'usuari en la sessió, utilitzant la framework de sessió de Django.

Aquest exemple mostra com poden utilitzar-se aquestes funcions en una funció vista:

from django.contrib import auth

def login(request):
    username = request.POST['username']
    password = request.POST['password']
    user = auth.authenticate(username=username, password=password)
    if user is not None and user.is_active:
        # Correct password, and the user is marked "active"
        auth.login(request, user)
        # Redirect to a success page.
        return HttpResponseRedirect("/account/loggedin/")
    else:
        # Show an error page
        return HttpResponseRedirect("/account/invalid/")

Per sortir, un usuari que està identificat, utilitza la funció django.contrib.auth.logout() dins la teva vista. Aquest agafa un objecte HttpRequest i no retorna cap valor:

from django.contrib import auth

def logout(request):
    auth.logout(request)
    # Redirect to a success page.
    return HttpResponseRedirect("/account/loggedout/")

Fixa't que logout() no llença cap error si l'usuari no estava identificat.

Identificar-se i sortir, de forma fàcil

En la pràctica, normalment no et caldrà escriure les teves pròpies funcions; el sistema d'identificació ve amb un conjunt de vistes per realitzar l'identificació i la sortida.

La primera passa és usar les vistes d'autenticació enllaçada en les teves URLconf. Necessitaràs afegir aquest tros de codi:

from django.contrib.auth.views import login, logout

urlpatterns = patterns('',
    # existing patterns here...
    (r'^accounts/login/$',  login)
    (r'^accounts/logout/$', logout)
)

/accounts/login/ i /accounts/logout/ són les URL per defecte que Django utilitza per aquestes vistes, però pots posar-ne qualsevol que a tu t'agradi.

Per defecte, les vistes login imprimeixen una plantilla de registration/login.html (pots canviar aquest nom de plantilla passat un argument de vista extra template_name). Aquest formulari necessita contenir els camps username i un camp password. Una plantilla simple seria quelcom així:

{% extends "base.html" %}

{% block content %}

  {% if form.errors %}
    <p class="error">Sorry, that's not a valid username or password</p>
  {% endif %}

  <form action='.' method='post'>
    <label for="username">User name:</label>
    <input type="text" name="username" value="" id="username">
    <label for="password">Password:</label>
    <input type="password" name="password" value="" id="password">

    <input type="submit" value="login" />
    <input type="hidden" name="next" value="{{ next }}" />
  <form action='.' method='post'>

{% endblock %}

Si l'usuari s'identifica correctament, se'l re-dirigirà per defecte /accounts/profile/. Pots sobre-escriure aquesta re-direcció proporcionant un camp ocult anomenat next amb la URL per re-direccionar a un altre lloc. També pots passar aquest valor com un paràmetre GET a la vista d'identificació i aquest serà automàticament afegit al context com una variable anomenada next que podràs afegir en un camp ocult.

La vista de sortida funciona una mica diferent; per defecte aquest imprimeix una plantilla registration/logged_out.html (que normalment conté un missatge "Has sortit correctament"). Tanmateix, pots cridar la vista amb un argument extra, next_page, que indicarà a la vista per re-direccionar després de sortir.

Limitar l'accés per identificar usuaris

Per descomptat, la raó que volem aconseguir és poder limitar l'accés a parts del nostre lloc.

La forma simple de limitar l'accés a pàgina és comprovar reques.user.is_authenticated() i redirigir-lo a una pàgina d'identificació:

from django.http import HttpResponseRedirect

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

O potser mostrar un missatge d'error:

def my_view(request):
    if not request.user.is_authenticated():
        return render_to_response('myapp/login_error.html')
    # ...

Com a drecera, pots usar el decorador login_required:

from django.contrib.auth.decorators import login_required

@login_required
def my_view(request):
    # ...

login_required fa el següent:

  • Si l'usuari no està identificat, redirigeix a /accounts/login/, passant-li l'actua URL en una variable next. Per exemple: /accounts/login/?next=/polls/3/.
  • Si l'usuari s'ha identificat, executa la vista normalment. El codi de la vista pot llavors assumir que l'usuari està identificat.

Nota
Si hi entens sobre patrons de programació, fixa't que aquest decorador i algunes discutits deprés són exemples de patrons "Guard". No són fantàstics?

Limitant l'accés a usuaris que passen un comprovació

Limitar l'accés basat en certs permisos o algun altre test, o proporcionant un lloc diferent per a la vista de l'identificació funciona essencialment de la mateixa forma.

La forma directa és executar la teva comprovació sobre request.user directament en la vista. Per exemple, aquesta vista comprova que estem segurs que l'usuari està identificat i té els permisos polls.can_vote (mireu després per saber millor com funciona):

def vote(request):
    if request.user.is_authenticated() and request.user.has_perm('polls.can_vote')):
        # vote here
    else:
        return HttpResponse("You can't vote in this poll.")

Una altra vegada, Django proporciona una drecera. Aquesta s'anomena user_passes_test que és ara mateix una fabrica de decoradors: Aquest pren arguments i genera un decorador especialitzat per la teva situació en particular. Per exemple:

def user_can_vote(user):
    return user.is_authenticated() and user.has_perm("polls.can_vote")

@user_passes_text(user_can_vote, login_url="/login/")
def vote(request):
    # Code here can assume a logged in user with the correct permission.
    ...

user_passes_test agafa un argument requerit: una funció cridable que agafa un objecte User i retorna True si l'usuari té permisos per veure la pàgina. Fixa't que user_passes_test no comprova automàticament que l'usuari està identificat; podràs fer-ho tu mateix.

En aquest exemple també mostren el segon argument opcional, login_url, que et permet especificar la URL per la pàgina d'identificació (/accounts/login/ per defecte).

Ja que aquesta és una tasca relativament comuna per comprovar si un usuari té uns permisos en particular, Django proporciona una drecera per aquest cas: el decorador permission_required(). Utilitzant aquest decorador, l'exemple anterior podria escriure's així:

from django.contrib.auth.decorators import permission_required

@permission_required('polls.can_vote', login_url="/login/")
def vote(request):
    # ...

Fixa't que el permission_required() també pren un paràmetre opcional login_url que també per defecte val '/account/login/'.

Limitant l'accés a vistes genèriques

Una de les preguntes més freqüents sobre la llista d'usuaris de Django tracta de la limitació de l'accés en les vistes genèriques. Per fer això, necessites escriure un embolcall al voltant de la vista, i indicar a la URL l'embolcall en comptes de la vista genèrica:

from dango.contrib.auth.decorators import login_required
from django.views.generic.date_based import object_detail

@login_required
def limited_object_detail(*args, **kwargs):
    return object_detail(*args, **kwargs)

Pots, per descomptat, substituir el login_required amb qualsevol altre decorador.

Gestionant usuaris, permisos i grups

La forma senzilla de gestionar sistemes d'identificació és a través de l'administrador. El capítol 6 tracta de com utilitzar l'administrador de Django per editar els usuaris i controlar els seus permisos i l'accés, i la majoria de cops que només utilitzes aquesta interfícies.

Tanmateix, hi ha APIs de baix nivell que pots desenvolupar quan et cal tenir un control absolut.

Creant usuaris

La forma bàsica per crear usuaris és usar la funció d'ajuda create_user:

>>> from django.contrib.auth.models import User
>>> user = User.objects.create_user(username='john',
...                                 email='jlennon@beatles.com',
...                                 password='glass onion')

En aquest punt, user és una instància User preparada per guardar-se a la base de dades. Pots continuar per canviar els seus atributs abans de guardar-los, també:

>>> user.is_staff = True
>>> user.save()

Canviant les contrasenyes

Pots canviar una contrasenya amb set_password():

>>> user = User.objects.get(username='john')
>>> user.set_password('goo goo goo joob')
>>> user.save()

No posis l'atribut password directament excepte que sàpigues el que estàs fent; la contrasenya es guarda actualment com una hash i aquesta no pot modificar-se directament.

Més formalment, l'atribut password d'un objecte User és una cadena en aquest format:

hashtype$salt$hash

Aquest és un tipus de hash, el salt és el propi hash, separat pel signe de dollar.

hashtype és o sha1 (per defecte) o md5 -- l'algoritme utilitzat per executar el hash en una única direcció per a una contrasenya. Salt és una cadena al·leatoria utilitzada per salar la contrasenya directa per crear el hash.

Per exemple:

sha1$a1976$a36cc8cbf81742a8fb52e221aaeab48ed7f58ab4

Les funcions User.set_password() i User.check_password() tracten d'activar i comprovar aquests valors entre les escenes.

Això és com una droga?
No, una salted hash no té res a veure amb la maria-juana; és la forma actual per guardar les contrasenyes de forma segura. Una hash és un tipus de funció criptogràfica; és a dir, pots computar la hash fàcilment d'un valor donat, però és gairebé impossible agafar el hash i reconstruir el valor original.

Si guardem les contrasenyes com a text pla, qualsevol que tingui accés a la base de dades pot saber les contrasenyes de qualsevol. Guardar passwords com hashes redueix el valors de les bases de dades compromeses.

Tanmateix, un atacant amb la base de dades de contrasenyes podria executar un atac de força bruta, fent hash de milions de contrasenyes i comparant-les amb els valors guardats. Això podria trigar una mica, però menys del que creus -- els ordinadors són increïblement ràpids.

A més hi ha disponibles taules d'arc de sant martí -- bases de dades de hash pre-calculats de milions de contrasenyes. Amb una taula d'arc de sant martí, un atacant pot trencar moltes contrasenyes en segons.

Afegint sal -- bàsicament un valor inicial aleatori -- el hash guardat afegeix una altra capa de dificultat. Ja que la sal serà diferent d'una contrasenya a una altra, la sal també evita l'ús de les taules d'arc de sant mar'ti, així que els atacants de força bruta fallaran en el seu atac -- ell mateix fa més difícil per l'entropia de més afegida al hash amb la sal.

Com que les hash amb sal no són absolutament la forma més segura de guardar contrasenyes, són un terreny intermedi entre la seguretat i la conveniencia.

Creant comptes

Podem utilitzar les eines de baix nivell per crear vistes que permetin als usuaris crear-se comptes. Normalment cada desenvolupador vol implementar el registre de forma diferent, així que Django et deixa que escriguis una vista de registre; afortunadament és molt fàcil.

Amb aquesta simplicitat, podríem proporcionar una petita vista que demani la informació obligatori de l'usuari i crei aquest usuari. Django proporciona un formulari intern que pots usar per aquest propòsit, el qual usarem en aquest exemple:

from django import oldforms as forms
from django.http import HttpResponseRedirect
from django.shortcuts import render_to_response
from django.contrib.auth.forms import UserCreationForm

def register(request):
    form = UserCreationForm()

    if request.method == 'POST':
        data = request.POST.copy()
        errors = form.get_validation_errors(data)
        if not errors:
            new_user = form.save()
            return HttpResponseRedirect("/accounts/created/")
    else:
        data, errors = {}, {}

    return render_to_response("registration/register.html", {
        'form' : forms.FormWrapper(form, data, errors)
    })

Aquest assumeix una plantilla anomenada registration/register.html; aquí hi ha un exemple de com podria ser una plantilla:

{% extends "base.html" %}

{% block title %}Create an account{% endblock %}

{% block content %}
      <h1>Create an account</h1>
      <form action="." method="post">
        {% if form.error_dict %}
          <p class="error">Please correct the errors below.</p>
        {% endif %}

        {% if form.username.errors %}
          {{ form.username.html_error_list }}
        {% endif %}
        <label for="id_username">Username:</label> {{ form.username }}

        {% if form.password1.errors %}
          {{ form.password1.html_error_list }}
        {% endif %}
        <label for="id_password1">Password: {{ form.password1 }}

        {% if form.password2.errors %}
          {{ form.password2.html_error_list }}
        {% endif %}
        <label for="id_password2">Password (again): {{ form.password2 }}

        <input type="submit" value="Create the account" />
      </label>
{% endblock %}

Utilitzant dades d'identificació en les plantilles

L'usuari actualment identificat i els seus permisos estan disponibles en el context de la plantilla quan utilitzes el RequestContext (mira el capítol 10).

Nota
Tecnicament, aquestes variables només estarien disponibles en el context de la plantilla si utilitzessis el RequestContext i el teu paràmetre de configuració TEMPLATE_CONTEXT_PROCESSORS conté l'entrada "django.core.context_processors.auth", que hi és per defecte. Et repetim que et miris el Capítol 10 per una explicació més completa.

Quan utilitzes el RequestContext, l'usuari actual -- ja sigui una instància d'usuari o una instància d'usuari anònim -- es guarda en la variable de plantilla {{ user }}:

{% if user.is_authenticated %}
  <p>Welcome, {{ user.username }}. Thanks for logging in.</p>
{% else %}
  <p>Welcome, new user. Please log in.</p>
{% endif %}

Aquests permisos d'usuari es guarden en la variable de plantilla {{ perms }}. Aquesta és una passarel·la per a les plantilles per accedir als mètodes dels permisos. Mira la secció de permisos, a sota, per saber-ne més sobre els seus mètodes.

Hi ha dues formes que pots usar aquest objecte perms. Pots utilitzar-lo així {{ perms.polls }} per comprovar si l'usuari té algun permís sobre alguna aplicació, o pots usar-lo així {{ perms.polls.can_vote }} per comprovar si l'usuari té un permís específic.

Així, pots comprovar els permisos en les plantilles amb entrades {% if %}:

{% if perms.polls %}
  <p>You have permission to do something in the polls app.</p>
  {% if perms.polls.can_vote %}
    <p>You can vote!</p>
  {% endif %}
{% else %}
  <p>You don't have permission to do anything in the polls app.</p>
{% endif %}

Altres coses: permisos, grups, missatges i perfils

Hi ha alguna altra cosa en la framework d'autenticació que només hem tractat per sobre. Fem-hi una ullada:

Permisos

Els permisos són una forma simple de "marcar" usuaris i grups i permetre'ls executar alguna acció. S'usa normalment en el lloc d'administració de Django, però pots fàcilment usar-lo en el teu propi codi.

El lloc d'administració de Django utilitza els permisos així:

  • Accés a les vistes dels formularis "afegir" i afegir un objecte està limitat as usuaris amb el permís "add" per aquest tipus d'objecte.
  • Accés a les vistes de llista de modificació, les vistes de "modificació" i modificar un objecte està limitat als usuaris amb el permís "change" per a aquest tipus d'objecte.
  • L'accés a esborrar un objecte està limitat als usuaris amb el permís "delete" per un tipus d'objecte.

Els permisos s'activen globalment per tipus d'objecte, no només per una instància en particular. Per exemple, és possible dir "La Maria pot canviar les notícies", però no és possible dir "La Maria pot canviar notícies, però només les que ella ha creat" o "La Maria només pot canviar notícies amb un cert estat, data de publicació o ID".

Aquests tres permisos bàsics -- afegir, modificar, esborrar -- es creen automàticament al crear cada model de Django que té la classe Admin. Darrera l'escenari, aquest permisos són afegits a la taula de la base de dades auth_permission quan executes manage.py syncdb.

Aquests permisos tindran la següent forma "<app>.<action>_<object_name>". És a dir, si tens una aplicació polls amb un model Choice, tindràs permisos anomenats "polls.add_choice", "polls.change_choice" i "polls.delete_choice".

Fixa't que si el teu model no té activada la classe Admin quan executes el syncdb, els permisos no es crearan. Si inicialitzes la teva base de dades i afegeixes la classe Admin als models, després hauràs de tornar-ho a executar per crear els permisos de les aplicacions instal·lades.

També pots crear permisos personalitzats per a un objecte de model, utilitzant l'atribut permissions sobre Meta. Aques model d'exemple crea tres permisos personalitzats:

class USCitizen(models.Model):
    # ...
    class Meta:
        permissions = (
            # Permission identifier     human-readable permission name
            ("can_drive",               "Can drive"),
            ("can_vote",                "Can vote in elections"),
            ("can_drink",               "Can drink alcohol"),
        )

Això només crea aquests permisos extra quan executes syncdb; és cosa teva saber si t'interessa tenir aquests permisos.

Igual que els usuaris els permisos estan implementats en un model Django que és a django.contrib.auth.models; això vol dir que pots utilitzar la API de base de dades de Django per interactuar directament amb els permisos si ho prefereixes.

Grups

Els grups són formes genèriques de categoritzar als usuaris per a aplicar-los-hi permisos, o alguna altra etiqueta, per a aquestos usuaris. Un usuari pot pertànyer a qualsevol nombre de grups.

Un usuari en un grup automàticament té els permisos d'aquest grup. Per exemple, si el grup Editors del lloc té els permisos can_edit_home_page, qualsevol usuari en aquest grup tindrà aquests permisos.

Més enllà dels permisos, els grups són una forma convenient per categoritzar els usuaris i donar-los una etiqueta, o estendre la funcionalitat. Per exemple, podries crear un grup "Usuaris Especials", i podries escriure codi que, diguem-ne, donar-los-hi accés a un part del teu lloc, o enviar-los-hi missatge de correu-e.

Com els usuaris, la forma més fàcil de gestionar els grups és a través de l'administrador. No obstant, els grups també són models Django que resideixen a django.contrib.auth.models, així repetim que sempre pots utilitzar la API de la base de dades de Django per tractar amb els grups a baix nivell.

Missatges

El sistema de missatges és un forma lleugera d'encuar missatges per a uns usuaris. Un missatges s'associa a un User. Sense cap concepte d'expiració o de data.

Els missatges s'utilitzen per l'administrador de Django després de realitzar accions. Per exemple, quan crees un objecte, rebràs una notificació amb un missatge "L'objecte s'ha creat correctament" a sobre la pàgina d'administració.

Pots usar aquesta API per encuar i mostrar missatges en les teves pròpies aplicacions. La API és simple:

  • Per crear un nou missatge, utilitza user.message_set.create(message='message_text').
  • Per retornar/esborrar missatges, utilitza user_obj.get_and_delete_messages() que retornarà un llistat d'objectes Message de la cua de l'usuari (si n'hi ha) i esborrarà els missatges de la cua.

En aquest exemple de vista, el sistema guarda un missatge per a l'usuari després de crear una llista de reproducció:

def create_playlist(request, songs):
    # Create the playlist with the given songs.
    # ...
    request.user.message_set.create(
        message="Your playlist was added successfully."
    )
    return render_to_response("playlists/create.html",
        context_instance=RequestContext(request))

Quan utilitzes el RequestContext, l'usuari actualment identificat i els seus missatges estaran disponibles en el context de la plantilla com una variable de plantilla {{ messages }}. Aquí hi ha un exemple de codi de plantilla que mostra els missatges:

{% if messages %}
<ul>
    {% for message in messages %}
    <li>{{ message }}</li>
    {% endfor %}
</ul>
{% endif %}

Fixa't que RequestContext crida a get_and_delete_messages darrera de l'escenari, així que qualsevol missatge s'esborrarà si no el mostres.

Finalment, fixa't que aquesta framework de missatges només funciona amb usuaris de la base de dades d'usuari. Per enviar missatges a usuaris anònims, utilitza directament la framework de sessió.

Perfils

L'última peça del trencaclosques és el sistema de perfils. Per entendre que són els perfils, mirem primer el problema:

En una closca de nou, moltes vegades cal guardar més informació de l'usuari que la disponible en un objecte User estàndard. Per composar el problema, molts llocs tenen camps "extra" diferents. Així, Django proporciona una forma lleugera de definir un objecte "perfil" que s'enllaça amb un usuari; aquest perfil pot canviar d'un projecte a un altre, i poden haver-hi perfils diferents per llocs diferents servits per la mateixa base de dades.

El primer pas per crear un perfil és definir un model que mantingui la informació del perfil. L'únic requeriment que demana Django per aquest model és que tingui una única clau forània ForeignKey cap al model User; aquest camp ha d'anomenar-se user. Els altres camps poden ser els que tu prefereixis. Aquí hi ha un model arbitrari de perfil:

from django.db import models
from django.contrib.auth.models import User

class MySiteProfile(models.Model):
    # This is the only required field
    user = models.ForeignKey(User, unique=True)

    # The rest is completely up to you...
    favorite_band = models.CharField(maxlength=100, blank=True)
    favorite_cheese = models.CharField(maxlength=100, blank=True)
    lucky_number = models.IntegerField()

Després, necessitarà dir-li a Django on buscar aquest objecte de perfil. Ho faràs indicant-ho al paràmetre de configuració AUTH_PROFILE_MODULE per a identificar el teu model. Així, si el teu model és en l'aplicació myapp, hauràs de posar en el teu fitxer de configuració:

AUTH_PROFILE_MODULE = "myapp.mysiteprofile"

Un cop fet això, podràs accedir al perfil de l'usuari cridant a user.get_profile(). Aquesta funció llençarà una excepció SiteProfileNotAvailable si AUTH_PROFILE_MODULE no s'ha definit, i també pot llançar l'excepció DoesNotExists si l'usuari no té un perfil (normalment capturaràs aquesta excepció i crearàs un nou perfil el aquell moment).

Resumint

Si, el sistema de sessió i autorització té molt de material. La majoria de cops no necessitaràs totes aquestes característiques descrites en aquest capítol, però quan les necessitis per permetre interaccions complexes entre usuaris, és bo tenir aquesta potència disponible.

En el pròxim capítol, mirarem la part de Django que està construit sobre el sistema d'usuari/sessió: l'aplicació comentaris. Aquesta et permet afegir comentaris d'una forma molt senzilla -- des d'usuaris autoritzats a anònims -- a objectes arbitraris.

Endavant i ànim!