/tmp/tmp.2e73529882766d9fd5115435379fd70e/tmp.2e73529882766d9fd5115435379fd70e.rst:1018: (ERROR/3) Unknown directive type "annotation". .. annotation:: \xbfEs alg\xfan tipo de droga? No, un **salted hash** no tiene nada que ver con la marihuana; es una manera bastante com\xfan de almacenar claves de manera segura. Un **hash** es una funci\xf3n criptogr\xe1fica de una sola direcci\xf3n; es decir, que se puede computar de forma sencilla el *hash* de un valor dado, pero es casi imposible reconstruir el valor original partiendo del *hash*. Si almacen\xe1semos las claves como texto sencillo, cualquiera que consiguiese acceder a la base de datos de claves podr\xeda saber al instante las claves de todo el mundo. Almacenar las claves como *hashes* reduce el valor de una base de datos comprometida. Sin embargo, un atacante con la base de claves podr\xeda a\xfan intentar un ataque por **fuerza bruta**, obteniendo el *hash* de millones de claves y compar\xe1ndolas con los valores almacenados. Esto podr\xeda llevar tiempo, pero menos del que piensa (los computadores son incre\xedblemente r\xe1pidos) Lo que es peor, existen las llamadas **rainbow tables** (bases con *hashes* precalculados de millones de claves) y est\xe1n disponibles de forma p\xfablica. Con una de estas tablas un atacante puede descubrir la mayor\xeda de las claves en segundos. A\xf1adir un **salt** (b\xe1sicamente, un valor inicial aleatorio) al *hash* almacenado a\xf1ade otra capa de dificultad. Dado que el *salt* cambia de una clave a otra, los *salt* tambi\xe9n previenten ante el uso de tablas *rainbow*, forzando as\xed a los atacantes a volver al ataque por fuerza bruta (a su vez m\xe1s dif\xedcil debido a la entrop\xeda adicional creada en el *hash* por el *salt*) Aunque \xe9sta no es exactamente la manera m\xe1s segura de almacenar claves, son un punto medio bastante bueno entre la seguridad y la conveniencia. /tmp/tmp.2e73529882766d9fd5115435379fd70e/tmp.2e73529882766d9fd5115435379fd70e.rst:1088: (WARNING/2) Literal block expected; none found. Capítulo 12: Sesiones, usuarios y registro

Capítulo 12: Sesiones, usuarios y registro

Es hora de confesar algo: hemos estado ignorando deliberadamente un aspecto increíblemente importante del desarrollo web hasta este punto. Hasta aquí, hemos pensado en el tráfico que visita nuestros sitios como una masa sin rostro y anónima embistiendo contra nuestras páginas diseñadas con tanto cuidado.

Por supuesto, esto no escierto; los navegadores que llegan a nuestros sitios tienen tras ellos personas reales (a menudo, al menos). Esto es algo muy grande como para ignorarlo: lo mejor de la Internet se ve cuando sirve para conectar a gente, no a máquinas. Si vamos a desarrollar sitios realmente atractivos, en algún momento tendremos que tratar con los cuerpos tras el navegador.

Por desgracia, esto no es tan sencillo. HTTP se diseñó como protocolo sin estado (stateless); es decir, cada petición sucede en el vacío. No hay persistencia entre una petición y la siguiente y no podemos contar con que ningún aspecto de una petición (dirección IP, user-agent, etc.) indique de forma consistente peticiones sucesivas de una misma persona.

Los desarrolladores de navegadores reconocieron hace mucho que esta falta de estado de HTTP suponía un enorme problema para los desarrolladores de webs, y así nacieron las cookies. Una cookie es una pequeña porción de información que almacenan los navegadores a petición de los servidores web; cada vez que un navegador solicita una página de un servidor en concreto, le devuelve la cookie que recibió anteriormente.

Cookies

Echemos un vistazo a la manera en que funcionaría esto. Cuando abrimos el navegador y escribimos google.com, el navegador envía una petición HTTP a Google que comienza parecido a esto:

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

Cuando Google responde, la respuesta HTTP es algo parecido a esto:

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
...

Observe la cabecera Set-Cookie. El navegador guardará ese valor de cookie (PREF=ID=5b14f22bdaf1e81c:TM=1167000671:LM=1167000671) y se la servirá a Google cada vez que accedamos al sitio. Así que la siguiente vez que acuda a Google, el navegador enviará una petición parecida a ésta:

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

Google usará entonces ese valor de Cookie para saber si somos la misma persona que accedió anteriormente al sitio. Este valor podría ser por ejemplo una clave en una base de datos que guarda información de usuario; Google podría usarlo (y lo hace) para mostrar su nombre en la página.

Obtener y fijar cookies

Cuando tratemos con persistencia en Django, la mayoría de las veces querremos usar los framework de alto nivel de sesión o usuario de los que hablaremos en breve. Sin embargo, nos detendremos a examinar la manera en que Django lee y escribe las cookies. Quizá le ayude a entender cómo funcionan realmente el resto de las partes que se discuten en este capítulo y le será útil en caso de que tenga que trabajar directamente con cookies alguna vez.

Leer cookies que ya están fijadas es increíblemente sencillo: cada objeto de petición tiene un objeto COOKIES que actúa como un diccionario; lo podemos usar para leer cualquier cookie que el navegador haya enviado a la vista:

def mostrar_color(request):
    if "color_favorito" in request.COOKIES:
        return HttpResponse("Su color favorito es el %s" % \
            request.COOKIES["color_favorito"])
    else:
        return HttpResponse("No tiene un color favorito.")

Escribir cookies el ligeramente más complicado y necesitaremos usar el método set_cookie() en un objeto HttpResponse. Aquí tiene un ejemplo en el que establecemos la cookie color_favorito basándonos en un parámetro GET:

def fijar_color(request):
    if "color_favorito" in request.GET:

        # Creamos un objeto HttpResponse...
        response = HttpResponse("Ahora su color favorito es %s" % \
            request.GET["color_favorito"])

        # ... y creamos una cookie para la respuesta
        response.set_cookie("color_favorito",
                            request.GET["color_favorito"])

    else:
        return HttpResponse("No ha indicado un color favorito.")

También podemos pasar a request.set_cookie() varios argumentos opcionales que controlan aspectos de la cookie:

Parámetro Por omisión Descripción
max_age None Segundos que debería durar la cookie. Si es None, la cookie durará sólo hasta que se cierre el navegador.
expires None La fecha/hora real en que debería expirar la cookie. Debe crearse con el formato "Wdy, DD-Mth-YY HH:MM:SS GMT". Si se da, tiene precedencia frente al parámetro max_age
path "/"

El prefijo de la ruta para la que es válida esta cookie. Los navegadores sólo enviarán la cookie a als páginas que se encuentren bajo este prefijo, así que podemos usarlo para evitar que se envíen cookies a otras secciones de nuestro sitio.

Especialmente útil en caso de que no controlar niveles superiores de nuestro dominio.

domain None

El dominio para el que es válida esta cookie. Podemos usar esto para establecer cookies entre dominios diferentes. Por ejemplo, domain=".ejemplo.com" creará una cookie que pueden leer los dominios www.ejemplo.com, www2.ejemplo.com y otro.sub.dominio.ejemplo.com.

Si es None, la cookie la podrá leer sólo el dominio que la ha establecido.

secure False Si se pone a True, le indica al navegador que sólo ha de enviar la cookie a páginas a las que se accede mediante HTTPS.

La bendición maldita de las cookies

Puede que haya notado ya varios problemas potenciales en la manera en que trabajan las cookies. Examinemos los más importantes:

  • Las cookies son esencialmente voluntarias; el navegador no garantiza su almacenamiento. De hecho, cada navegador del planeta le permitirá tener control sobre su política para aceptación de cookies. Si quiere ver cuan vitales son las cookies en la web, prueba a activar la opción "solicitar permiso por cada cookie" de su navegador. ¡Incluso un enorme monstruo azul se empacharía con tanta "galleta"!

    Esto significa, por supuesto, que las cookies son la definición de la falta de fiabilidad; los desarrolladores deberían comprobar que un usuario está aceptando realmente las cookies antes de confiar en ellas.

    Aún más importante es que nunca debería almacenar datos importantes en las cookies. La web está llena de historias de horror de desarrolladores que han almacenado información irrecuperable en cookies de navegador sólo para ver cómo el navegador purga esos datos por una razón y otra.

  • Las cookies no son seguras de ninguna manera. Dado que los datos HTTP se envían como texto en claro, las cookies son extremadamente vulnerables a ataques espía. Es decir, un atacante que espíe una conexión puede interceptar una cookie y leerla. Esto significa que nunca debería almacenarse información sensible en una cookie.

    Existe un ataque incluso más insidioso llamado "man in the middle", u "hombre en el medio", donde un atacante intercepta una cookie y la usa para suplantar a otro usuario. El Capítulo 20 comenta en profundidad los ataques de esta naturaleza, así como maneras de prevenirlos.

  • Las cookies no son seguras ni siquiera viniendo de sus receptores legítimos. La mayoría de los navegadores proporcionan medios para editar el contenido de las cookies y los usuarios con recursos siempre pueden utilizar herramientas como mechanize para construir peticiones HTTP manualmente.

    Así que no podemos guardar en cookies datos susceptibles de ser falsificados. El error típico en estos casos es almacenar algo así como IsLoggedIn=1 en una cookie cuando el usuario se identifica. Le asombraría la cantidad de sitios que cometen fallos como éste. Sólo lleva unos segundos engañar a los sistemas de "seguridad" de estos sitios.

Framework de sesiones de Django

Con todas estas limitaciones y agujeros de seguridad potenciales, es obvio que las cookies y las sesiones persistentes son otros de esos "puntos dolorosos" del desarrollo web. Por supuesto, el objetivo de Django es ser un analgésico efectivo, así que Django incorpora un framework de sesiones diseñado para aliviarle estas dificultades.

Este framework le permite almacenar y recuperar datos arbitrarios por cada visitante del sitio. Almacena datos en el servidor y abstrae el envío y recepción de cookies. Las cookies usan sólo un ID de sesión (no lo sdatos en sí) protegiéndole así de la mayoría de problemas comunes de las cookies.

Habilitación de las sesiones

Las sesiones están implementadas mediante middleware (véase el Capítulo 16) y un modelo de Django. Para habilitar las sesiones tendremos que:

  1. Editar la opción MIDDLEWARE_CLASSES y asegurarnos de que contiene 'django.contrib.sessions.middleware.SessionMiddleware'.
  2. Asegurarnos de que 'django.contrib.sessions' está en nuestra opción INSTALLED_APPS (y ejecutar manage.py syncdb si hemos tenido que añadirlo).

El esqueleto que genera startproject ya tiene ambas opciones instaladas así que, a menos que las hayamos eliminado nosotros mismos, es probable que no tengamos que cambiar nada para que las sesiones funcionen.

Si no quiere usar las sesiones, quizá prefiera eliminar de MIDDLEWARE_CLASSES la línea con SessionMiddleware y 'django.contrib.sessions' de INSTALLED_APPS. Sólo le supondrá un alivio muy pequeño en cuanto a la carga del sitio, pero cada poquito cuenta.

Uso de sesiones en las vistas

Cuando se activa SessionMiddleware, cada objeto HttpRequest (el primer argumento de cualquier función de vista de Django) tendrá un atributo session, que es un objeto tipo diccionario. Puede leerlo y escribir en él de la misma manera que con un diccionario normal. Por ejemplo, en una vista podríamos hacer algo así:

# Establecer el valor de una sesión:
request.session["color_fav"] = "azul"

# Obtener un valor de una sesión (podría hacerse la llamada en otra
# vista, o muchas peticiones más tarde (o ambas):
color_fav = request.session["color_fav"]

# Eliminar un elemento de una sesión:
del request.session["color_fav"]

# Comprobar si la sesión tiene una clave dada:
if "color_fav" in request.session:
    ...

También se pueden usar sobre request.session otros métodos de acceso como keys() e items().

Hay un par de reglas sencillas para usar las sesiones de Django de forma efectiva:

  • Utilice cadenas normales de Python como claves de diccionario en request.session (en lugar de enteros, otros objetos, etc). Es más una convención que una regla estricta, pero merece la pena seguirla.
  • Las claves de diccionario de sesiones cuyo nombre empieza por un guión bajo están reservadas para uso interno de Django. En la práctica el framework sólo usa unas pocas de estas variables de sesión, pero a menos que sepa todos sus nombres (y esté dispuesto a seguir la pista a cualquier cambio en el propio Django), es mejor evitar estos prefijos para evitar que Django interfiera con nuestra aplicación.
  • No sustituya request.session con un nuevo objeto, ni acceda o cambie sus atributos. Úselo como un diccionario normal de Python.

Echemos un vistazo a unos ejemplos rápidos. Esta vista simple pone a True una variable ha_comentado después de que un usuario envíe un comentario. No le permite comentar al usuario más de una vez:

def post_comment(request, nuevo_comentario):
    if request.session.get('ha_comentado', False):
        return HttpResponse("Ya ha comentado.")
    c = comments.Comment(comment=nuevo_comentario)
    c.save()
    request.session['ha_comentado'] = True
    return HttpResponse('¡Gracias por su comentario!')

Esta otra vista simple identifica a un "miembro" de un sitio:

def login(request):
    m = miembro.get_object(usuario=request.POST['usuario'])
    if m.clave == request.POST['clave']:
        request.session['id_miembro'] = m.id
        return HttpResponse("Se ha identificado.")
    else:
        return HttpResponse("No coinciden usuario y clave.")

Y esta otra lo desconecta, de acuerdo con el login() anterior:

def logout(request):
    try:
        del request.session['id_miembro']
    except KeyError:
        pass
    return HttpResponse("Se ha desconectado.")

Nota

En la práctica, ésta es una manera algo descuidada de identificar a los usuarios. El framework de autentificación que se comenta más adelante se encarga de esto por usted de manera mucho más robusta y útil; estos ejemplos se los damos sólo porque son fáciles de entender.

Creación de cookies de prueba

Como mencionamos antes, no se puede confiar en que todos los navegadores acepten cookies. Por tanto y por conveniencia, Django proporciona una manera sencilla de probar si el navegador del usuario acepta cookies. Sólo necesita invocar a request.session.set_test_cookie() en una vidsta, y comprobar request.session.test_cookie_worked() en una vista posterior (no en la misma).

Esta división entre set_test_cookie() y test_cookie_worked() es necesaria debido a la manera en que funciona las cookies. Cuando se establece una, no se puede decir realmente si el navegador la ha aceptado hasta su siguiente petición.

Es una buena práctica usar delete_test_cookie() para limpiar tras nosotros. Hágalo una vez que haya verificado si funcionó la creación de la cookie de prueba.

He aquí un ejemplo típico de uso:

def login(request):

    # Si hemos enviado el formulario...
    if request.method == 'POST':

        # Comprobar si la cookie de prueba ah funcionado (le damos valor
        # más adelante):
        if request.session.test_cookie_worked():

            # Ha funcionado, así que la borramos.
            request.session.delete_test_cookie()

            # En la práctica, necesitaríamos algo para comprobar el usuario
            # y su clave, pero como es un ejemplo...
            return HttpResponse("Se ha identificado.")

        # La prueba de cookie ha fallado, así que mostramos un mensaje de
        # error. Si esto fuera un sitio real querríamos mostrar un mensaje
        # más amistoso.
        else:
            return HttpResponse("Por favor, active las cookies e inténtelo de nuevo.")

    # Si no hemos hecho 'post', enviamos la cookie de prueba junto con el
    # formulario de ingreso.
    request.session.set_test_cookie()
    return render_to_response('foo/formulario_login.html')

Nota

De nuevo, las funciones incorporadas para ingreso y desconexión se ocupan por usted de hacer estas comprobaciones.

Uso de sesiones fuera de las vistas

Internamente, cada sesión es sólo un modelo normal de Django definido en django.contrib.sessions.models. Debido a que es un modelo normal, se puede acceder a cada sesión usando el API de base de datos de 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)

Tendremos a invocar a get_decoded() para obtener los datos reales de la sesión. Esto es necesario porque el diccionario se almacena en un formato codificado:

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

Cuándo se guardan las sesiones

Por omisión, Django sólo guarda en la base de datos de sesiones cuando éstas son modificadas (es decir, cuando se almacena o elimina algún valor en ellas):

# Se modifica la sesión.
request.session['foo'] = 'bar'

# Se modifica la sesión.
del request.session['foo']

# Se modifica la sesión.
request.session['foo'] = {}

# Fallo: NO se ha modificado la sesión, ya que esto
# altera request.session['foo'] en lugar de request.session.
request.session['foo']['bar'] = 'baz'

Para modificar este comportamiento, ponga True en la opción SESSION_SAVE_EVERY_REQUEST. Si SESSION_SAVE_EVERY_REQUEST es True, Django guardará la sesión en la base de datos tras cada petición, incluso si no ha cambiado.

Obsérvese que la cookie de sesión sólo se envía cuando se ha creado o modificado una sesión. Si SESSION_SAVE_EVERY_REQUEST es True, la cookie de sesión se va a enviar tras cada petición.

De forma similar, el valor expires de una cookie de sesión se actualiza cada vez que se envía al navegador.

Sesiones persistentes frente a duración del navegador

Quizá haya observado antes que la cookie que envió Google contenía expires=Sun, 17-Jan-2038 19:14:07 GMT;. Las cookies pueden contener opcionalmente una fecha de expiracion que aconseja al navegador sobre el momento de eliminar la cookie. Si una cookie no contiene una fecha de expiración, el navegador la eliminará cuando el usuario cierre la ventana. Se puede controlar el comportamiento del framework de sesiones a este respecto con la opción SESSION_EXPIRE_AT_BROWSER_CLOSE.

Por omisión, SESSION_EXPIRE_AT_BROWSER_CLOSE se encuentra a False, lo que significa que las cookies de sesión se almacenarán en el navegador del usuario durante SESSION_COOKIE_AGE segundos (que por omisión son dos semanas - 1209600 segundos). Utilícelo si no quiere que la gente tenga que identificarse cada vez que abre un navegador.

Si se cambia SESSION_EXPIRE_AT_BROWSER_CLOSE a True, Django usará cookies que expiran al cerrar el navegador.

Otras opciones para sesiones

Aparte de las opciones que ya hemos mencionado, hay unas pocas más que influyen en la manera en que el framework de sesiones de Django usa las cookies:

Opción Explicación Valor por omisión
SESSION_COOKIE_DOMAIN El dominio que ha de usarse para las cookies de sesión. Ponga aquí una cadena como ".lawrence.com" si quiere cookies para más de un dominio, o None para una estándar. None
SESSION_COOKIE_NAME El nombre de la cookie a usar para las sesiones. Puede ser cualquier cadena. "sessionid"
SESSION_COOKIE_SECURE Si se usa una cookie "segura" para las cookies de sesión. Si se pone a True, la cookie será marcada como "segura," que significa que el navegador se asegurará de que sólo se envia usando HTTPS. False

Detalles técnicos

Hemos reunido algunas notas técnicas sobre el funcionamiento interno del framework de sesión, para los curiosos:

  • El diccionario de sesión acepta cualquier objeto de Python aceptado por pickle. Vea la documentación de este módulo que incorpora Python si desea más información sobre su funcionamiento.

  • Los datos de la sesión se almacenan en una tabla de la base de datos llamada django_session.

  • Los datos de sesión se leen de forma "perezosa": si nunca se accede a request.session, Django no tocará esa tabla de la base de datos.

  • Django sólo envía una cookie si necesita hacerlo. Si no establece ningún dato de sesión, no se enviará una cookie de sesión (a manos que SESSION_SAVE_EVERY_REQUEST esté a True).

  • El framework de sesiones de Django se basa por entero y en exclusiva en cookies. No recurre a colocar ID de sesión en las URL como último recuerdo, como hacen otras herramientas (PHP, JSP).

    Esta decisión de diseño es deliberada. Poner sesiones en las URL no sólo las hace horribles, sino que además hace vulnerable a su sitio a ciertas formas de robo de ID de sesión mediante la cabecera "Referer".

Si sigue teniendo curiosidad, el código fuente es bastante explícito; mire en django.contrib.sessions si desea información privilegiada.

Usuarios y autentificación

Así que estamos a medio camino de conectar los navegadores directamente a la Gente Real. Las sesiones nos dan una manera de hacer persistir a los datos entre múltiples peticiones del navegador; la segunda parte de la ecuación es usar esas sesiones para la identificación del usuario. Por supuesto, no podemos fiarnos simplemente de que los usuarios sean quien dicen ser, así que tendremos que autentificarlos.

Naturalmente, Django proporciona herramientas para gestionar estas tareas comunes (y muchas otras). El sistema de autentificación de Django trata con cuentas de usuario, grupos, peromisos y sesiones de usuario basadas en cookies. A este sistema se le suele denominar "auth/auth" (autentificación y autorización). Este nombre reconoce que tratar con los usuarios a menudo es un proceso de dos pasos; necesitamos

  1. verificar (autentificar) que un usuario es quien dice ser (normalmente comprobando un nombre de usuario y una clave en una base de datos de usuarios), y entonces
  2. verificar que el usuario está autorizado a realizar alguna operación dada (normalmente comprobando una tabla de permisos).

De acuerdo con estas necesidades, el sistema auth/auth de Django consiste en varias partes:

Si ha usado la herramienta de administración (Capítulo 6), ya habrá visto muchas de estas herramientras, y si ha editado usuarios o grupos en el administrador en realidad habrá estado editando datos en las tablas de la base de datos del sistema de autentificación.

Instalación

Al igual que con las herramientas de sesión, el soporte de la autentificación está incluido en la aplicación de Django django.contrib que hace falta instalar. Igual que el sistema de sesiones, está instalado por omisión, pero si lo hemos eliminado tendremos que seguir los siguientes pasos para instalarlo:

  1. Asegurarnos de que el framework de sesiones está instalado (véase más arriba). Llevar el seguimiento de los usuarios precisa cookies, obviamente, y por tanto se apoya en el framework de sesiones.
  2. Ponga 'django.contrib.auth' en la opción INSTALLED_APPS y ejecute manage.py syncdb.
  3. Asegúrese de que tiene en su opción MIDDLEWARE_CLASSES la entrada 'django.contrib.auth.middleware.AuthenticationMiddleware' (después de SessionMiddleware).

Habiendo terminado la instalación, estamos listos para tratar con usuarios en las funciones de vista. La interfaz principal que vamos a usar para acceder a los usuarios dentro de una vista es request.user, que es un objeto que representa al usuario identificado en ese momento. Si el usuario no está identificado, en su lugar será un AnonymousUser (más adelante hay más detalles).

Podemos decir fácilmente si un usuario está identificado usando el método is_authenticated():

if request.user.is_authenticated():
    # Hacer algo con el usuario autentificado.
else:
    # Hacer algo con el usuario anónimo.

Trabajo con usuarios

Una vez que hemos obtenido un usuario (a menudo desde request.user, pero quizá mediante alguno de los métodos que discutiremos), ese objeto pone a nuestra disposición varios campos y métodos. Los objetos AnonymousUser emulan algunos de esos campos y métodos, pero no todos, así que siempre tendríamos que comprobar user.is_authenticated() antes de asumir que estamos tratando con un objeto de usuario completo.

Campos en los objetos User

Campo Descripción
username Preciso. 30 caracteres o menos. Sólo caracteres alfanuméricos (letras, dígitos y guiones bajos).
first_name Opcional. 30 caracteres o menos.
last_name Opcional. 30 caracteres o menos.
email Opcional. Dirección de correo electrónico.
password Preciso. Una suma de comprobación de la clave y metadatos asociados (Django no almacena claves en plano). Lea la sección "Claves" si quiere más detalles sobre este valor.
is_staff Booleano. Indica si este usuario puede acceder al sitio de administración.
is_active Booleano. Indica si esta cuenta se puede usar para ingresar al sitio. Ponga este marcador a False en lugar de borrar cuentas.
is_superuser Booleano. Indica que este usuario tiene todos los permisos sin tener que asignarlos de forma explícita.
last_login Fecha y hora del último ingreso del usuario. Está configurada para ser la fecha y hora actual por omisión.
date_joined Fecha y hora que indica el momento en que fue creada la cuenta. Está fijada a la fecha y hora del momento de creación de la cuenta.

Métodos en los objetos User

Método Descripción
is_authenticated() Siempre devuelve True``para objetos ``User "reales". Ésta es la manera de averiguar si el usuario se ha autentificado. Esto no implica ningún permiso, y no comprueba si el usuario está activo (sólo que el usuario se ha autentificado con éxito).
is_anonymous() Devuelve True sólo en objetos AnonymousUser (y False para objetos User "reales"). Por lo general, debería preferirse el uso de is_authenticated() en lugar de este método.
get_full_name() Devuelve el first_name sumado al last_name, con un espacio entre ellos.
set_password(clave) Establece la clave dada como la del usuario, ocupándose de crear el hash. Este método no guarda los datos del objeto User.
check_password(clave) Devuelve True si la clave en plano dada es la correcta para el usuario. Se ocupa de crear el hash para hacer la comparación.
get_group_permissions() Devuelve una lista de cadenas con permisos de que dispone el usuario debido a los grupos a los que pertenece.
get_all_permissions() Devuelve una lista de cadenas de permisos que posee, tanto por su pertenencia a grupos como los personales.
has_perm(perm) Devuelve True si el usuario tiene el permiso especificado, estando perm en el formato "paquete.nombrencódigo". Si el usuario está inactivo, este método devuelve siempre False.
has_perms(lista_perm) Devuelve True si el usuario tiene todos los permisos especificados. Si el usuario se encuentra inactivo, este método devuelve siempre False.
has_module_perms(nombreapp) Devuelve True``si el usuario tiene algún permiso sobre la aplicación dada. Si el usuario está inactivo, este método devolverá siempre ``False.
get_and_delete_messages() Devuelve una lista de objetos Message en la cola del usuario y elimina de ella los mensajes.
email_user(tema, mensaje) Envía un mensaje de correo electrónico al usuario. Este mensaje tendrá como remite la dirección indicada en la opción DEFAULT_FROM_EMAIL. También se puede pasar un tercer argumento, from_email, para usarlo como remite.
get_profile() Devuelve un perfil de este usuario que es específico al sitio web; vea más adelante la sección sobre perfiles si quiere saber más sobre este método.

Por último, los objetos User tienen dos campos muchos-a-muchos: groups y permissions. Los objetos User pueden acceder a sus objetos relacionados de la misma manera que con cualquier otro campo muchos-a-muchos:

# Establece los grupos de un usuario:
miusuario.groups = lista_grupos

# Añade un usuario a varios grupos:
miusuario.groups.add(grupo1, grupo2,...)

# Elimina un usuario de varios grupos:
miusuario.groups.remove(grupo1, grupo2,...)

# Elimina un usuario de todos los grupos:
miusuario.groups.clear()

# Los permisos funcionan igual
miusuario.permissions = lista_permisos
miusuario.permissions.add(permiso1, permiso2, ...)
miusuario.permissions.remove(permiso1, permiso2, ...)
miusuario.permissions.clear()

Ingreso y desconexión

Django proporciona funciones de vista para gestionar el ingreso y desconexión (y algunas otras cosas interesantes), pero antes de que llegemos a eso vamos a echar un vistazo a la manera de identificar y desconectar "a mano" a los usuarios. Django proporciona dos funcioens para realizar estas acciones en django.contrib.auth: authenticate() y login().

Para autentificar un usuario y clave dados, usamos authenticate(). Lleva dos argumentos por nombre, username y password, y devuelve un objeto User si la clave es válida para el usuario dado. Si es incorrecta, authenticate() devuelve None:

>>> from django.contrib import auth authenticate
>>> usuario = auth.authenticate(username='juan', password='secreto')
>>> if usuario is not None:
...     print "¡Correcto!"
... else:
...     print "Ups, ¡es incorrecto!"
Ups, ¡es incorrecto!

Para identificar a un usuario, en una vista, utilice login(). Toma como argumentos un objeto HttpRequest y un User y guarda el ID del usuario en la sesión, usando el framework de sesión de Django.

Este ejemplo muestra cómo deberían usarse authenticate() y login() en una función de 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:
        # Clave correcta, y el usuario está marcado "activo"
        auth.login(request, user)
        # Redirigir a una página de éxito.
        return HttpResponseRedirect("/account/identificado/")
    else:
        # Mostrar una página de error
        return HttpResponseRedirect("/account/novalido/")

Para desconectar a un usuario que ha ingresado, utilice django.contrib.auth.logout() en la vista. Toma un objeto HttpRequest y no devuelve ningún valor:

from django.contrib import auth

def logout(request):
    auth.logout(request)
    # Redirigir a una página de éxito.
    return HttpResponseRedirect("/account/desconectado/")

Tenga en cuenta que logout() no lanza ningún error si el usuario no había ingresado.

Ingresar y desconectar, a la manera fácil

En la práctica, normalmente no tendremos que escribir nuestras propias de ingreso y desconexión; el sistema de autentificación incorpora un grupo de vistas para gestionar estas operaciones de forma genérica.

El primer paso para usar estas vistas es conectarlas en nuestra URLconf. Lo haremos añadiendo este pedazo de código:

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

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

/accounts/login/ y /accounts/logout/ son las URL que usa Django por omisión para estas vistas, pero con un poco de esfuerzo las puede poner donde quiera.

Si no se especifica, le vista login muestra la plantilla registration/login.html (puede cambiarla pasando a la vista un argumento adicional llamado template_name). Este formulario debe contener un campo username y un password. Una plantilla sencilla sería como ésta:

{% extends "base.html" %}

{% block contenido %}

  {% if form.errors %}
    <p class="error">Lo siento, el nombre de usuario y clave no son válidos</p>
  {% endif %}

  <form action='.' method='post'>
    <label for="username">Nombre de usuario:</label>
    <input type="text" name="username" value="" id="username">
    <label for="password">Clave:</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 el usuario ingresa con éxito, se le redirigirá a /accounts/profile/. Puede cambiar esto incluyendo un campo oculto llamado next que contenga la URL a la que hay que redirigir tras el ingreso. También se puede pasar este valor como parámetro GET a la vista login y se añadirá automáticamente al contexto como variable llamada next que se podrá insertar en un campo oculto.

La vista de desconexión trabaja de forma ligeramente diferente; normalmente mostrará la plantilla registration/logged_out.html (que normalmente contiene un mensaje que diga "se ha desconectado con éxito"). Sin embargo, se puede invocar la vista añadiendo un argumento adicional, next_page, que le informa a la vista a dónde redirigir tras una desconexión.

Limitación de acceso a usuarios identificados

Por supuesto, la razón por la que nos tomamos tantas molestias es la de poder limitar el acceso a ciertas partes de nuestro sitio.

La manera sencilla y directa de limitar el acceso a las páginas es comprobar request.user.is_authenticated() y redirigir a una página de ingreso:

from django.http import HttpResponseRedirect

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

O quizá mostrar un mensaje de error:

def mi_vista(request):
    if not request.user.is_authenticated():
        return render_to_response('miapl/error_login.html')
    # ...

Como atajo, podemos usar el decorador login_required:

from django.contrib.auth.decorators import login_required

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

login_required hace lo siguiente:

Nota

Si le gustan los patrones de programación, verá que este decorador y los que comentaremos después son ejemplos del patrón "Guard". ¿No son divertidos los patrones?

Limitación de acceso a usuarios que pasan una prueba

Limitar el acceso basándose en ciertos permisos o alguna otra prueba, o proporcionar un destino diferente para la vista de ingreso funciona básicamente de la misma manera.

La manera directa es realizar la prueba sobre request.user en la propia vista. Por ejemplo, esta vista se asegura de que el usuario está identificado y que tiene el permiso encuestas.puede_votar (véase más adelante el funcionamiento de los permisos):

def votar(request):
    if request.user.is_authenticated() and request.user.has_perm('encuestas.puede_votar')):
        # Vote aquí
    else:
        return HttpResponse("No puede votar en esta encuesta.")

De nuevo, Django proporciona un atajo. Éste se llama user_passes_test que en realidad es una factoría de decoradores: toma argumentos y genera un decorador especializado para nuestra situación particular. Por ejemplo:

def usuario_puede_votar(user):
    return user.is_authenticated() and user.has_perm("encuestas.puede_votar")

@user_passes_text(usuario_puede_votar, login_url="/login/")
def votar(request):
    # Aquí va código que puede asumir que entra un usuario identificado
    # con los permisos correctos.
    ...

user_passes_test sólo toma un argumento obligatorio: un objeto invocable que toma como parámetro un objeto User y devuelve True si el usuario tiene permiso para ver la página. Fíjese en que user_passes_test no comprueba automáticamente que el User esté autentificado; eso debe hacerlo usted mismo.

En este ejemplo también mostramos el argumento opcional, login_url, que le permite especificar la URL de la página de ingreso (/accounts/login por omisión).

Dado que la tarea de comprobar si un usuario tiene un permiso en particular es relativamente común, Django proporciona un atajo para ese caso: el decorador permission_required(). El ejemplo anterior se podría escribir así usando este decorador:

from django.contrib.auth.decorators import permission_required

@permission_required('encuestas.puede_votar', login_url="/login/")
def votar(request):
    # ...

permission_required() también toma un segundo parámetro opcional login_url que también tiene como valor predeterminado '/accounts/login/'.

Limitación de acceso a vistas genéricas

Una de las preguntas más frecuentes en la lista Django-users versa sobre la limitación de acceso a vistas genéricas. Para conseguir esto necesitaremos escribir un fino recubrimiento sobre la vista y apuntar la URLconf hacia él en lugar de a la vista genérica en sí:

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

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

Por supuesto, también se puede sustituir login_required por cualquiera de los otros decoradores de limitación.

Gestión de usuarios, permisos y grupos

De lejos, la manera más sencilla de gestionar el sistema de autentificación es mediante el administrador. En el Capítulo 6 comentamos la manera de usar el administrador de Django para editar los usuarios y controlar sus permisos y acceso y la mayor parte del tiempo nos limitaremos a usar esa interfaz.

Sin embargo, existen APIs de bajo nivel a las que puede descender en caso de necesidad de control absoluto.

Creación de usuarios

La manera básica de crear usuarios es usar la función de apoyo create_user:

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

Llegados aquí, usuario es una instancia de User preparada para ser guardada en la base de datos. También podemos hacer algunos cambios más antes de guardarla:

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

Cambio de claves

Podemos cambiar una clave con set_password():

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

No cambie directamente el atributo password a menos que sepa bien lo que está haciendo; la forma en que se almacena realmente la clave es la de salted hash y por tanto no se puede editar de forma directa.

Siendo más formales, el atributo password de un objeto User es una cadena con este formato:

tipohash$salt$hash

El tipo de hash, el salt y el hash en sí, separados por el caracter del dólar.

tipohash puede ser bien sha1 (predeterminado) o md5 (el algoritmo que se va a usar para crear un hash unidireccional de la clave). Salt es una cadena al azar que se usa para perturbar la clave original al crear el hash.

Por ejemplo:

sha1$a1976$a36cc8cbf81742a8fb52e221aaeab48ed7f58ab4

Las funciones User.set_password() y User.check_password() gestionan tras el telón la modificación y comprobación de estos valores.

System Message: ERROR/3 (/tmp/tmp.2e73529882766d9fd5115435379fd70e/tmp.2e73529882766d9fd5115435379fd70e.rst, line 1018)

Unknown directive type "annotation".

.. annotation::
   ¿Es algún tipo de droga?

   No, un **salted hash** no tiene nada que ver con la marihuana; es una manera
   bastante común de almacenar claves de manera segura. Un **hash** es una
   función criptográfica de una sola dirección; es decir, que se puede computar
   de forma sencilla el *hash* de un valor dado, pero es casi imposible
   reconstruir el valor original partiendo del *hash*.

   Si almacenásemos las claves como texto sencillo, cualquiera que consiguiese
   acceder a la base de datos de claves podría saber al instante las claves de
   todo el mundo. Almacenar las claves como *hashes* reduce el valor de una
   base de datos comprometida.

   Sin embargo, un atacante con la base de claves podría aún intentar un ataque
   por **fuerza bruta**, obteniendo el *hash* de millones de claves y
   comparándolas con los valores almacenados. Esto podría llevar tiempo, pero
   menos del que piensa (los computadores son increíblemente rápidos)

   Lo que es peor, existen las llamadas **rainbow tables** (bases con *hashes*
   precalculados de millones de claves) y están disponibles de forma pública.
   Con una de estas tablas un atacante puede descubrir la mayoría de las
   claves en segundos.

   Añadir un **salt** (básicamente, un valor inicial aleatorio) al *hash*
   almacenado añade otra capa de dificultad. Dado que el *salt* cambia de una
   clave a otra, los *salt* también previenten ante el uso de tablas
   *rainbow*, forzando así a los atacantes a volver al ataque por fuerza
   bruta (a su vez más difícil debido a la entropía adicional creada en
   el *hash* por el *salt*)

   Aunque ésta no es exactamente la manera más segura de almacenar claves,
   son un punto medio bastante bueno entre la seguridad y la conveniencia.

Gestión de registros

Podemos usar estas herramientas de bajo nivel para crear vistas que permitan el registro de nuevos usuarios. Casi cualquier desarrollador querrá implementar este proceso de una manera diferente, así que Django le deja a usted la tarea de escribir una vista de registro; por suerte, es muy fácil.

Lo más simple sería proporcionar una pequeña vista que solicite al usuario la información necesaria y cree el usuario. Django incorpora un formulario que se puede usar para este propósito y que usaremos en este ejemplo:

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 registro(request):
    formulario = UserCreationForm()

    if request.method == 'POST':
        datos = request.POST.copy()
        errores = formulario.get_validation_errors(data)
        if not errores:
            nuevo_usuario = formulario.save()
            return HttpResponseRedirect("/accounts/created/")
    else:
        datos, errores = {}, {}

    return render_to_response("registration/register.html", {
        'form' : forms.FormWrapper(formulario, datos, errores)
    })

Esto asume la existencia de una plantilla llamada registration/register.html; y aquí tiene un ejemplo de cómo podría ser tal plantilla:

System Message: WARNING/2 (/tmp/tmp.2e73529882766d9fd5115435379fd70e/tmp.2e73529882766d9fd5115435379fd70e.rst, line 1088)

Literal block expected; none found.