USERS = {'vtacius':'editor', 'viewer':'viewer'} GROUPS = {'vtacius':['group:editors','group:admins']} def groupfinder(userid, request): if userid in USERS: return GROUPS.get(userid, [])Lo que necesitamos en realidad es a groupfinder. Los diccionarios USERS y GROUPS son, por decirlo de una forma, nuestra forma de simular nuestra base de datos. De hecho, esta función no es del todo obligatoria. Sólo la usamos si queremos agregar un principals a nuestro usuario logueado. Sea cual sea la forma que usemos para esta función (Seguramente habrá una consulta a una base de datos), lo importante es que debe devolver una lista de principals (Que a esta altura se antoja entenderlos como "roles") en la forma mostrada:
['groups:editors','groups:publishers']
Luego creamos el ficheros ./ambiente/aplicacion/aplicacion/resources.py con el siguiente contenido:
from pyramid.security import Allow, Everyone, Authenticated class Root(object): __acl__ = [ (Allow, Everyone, 'listar'), (Allow, Authenticated, 'detallar'), (Allow, 'groups:admins', 'creacion'), (Allow, 'groups:editors', 'modificacion'), (Allow, 'groups:admins', 'borrado') ] def __init__(self, request): passQue es básicamente la ACL de la aplicación. La ACL en cuestión de compone de una lista de tuplas (Llamadas ACE). que tienen la siguiente forma:
({{Acción a tomar}}, {{Principals necesario}}, {{Nombre de ACE}})Para unir todo esto, necesitamos modificar el ./ambiente/aplicacion/aplicacion/__init__.py de nuestra aplicación para configurar la autenticación y autorización:
# coding: utf-8 from pyramid.config import Configurator # Empieza el trabajo con la autenticación de la wea esta from pyramid.authentication import AuthTktAuthenticationPolicy from pyramid.authorization import ACLAuthorizationPolicy from pyramid.security import Authenticated from .security import groupfinder def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ # Empieza el trabajo con la autenticacion: Creo las politicas, así nomás y sin gracia # Por cierto, 'c3cre3t0 debería cambiarse a algo más personal # como callback, aparece nuestro amigo groupfinder, pero esto podría obviarse authn_policy = AuthTktAuthenticationPolicy('c3cr3t0', hashalg='sha512', callback=groupfinder) authz_policy = ACLAuthorizationPolicy() # Y por acá agregamos a resources.py config = Configurator(settings=settings, root_factory='.resources.Root') # Empieza el trabajo con la autenticacion: Configuro las politicas en la aplicación config.set_authentication_policy(authn_policy) config.set_authorization_policy(authz_policy) # Donde sucede la magia del login config.add_route(name='login', pattern='/login', request_method='POST') # Cuando request_method se encuentra acá, es devuelto un error como # "The resource could not be found." # Así que es como mejor configurar desde acá a request_method config.add_route('ficheros_listado', '/ficheros', request_method='GET') config.add_route('ficheros_detalle', '/ficheros/{usuario}', request_method='GET') config.add_route('ficheros_creacion', '/ficheros', request_method='POST') config.add_route('ficheros_modificacion', '/ficheros/{usuario}', request_method='PUT') config.add_route('ficheros_borrado', '/ficheros/{usuario}', request_method='DELETE') config.scan() return config.make_wsgi_app()Y añadimos los permisos en nuestras vistas. Pues parece que sólo funcionan de esa manera. Así que el contenido de ./ambiente/aplicacion/aplicacion/views/actividades.py queda de la siguiente forma
# coding: utf-8 from pyramid.view import view_config from ..fichero_app.ficheros import Usuarios from pyramid import httpexceptions as exception # Podrías usar los siguientes en lugar de los permisos personalizados que tienes en este momento, pero no # La importación de hecho no es necesaria para nada from pyramid.security import Everyone, Authenticated @view_config(route_name='ficheros_listado', renderer='json', permission='listar') def ficheros_listado(request): """Cuando request_method se configura acà, el mensaje es diferente porque la operación alcanzada es diferente The resource could not be found. predicate mismatch for view get_listar_ficheros (request_method = GET,HEAD) Por tanto lo mejor es configurarlo allá en __init__ """ ficheros = Usuarios() listado = ficheros.listado() return {'respuesta': listado} @view_config(route_name='ficheros_detalle', renderer='json', permission='detallar') def ficheros_detalle(request): usuario = request.matchdict['usuario'] ficheros = Usuarios() detalle = ficheros.detalle(usuario) return {'respuesta': detalle} @view_config(route_name='ficheros_creacion', renderer='json', permission='creacion') def ficheros_creacion(request): try: usuario = request.json_body['usuario'] data = request.json_body['data'] except Exception as e: return exception.HTTPBadRequest() ficheros = Usuarios() creacion = ficheros.creacion(usuario, data) return {'respuesta': creacion} @view_config(route_name='ficheros_modificacion', renderer='json', permission='modificacion') def ficheros_modificacion(request): usuario = request.matchdict['usuario'] try: data = request.json_body['data'] except Exception as e: return exception.HTTPBadRequest() ficheros = Usuarios() modificacion = ficheros.modificacion(usuario, data) return {'respuesta': modificacion} @view_config(route_name='ficheros_borrado', renderer='json', permission='borrado') def ficheros_borrado(request): usuario = request.matchdict['usuario'] ficheros = Usuarios() borrado = ficheros.borrado(usuario) return {'respuesta': borrado}Y solo faltaría agregar la pequeña vista que se encarga del login ./ambiente/aplicacion/aplicacion/views/login.py:
# coding: utf-8 from pyramid.view import view_config from pyramid.security import remember from pyramid.httpexceptions import HTTPFound @view_config(route_name='login', request_method='POST') def login(request): usuario = request.POST.get('username') cabeceras = remember(request, usuario) return HTTPFound(headers=cabeceras) # De la siguiente forma, no hay HTML devuelto, pero todo funciona bien #response = request.response #response.headerlist.extend(cabeceras) #return responseAsí procedemos a probar nuestra aplicación desde consola:
$ curl -w '\n' -X GET http://localhost:6543/ficheros {"respuesta": ["alortiz", "kpenate", "opineda"]}Atentos a este. -i agregará las cabeceras que recibimos de respuesta para que quede totalmente claro lo que pasa:
$ curl -i -w '\n' -X GET http://localhost:6543/ficheros/alortiz HTTP/1.1 403 Forbidden Content-Length: 1085 Content-Type: text/html; charset=UTF-8 Date: Tue, 07 Jun 2016 02:36:22 GMT Server: waitress <html> <head> <title>403 Forbidden</title> </head> <body> <h1>403 Forbidden</h1> Access was denied to this resource.<br/><br/> debug_authorization of url http://localhost:6543/ficheros/alortiz (view name u'' against context <aplicacion.resources.Root object at 0x7f501c490e90>): ACLDenied permission 'detallar' via ACE '<default deny>' in ACL [('Allow', 'system.Everyone', 'listar'), ('Allow', 'system.Authenticated', 'detallar'), ('Allow', 'groups:admins', 'creacion'), ('Allow', 'groups:editors', 'modificar'), ('Allow', 'groups:admins', 'eliminar')] on context <aplicacion.resources.Root object at 0x7f501c490e90> for principals ['system.Everyone'] <link rel="stylesheet" type="text/css" href="http://localhost:6543/_debug_toolbar/static/toolbar/toolbar_button.css"> <div id="pDebug"> <div id="pDebugToolbarHandle"> <a title="Show Toolbar" id="pShowToolBarButton" href="http://localhost:6543/_debug_toolbar/313339393832303438363539353336" target="pDebugToolbar">« FIXME: Debug Toolbar</a> </div> </div> </body> </html>HTTP/1.1 403 Forbidden. Que ha funcionado. Así que ahora probamos la autenticación mediante curl desde la siguiente forma:
$ curl -i -w "\n" -X POST http://localhost:6543/login -d "username=vtacius"
HTTP/1.1 302 Found Content-Length: 556 Content-Type: text/html; charset=UTF-8 Date: Tue, 07 Jun 2016 02:43:25 GMT Location: http://localhost:6543/login Server: waitress Set-Cookie: auth_tkt=95c36928020725f40edd54266285117cfddd4d05b0cc42f969b9fee13a3ff52f4e5f98491c426f6c6bc5242d1ace926e264865a8cc1a30f127d1b1a6ec3de457575634cddnRhY2l1cw%3D%3D!userid_type:b64unicode; Path=/ Set-Cookie: auth_tkt=95c36928020725f40edd54266285117cfddd4d05b0cc42f969b9fee13a3ff52f4e5f98491c426f6c6bc5242d1ace926e264865a8cc1a30f127d1b1a6ec3de457575634cddnRhY2l1cw%3D%3D!userid_type:b64unicode; Domain=localhost; Path=/ Set-Cookie: auth_tkt=95c36928020725f40edd54266285117cfddd4d05b0cc42f969b9fee13a3ff52f4e5f98491c426f6c6bc5242d1ace926e264865a8cc1a30f127d1b1a6ec3de457575634cddnRhY2l1cw%3D%3D!userid_type:b64unicode; Domain=.localhost; Path=/ <html> <head> <title>302 Found</title> </head> <body> <h1>302 Found</h1> The resource was found at ; you should be redirected automatically. <link rel="stylesheet" type="text/css" href="http://localhost:6543/_debug_toolbar/static/toolbar/toolbar_button.css"> <div id="pDebug"> <div id="pDebugToolbarHandle"> <a title="Show Toolbar" id="pShowToolBarButton" href="http://localhost:6543/_debug_toolbar/313339393832303434383337353834" target="pDebugToolbar">« FIXME: Debug Toolbar</a> </div> </div> </body> </html>(Para correr este última petición podría ser necesario reiniciar nuestro servidor web de prueba)
Y otra vez nos hemos ahorrado escribir un formulario a la carrera con la opción -d de curl, con la que enviamos los datos que la aplicación requiere.
Lo que necesitamos de ahora en adelante es usar la opción -H para configurar a curl que use la cookie auth_tkt que nos envío como respuesta la aplicación en cada petición que necesite autenticación. Cuidado con usar comillas dobles para limitar el contenido de la cookie, desde consola existe el inconveniente de:
$ curl -w '\n' -X GET http://localhost:6543/ficheros/alortiz -H 'Cookie: auth_tkt=95c36928020725f40edd54266285117cfddd4d05b0cc42f969b9fee13a3ff52f4e5f98491c426f6c6bc5242d1ace926e264865a8cc1a30f127d1b1a6ec3de457575634cddnRhY2l1cw%3D%3D!userid_type:b64unicode' {"respuesta": {"palabras": ["ambiente", "publico"], "nombre": "Alexander", "apellido": "Ort\u00edz"}}
Fuentes:
How to create high scalable web-backends for ios developers 0.0.0 documentation
No hay comentarios:
Publicar un comentario