/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.
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.
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.
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. |
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.
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.
Las sesiones están implementadas mediante middleware (véase el Capítulo 16) y un modelo de Django. Para habilitar las sesiones tendremos que:
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.
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:
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.
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.
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}
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.
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.
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.
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
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.
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:
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.
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.
| 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. |
| 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é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()
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.
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.
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?
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/'.
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.
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.
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()
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.
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: