Control de Acceso

web2py incluye un poderoso y personalizable mecanismo de control de acceso basado en roles (RBAC por sus siglas en inglés).

Aquí está una definición desde Wikipedia:

“El Control de Acceso Basado en Roles (RBAC) es un enfoque para restringir acceso al sistema a usuarios autorizados. Es un nuevo enfoque alternativo al acceso de control mandatario (MAC) y acceso de control discrecional (DAC). RBAC es referido algunas veces como seguridad basada en roles.

RBAC es una tecnología de control de acceso flexible y de política neutral suficientemente poderosa para simular DAC y MAC. De manera inversa, MAC puede simular RBAC si el gráfico de roles está restringido a un árbol en vez de un conjunto parcialmente ordenado.

Anterior al desarrollo de RBAC, MAC y DAC eran considerados como los únicos modelos conocidos para el control de acceso: si un modelo no era MAC, se consideraba que era DAC, y viceversa. Investigaciones a finales de los 90s demostraron que RBAC no cae en ninguna de las dos categorías.

Dentro de una organización son creados roles para varias funciones de trabajo. Los permisos para realizar ciertas operaciones son asignados a roles específicos. Miembros del personal (u otros usuarios del sistema) se les asigna roles particulares, y a través de la asignación de esos roles adquieren los permisos para realizar funciones de sistema particulares. A diferencia de control de acceso basado en contexto (CBAC), RBAC no revisa el contexto del mensaje (tal como la fuente de la conexión).

Ya que a los usuarios no se les asigna permisos directamente, sino que sólo adquieren dichos permisos a través de su rol (o roles), el manejo de los derechos individuales de los usuarios se vuelve una tarea de simplemente asignarle al usuario roles apropiados; ésto simplifica operaciones comunes, tales como agregar un usuario, o cambiar el departamento de un usuario.

RBAC difiere de las listas de control de acceso (ACLs) usadas en sistemas de control de acceso discrecional en que asigna permisos a operaciones específicas con significado en la organización, en vez de a objetos con bajo nivel de datos. Por ejemplo, una lista de control de acceso podría ser usada para dar o negar acceso de escritura a un archivo de sistema particular, pero no dictaría como ese archivo pudiese ser cambiado.”

La clase web2py que implementa RBAC es llamada Auth.

Auth necesita (y define) las siguientes tablas:

  • auth_user: guarda el nombre, dirección de correo, clave, y estatus (registro pendiente, aceptado, bloqueado) del usuario.
  • auth_group: guarda grupos o roles para usuarios en una estructura muchos a muchos. Por defecto, cada usuario está en su propio grupo, pero un usuario puede estar en múltiples grupos, y cada grupo puede contener múltiples usuarios. Un grupo es identificado por un rol y una descripción.
  • auth_membership: vincula usuarios y grupos en una estructura muchos a muchos.
  • auth_permission: vincula grupos y permisos. Un permiso es identificado por un nombre y, opcionalmente, una tabla y un registro. Por ejemplo, miembros de cierto grupo pueden tener permisos de actualización a un registro específico de una tabla específica.auth_permission: vincula grupos y permisos. Un permiso es identificado por un nombre y, opcionalmente, una tabla y un registro. Por ejemplo, miembros de cierto grupo pueden tener permisos de actualización a un registro específico de una tabla específica.
  • auth_event: registra los cambios en el orden de las tablas y acceso exitoso por medio de CRUD a objetos controlados por el RBAC.

En principio, no hay restricción en los nombres de los roles y los nombres de los permisos; el desarrollador puede crearlos para arreglar los roles y permisos en la organización. Una vez que han sido creados, web2py provee un API para revisar si un usuario ha iniciado sesión, si un usuario es miembro de un grupo dado, y/o si el usuario es miembro de algún grupo que tenga un permiso requerido dado.

web2py también proporciona decoradores para restringir el acceso a cualquier función basada en inicio de sesión, membresía y permisos.

web2py también comprende algunos permisos específicos, por ejemplo, esos que tienen un nombre que corresponde a los métodos CRUD (create, read, update, delete) y puede hacerlos cumplir automáticamente sin la necesidad de usar decoradores.

En este capítulo vamos a discutir diferentes partes del RBAC, una a una.

Autenticación

A fin de utilizar RBAC, los usuarios necesitan estar identificados. Ésto significa que necesitan registrarse (o estar registrados) e iniciar sesión.

Auth proporciona múltiples métodos de inicio de sesión. El predeterminado consiste en identificar los usuarios basándose en la tabla local auth_user. De manera alterna, puede iniciar sesión a los usuarios chequeando contra sistemas de autenticación de terceros e inicio de sesión únicos en proveedores tales como Google, PAM, LDAP, Facebook, LinkedIn, OpenID, Oauth, etc..

Para empezar a usar Auth, usted necesita por lo menos este código en un archivo de modelo, el cual también viene incluído con la aplicación “welcome” de web2py y que asume un objeto de conexión db:

1
2
3
from gluon.tools import Auth
auth = Auth(globals(), db)
auth.define_tables(username=False)

Establezca username=True si quiere autenticar el nombre de usuario para inicio de sesión en vez del correo electrónico.

Para exponer Auth, usted también necesita la siguiente función en un controlador (por ejemplo en “default.py”):

1
def user(): return dict(form=auth())

El objeto auth y la acción user ya están definidas en la aplicación de andamiaje.

web2py también incluye una vista ejemplo “default/user.html” para pasar esta función propiamente que se ve como:

1
2
3
4
5
6
7
{{extend 'layout.html'}}
<h2>{{=request.args(0)}}</h2>
{{=form}}
{{if request.args(0)=='login':}}
<a href="{{=URL(args='register')}}" >register</a><br />
<a href="{{=URL(args='request_reset_password')}}" >lost password</a><br />
{{pass}}

Note que esta función simplemente muestra un formulario y por lo tanto puede ser personalizado usando la sintaxis normal de formularios personalizados. La única salvedad es que el formulario mostrado depende del valor de request.args(0) por lo tanto usted puede necesitar una sentencia if como la siguiente:

1
{{if request.args(0)=='login':}}...custom login form...{{pass}}

El anterior controlador expone múltiples acciones:

http://.../[app]/default/user/register

http://.../[app]/default/user/login

http://.../[app]/default/user/logout

http://.../[app]/default/user/profile

http://.../[app]/default/user/change_password

http://.../[app]/default/user/verify_email

http://.../[app]/default/user/retrieve_username

http://.../[app]/default/user/request_reset_password

http://.../[app]/default/user/reset_password

http://.../[app]/default/user/impersonate

http://.../[app]/default/user/groups

http://.../[app]/default/user/not_authorized

  • register: le permite a los usuario registrarse. Es integrado con CAPTCHA, aunque ésto está por defecto deshabilitado.
  • login: permite a los usuarios registrados iniciar sesión (si el registro es verificado o no requiere verificación, si ya ha sido aprobado o no requiere aprobación, y si no ha sido bloqueado).
  • logout: hace lo que usted esperaría pero también, como los otros métodos, registra el evento y puede ser usado para disparar algún evento.
  • profile: le permite a los usuarios editar su perfil, es decir, el contenido de la tabla auth_user. Note que esta tabla no tiene una estructura fija y puede ser personalizada.
  • change_password: le permite a los usuarios cambiar su clave de manera segura a prueba de fallos.
  • verify_email: si la verificación por correo está encendida, entonces los visitantes, luego de registrarse, reciben un correo con un vínculo para verificar su información de correo. El vínculo apunta a esta acción.
  • retrieve_username: por defecto, Auth usa el correo y la clave para el inicio de sesión, pero puede, opcionalmente, usar el nombre de usuario en vez del correo. En este caso, si un usuario olvida su nombre de usuario, el método retrieve_username le permite escribir el correo y recuperar el nombre de usuario por correo.
  • request_reset_password: le permite a los usuarios que olvidaron su clave pedir una nueva. Ellos recibirán un correo de confirmación apuntando a reset_password.
  • impersonate: le permite al usuario suplantar a otro usuario. Ésto es importante para fines de búsqueda de errores y soporte. request.args[0] es el id del usuario que va a ser suplantado. Ésto es permitido sólo si el usuario que inició sesión tiene permisología has_permission(‘impersonate’, db.auth_user, user_id).
  • groups: lista los grupos a los cuales el usuario que inicio sesión pertenece.
  • not_authorized: muestra un mensaje de error cuando el visitante trata de hacer algo para lo cual no está autorizado(a).
  • navbar: en un ayudante que genera una barra con vínculos de inicio de sesión (login) / registro (register) / etc..

Logout, profile, change_password, impersonate, y groups requieren iniciar sesión.

Por defecto todos ellos son expuestos, pero es posible restringir el acceso a solo unas de estas acciones.

Todos los métodos mostrados arriba pueden ser extendidos o reemplazados por subclases Auth.

Para restringir el acceso a funciones a sólo visitantes que hayan iniciado sesión, decore la función como muestra el siguiente ejemplo:

1
2
3
@auth.requires_login()
def hello():
     return dict(message='hello %(first_name)' % auth.user)

Cualquier función puede ser decorada, no sólo las acciones expuestas. Por supuesto ésto es aún un muy simple ejemplo de control de acceso. Ejemplos más complejos serán discutidos más adelante.

auth.user contiene una copia de los registros db.auth_user para el usuario que inició sesión o None para el caso contrario. También hay un auth.user_id que el el mismo que auth.user.id (es decir, el id del usuario que inició sesión) o None.

Restricciones en el proceso de registro

Si quiere permitir a los visitantes registrarse pero no iniciar sesión hasta que el registro ha sido aprobado por el administrador:

1
auth.settings.registration_requires_approval = True

Usted puede aprobar el registro por medio de la interfaz de administración de aplicaciones. Vea la tabla auth_user. Los registros pendientes tienen un campo registration_key que está en “pending” (pendiente). Un registro es aprobado cuando este campo está en blanco.

Por medio de la interfaz de administración de aplicaciones usted también puede bloquear el inicio de sesión de un usuario. Localice el usuario en la tabla auth_user y defina registration_key en “blocked” (bloqueado). Los usuarios bloqueados no tienen permitido iniciar sesión. Note que ésto evitará que el visitante inicie sesión pero no forzará a un visitante que ya inició sesión a que la termine.

Usted puede bloquar el acceso a la página de registro completamente con la siguiente sentencia:

1
auth.settings.actions_disabled.append('register')

Otro métodos de Auth pueden restringirse de la misma manera.

Integración con OpenID, Facebook, etc.

Usted puede usar el control de acceso basado en roles de web2py y autenticar con otros servicios como OpenID, Faceboo, LinkedIn, Google, MySpace, Flickr, etc. La manera más fácil es usar Janrain Engage (anteriormente RPX)(Janrain.com).

Janrain.com es un servicio que provee autenticación middleware. Usted se puede registar con Janrain.com, registrar un dominio (el nombre de su aplicación) y establecer los URLs que estará usando, y ellos le proveerán la llave API.

Ahora edite el modelo de su aplicación web2py y coloque las siguientes líneas en algún lugar antes de la definición del objeto auth:

1
2
3
4
5
6
from gluon.contrib.login_methods.rpx_account import RPXAccount
auth.settings.actions_disabled=['register','change_password','request_reset_password']
auth.settings.login_form = RPXAccount(request,
    api_key='...',
    domain='...',
    url = "http://localhost:8000/%s/default/user/login" % request.application)

La primera línea importa el nuevo método de inicio de sesión, la segunda deshabilita el registro local, y la tercera líne le pide a web2py que use el método de inicio de sesión RPX. Usted debe insertar su propio api_key proporcionado por Janrain.com, el dominio que eligió después del registro y el url externo de su página de inicio de sesión.

Cuando un nuevo usuario inicia sesión por primera vez, web2py crea un nuevo registro db.auth_user asociado al usuario. Usará el campo registration_id para almacenar un id único para el usuario. La mayoría de los métodos de autenticación también proveerán de un nombre de usuario, correo, nombre y apellido, pero esto no se garantiza. Cuales campos se proveen depende en el método de inicio de sesión seleccionado por el usuario. Si el mismo usuario inicia sesión dos veces usando diferentes mecanismos de autenticación (por ejemplo uno con OpenID y otro con Facebook), Janrain puede no reconocerle como el mismo usuario(a) y podría producir un diferente registration_id.

Usted puede personalizar la correspondencia entre los datos proporcionados por Janrain y los datos almacenados en db.auth_user. He aquí un ejemplo para Facebook:

1
2
3
4
5
6
auth.settings.login_form.mappings.Facebook = lambda profile:\
            dict(registration_id = profile["identifier"],
                 username = profile["preferredUsername"],
                 email = profile["email"],
                 first_name = profile["name"]["givenName"],
                 last_name = profile["name"]["familyName"])

Las claves en el diccionario son campos en db.auth_user y los valores son entradas de datos en el objeto de perfil provisto por Janrain. Vea la documentación en línea de Janrain para detalles de ésto último.

Janrain también mantendrá estadísticas acerca de los inicio de sesión de los usuarios.

Éste formulario de inicio de sesión está completamente integrado con el Control de Acceso Basado en Roles de web2py y usted aún puede crear grupos, hacer que usuarios sean miembros de grupos, asignar permisos, boquear usuarios, etc.

Si usted prefiere no usar Janrain y quiere usar un método de inicio de sesión diferente (LDAP, PAM, Google, OpenID, Oauth/Facebook, LinkedIn, etc.) puede hacerlos. Los API para hacerlo se describen más adelante.

CAPTCHA y reCAPTCHA

Para prevenir que spammers y bots se registren en su sitio, usted puede requerir un CAPTCHA de registro. web2py soporta reCAPTCHA(71) por completo desde el inicio. Ésto se debe a que reCAPTCHA está muy bien diseñado, es libre, accesible (puede leer las palabras a los visitantes), es fácil de configurar, y no requiere instalar librerías de terceros.

Ésto es lo que necesita para usar reCAPTCHA:

  • Registrarse con reCAPTCHA y obtener una pareja PUBLIC_KEY, PRIVATE_KEY para su cuenta. Éstas son sólo dos cadenas.

  • Agregar el siguiente código a su modelo después de que el objeto auth es definido:

    1
    2
    3
    from gluon.tools import Recaptcha
    auth.settings.captcha = Recaptcha(request,
        'PUBLIC_KEY', 'PRIVATE_KEY')
    

reCAPTCHA puede no trabajar si usted accede al sitio como ‘localhost’ o ‘127.0.0.1’, debido a que es registrado para trabajar solamente con sitios web públicamente visibles.

El constructor Recaptcha toma algunos argumentos opcionales:

1
Recaptcha(..., use_ssl=True, error_message='invalid')

Note que use_ssl=False es así por defecto.

Si usted no quiere usar reCAPTCHA, eche un vistazo a la definición de la clase Recaptcha en “gluon/tools.py”, ya que es fácil usar otros sistemas CAPTCHA.

Personalizando Auth

La llamada a

1
auth.define_tables()

define todas las tablas Auth que no ha sido definidas aún. Ésto significa que si usted lo desea, puede definir su propia tabla auth_user. Usando una sintaxis similar a la mostrada a continuación, usted puede personalizar cualquier otra tabla Auth.

Aquí está la manera apropiada para definir una tabla de usuario:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# after
# auth = Auth(globals(),db)
db.define_table(
    auth.settings.table_user_name,
    Field('first_name', length=128, default=''),
    Field('last_name', length=128, default=''),
    Field('email', length=128, default='', unique=True),
    Field('password', 'password', length=512,
          readable=False, label='Password'),
    Field('registration_key', length=512,
          writable=False, readable=False, default=''),
    Field('reset_password_key', length=512,
          writable=False, readable=False, default=''),
    Field('registration_id', length=512,
          writable=False, readable=False, default=''))
custom_auth_table = db[auth.settings.table_user_name] # get the custom_auth_table
custom_auth_table.first_name.requires = \
  IS_NOT_EMPTY(error_message=auth.messages.is_empty)
custom_auth_table.last_name.requires = \
  IS_NOT_EMPTY(error_message=auth.messages.is_empty)
custom_auth_table.password.requires = [IS_STRONG(), CRYPT()]
custom_auth_table.email.requires = [
  IS_EMAIL(error_message=auth.messages.invalid_email),
  IS_NOT_IN_DB(db, custom_auth_table.email)]
auth.settings.table_user = custom_auth_table # tell auth to use custom_auth_table
# before
# auth.define_tables()

Puede agregar cualquier campo que desee, pero no puede remover los campos requeridos mostrados en este ejemplo.

Es importante hacer que los campos clave “password”, clave de registro “registration_key”, clave de reinicio de clave “reset_password_key” e id de registro “registration_id” no puedan leerse “readable=False” ni reescribirse “writable=False”, ya que no se le puede permitir a un visitante que los manipule.

Si usted agrega un campo llamada “username”, será usado en lugar del correo “email” para el inicio de sesión. Si lo hace, necesitará agregar también un validador:

1
auth_table.username.requires = IS_NOT_IN_DB(db, auth_table.username)

Renombrando las tablas Auth

Los nombres reales de las tablas Auth son almacenados en

1
2
3
4
5
auth.settings.table_user_name = 'auth_user'
auth.settings.table_group_name = 'auth_group'
auth.settings.table_membership_name = 'auth_membership'
auth.settings.table_permission_name = 'auth_permission'
auth.settings.table_event_name = 'auth_event'

Los nombres de la tabla puede ser cambiado reasignando estas variables después de que el objeto auth es definido y antes de que las tablas Auth son definidas. Por ejemplo:

1
2
3
4
auth = Auth(globals(),db)
auth.settings.table_user_name = 'person'
#...
auth.define_tables()

Las tablas reales también pueden ser referenciadas, independiente de sus nombres reales, por

1
2
3
4
5
auth.settings.table_user
auth.settings.table_group
auth.settings.table_membership
auth.settings.table_permission
auth.settings.table_event

Otros métodos de inicio de sesión y formularios de inicio de sesión

Auth proporciona múltiples métodos de inicio de sesión y ganchos para crear nuevos métodos de inicio de sesión. Cada método de inicio de sesión soportado corresponde a un archivo en la carpeta

gluon/contrib/login_methods/

Diríjase a la documentación en los mismos archivos para cada método de inicio de sesión, aunque aquí tiene algunos ejemplos.

Antes que todo, necesitamos hacer distinción entre dos tipos de métodos de inicio de sesión alternos:

  • métodos de inicio de sesión que usan un formulario web2py (aunque las credenciales son verificadas afuera de web2py). Un ejemplo es LDAP.
  • Métodos de inicio de sesión que requieren un formulario externo (como ejemplo están Google y Facebook).

En el último caso, web2py nunca obtiene las credenciales de inicio de sesión, sólo un token de inicio de sesión producido por el proveedor de servicios. El token es almacenado en db.auth_user.registration_id.

Consideremos ejemplos del primer caso:

Basic

Digamos que usted tiene un servicio de autenticación, por ejemplo en el url

https://basic.example.com

que acepta autenticación de acceso básico. Ésto significa que el servidor acepta solicitudes HTTP con un encabezado de la forma:

GET /index.html HTTP/1.0
Host: basic.example.com
Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==

Donde la última cadena es la codificación en base 64 de la cadena nombredeusuario:clave. El servicio responde 200 OK si el usuario es autorizado y 400, 401, 402, 403 o 404 en caso contrario.

Usted quiere ingresar el nombre de usuario y la clave usando el formulario de inicio de sesión estándar Auth y verificar la credenciales contra un servicio como los antes vistos. Todo lo que necesita hacer es agregar el siguiente código en su apliación

1
2
3
from gluon.contrib.login_methods.basic_auth import basic_auth
auth.settings.login_methods.append(
    basic_auth('https://basic.example.com'))

Note que auth.settings.login_methods es una lista de métodos de autenticación que son ejecutados secuencialmente. Por defecto ésto se establece a

1
auth.settings.login_methods = [auth]

Cuando un método alterno se agrega, por ejemplo basic_auth, Auth primero trata de iniciarle sesión al usuario basándose en el contenido de auth_user, y cuando falla, prueba el siguiente método en la lista. Si un método tiene éxito al iniciar sesión, y si auth.settings.login_methods[0]==auth, Auth toma las siguientes acciones:

  • Si el usuario no existe en auth_user, un nuevo usuario es creado y el nombre de usuario / correo y la clave son almacenadas.
  • Si el usuario existe en auth_user pero la nueva clave aceptada no coincide con la antigua clave almacenada, la antigua clave es reemplazada por la nueva (note que las claves siempre son almacenadas luego de hacerles hash a menos que se especifique lo contrario).

Si no desea almacenar la nueva clave en auth_user, entonces es suficiente con cambiar el orden de los métodos de inicio de sesión, o remover auth de la lista. Por ejemplo:

1
2
3
from gluon.contrib.login_methods.basic_auth import basic_auth
auth.settings.login_methods = \
    [basic_auth('https://basic.example.com')]

Lo mismo aplica para cualquier otro método de inicio de sesión descrito aquí.

SMTP y Gmail

Usted puede verificar las credenciales de inicio de sesión usando un servidor remoto SMTP, por ejemplo Gmail; es decir, inicia sesión al usuario si el correo y la clave provistas son credenciales válidas para acceder al servidor Gmail SMTP (smtp.gmail.com:587) . Todo lo que se necesita es el siguiente código:

1
2
3
from gluon.contrib.login_methods.email_auth import email_auth
auth.settings.login_methods.append(
    email_auth("smtp.gmail.com:587", "@gmail.com"))

El primero argumento de email_auth es la dirección:puerto del servidor SMTP. El segundo argumento es el dominio del correo.

Ésto funciona con cualquier servidor SMTP que requiere autenticación TLS.

PAM

Autenticación usando los Módulos de Autenticación Conectables (PAM de sus siglas en inglés) funciona como los casos previos. Permite que web2py autentique usuarios usando las cuentas del sistema operativo:

1
2
from gluon.contrib.login_methods.pam_auth import pam_auth
auth.settings.login_methods.append(pam_auth())

LDAP

Autenticación usando LDAP funciona en su mayoría como los casos anteriores.

Para usar inicio de sesión LDAP con el MS Active Directory:

1
2
3
4
from gluon.contrib.login_methods.ldap_auth import ldap_auth
auth.settings.login_methods.append(ldap_auth(mode='ad',
   server='my.domain.controller',
   base_dn='ou=Users,dc=domain,dc=com'))

Para usar inicio de sesión LDAP con Lotus Notes y Domino:

1
2
auth.settings.login_methods.append(ldap_auth(mode='domino',
   server='my.domino.server'))

Para usar inicio de sesión LDAP con OpenLDAP (con UID):

1
2
auth.settings.login_methods.append(ldap_auth(server='my.ldap.server',
   base_dn='ou=Users,dc=domain,dc=com'))

Para usar inicio de sesión LDAP con OpenLDAP (con CN):

1
2
auth.settings.login_methods.append(ldap_auth(mode='cn',
   server='my.ldap.server', base_dn='ou=Users,dc=domain,dc=com'))

Motor de aplicaciones de Google

Autenticación usando Google cuando está funcionando el Motor de Aplicaciones de Google requiere saltarse el formulario de inicio de sesión de web2py, ser redirigido a la página de inicio de sesión de Google, y regresar de tener éxito. Debido a que el comportamiento es diferente que en los ejemplos previos, la API es un poco diferente:

1
2
from gluon.contrib.login_methods.gae_google_login import GaeGoogleAccount
auth.settings.login_form = GaeGoogleAccount()

OpenID

Previamente hemos discutido la integración con Janrain (que tiene soporte OpenID) y que es la manera más fácil de usar OpenID. Sin embargo algunas veces usted podría no querer depender en un servicio de terceros pero quiere acceder al proveedor OpenID directamente desde el consumidor (su aplicación).

He aquí un ejemplo:

1
2
from gluon.contrib.login_methods.openid_auth import OpenIDAuth
auth.settings.login_form = OpenIDAuth(auth)

OpenIDAUth requiere que el módulo “python-open” esté instalado de manera separada.

De manera transparente este método de inicio de sesión define la siguiente tabla

1
2
3
4
db.define_table('alt_logins',
    Field('username', length=512, default=''),
    Field('type', length =128, default='openid', readable=False),
    Field('user', self.table_user, readable=False))

que almacena los nombres de usuario openid para cada usuario. Si quiere mostrar los openids para el usuario que ha iniciado sesión:

{{=auth.settings.login_form.list_user_openids()}}

OAuth2.0 y Facebook

Hemos discutido anteriormente la integración con Janrain (que tiene soporte para Facebook), sin embargo algunas veces usted puede no querer depender en un servicio de terceros y quiere acceder al proveedor OAuth2.0 directamente; por ejemplo, Facebook. He aquí como hacerlo:

1
2
from gluon.contrib.login_methods.oauth20_account import OAuthAccount
auth.settings.login_form=OAuthAccount(globals(),YOUR_CLIENT_ID,YOUR_CLIENT_SECRET)

Las cosas comienzan a ponerse más complejas si usted quiere usar inicio de sesión Facebook OAuth2.0 para una aplicación específica de Facebook para accesar su API, en vez de su propia aplicación. He aquí un ejemplo para acceder a la API de gráficos de Facebook.

Antes que todo usted debe instalar el módulo “pyfacebook”.

Luego necesita el siguiente código en su modelo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# import required modules
from facebook import GraphAPI
from gluon.contrib.login_methods.oauth20_account import OAuthAccount
# extend the OAUthAccount class
class FaceBookAccount(OAuthAccount):
    """OAuth impl for FaceBook"""
    AUTH_URL="https://graph.facebook.com/oauth/authorize"
    TOKEN_URL="https://graph.facebook.com/oauth/access_token"
    def __init__(self, g):
        OAuthAccount.__init__(self, g,
                              YOUR_CLIENT_ID,
                              YOUR_CLIENT_SECRET,
                              self.AUTH_URL,
                              self.TOKEN_URL)
        self.graph = None
    # override function that fetches user info
    def get_user(self):
        "Returns the user using the Graph API"
        if not self.accessToken():
            return None
        if not self.graph:
            self.graph = GraphAPI((self.accessToken()))
        try:
            user = self.graph.get_object("me")
            return dict(first_name = user['first_name'],
                        last_name = user['last_name'],
                        username = user['id'])
        except GraphAPIError:
            self.session.token = None
            self.graph = None
            return None
# use the above class to build a new login form
auth.settings.login_form=FaceBookAccount(globals())

LinkedIn

Previamente hemos discutido la integración con Janrain (que tiene soporte LinkedIn) y esa es la manera más fácil de usar OpenID. Sin embargo algunas veces usted podría no querer depender en un servicio de terceros o quiere acceder a LinkedIn directamente para obtener más información de la que provee Janrain.

He aquí un ejemplo:

1
2
from gluon.contrib.login_methods.linkedin_account import LinkedInAccount
auth.settings.login_form=LinkedInAccount(request,KEY,SECRET,RETURN_URL)

LinkedInAccount requiere que el módulo “python-linkedin” sea instalado separadamente.

Múltiples Formularios de Inicio de Sesión

Algunos métodos de inicio de sesión modifican el formulario de inicio login_form, otros métodos no hacen esto. Cuando si lo hacen, es posible que no puedan coexistir. Sin embargo algunos si coexisten proporcionando múltiples formularios de inicio de sesión en la misma página. Web2py provee una manera de hacer esto. Aquí vemos un ejemplo mezclando inicio de sesión normal (auth) con inicio de sesión RPX (Janrain.com):

1
2
3
4
from gluon.contrib.login_methods.extended_login_form import ExtendedLoginForm
other_form = RPXAccount(request, api_key='...', domain='...', url='...')
auth.settings.login_form = ExtendedLoginForm(request,
    auth, other_form, signals=['token'])

Si las señales son establecidas y un parámetro en solicitud coincide con alguna señal, retornará en cambio el llamado de other_form.login_form. other_form puede gestionar algunas situaciones particulares, por ejemplo, múltiples pasos de un inicio de sesión OpenID dentro de other_form.login_form.

De otro modo entregará el formulario de inicio de sesión normal junto con el otro formulario other_form.

Auth y Mail

Por defecto la verificación de correo está deshabilitada. Para habilitar el correo, agregue las siguientes líneas en el modelo donde auth es definido:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
from gluon.tools import Mail
mail = Mail(globals())
mail.settings.server = 'smtp.example.com:25'
mail.settings.sender = 'you@example.com'
mail.settings.login = 'username:password'
auth.settings.mailer = mail
auth.settings.registration_requires_verification = False
auth.settings.registration_requires_approval = False
auth.settings.reset_password_requires_verification = True
auth.messages.verify_email = 'Click on the link http://' + \
    request.env.http_host + \
    URL(r=request,c='default',f='user',args=['verify_email']) + \
    '/%(key)s to verify your email'
auth.messages.reset_password = 'Click on the link http://' + \
    request.env.http_host + \
    URL(r=request,c='default',f='user',args=['reset_password']) + \
    '/%(key)s to reset your password'

Necesita reemplazar mail.settings con los parámetros acordes a su servidor SMTP. Establezca mail.settings.login=False si el servidor SMTP no requiere autenticación.

Usted también necesita reemplazar la cadena

1
'Click on the link ...'

en auth.messages.verify_email con el URL completo de la acción verify_email apropiado. Ésto es necesario porque web2py puede estar instalado detrás de un proxy, y no puede determinar sus propios URLs públicos con total certeza.

Una vez que mail está definido, también puede ser usado para enviar correo explícitamente por medio de

1
2
3
mail.send(to=['somebody@example.com'],
          subject='hello',
          message='hi there')

Mail retorna True si tiene éxito al enviar el correo, y retorna Falso en caso contrario.

Depuración del Correo

Con propósitos de depuración usted puede establecer

1
mail.settings.server = 'logging'

Y los correos no serán enviados sino que en cambio serán registrados en la cónsola.

Correo desde el Motor de Aplicaciones de Google

Para enviar correos desde el una cuenta en el Motor de Aplicaciones de Google:

1
mail.settings.server = 'gae'

Al momento de escribir este libro, web2py no soporta correos adjuntos y encriptados en el Motor de Aplicaciones de Google.

Más ejemplos de Correos

Correo de texto simple

1
2
3
mail.send('you@example.com',
  'Message subject',
  'Plain text body of the message')

Correos HTML

mail.send('you@example.com',
  'Message subject',
  <html>html body</html>)

Si el cuerpo del correo comienza con <html> y termina con </html> será enviado como correo HTML.

Combinando Texto y correos HTML

El mensaje de correo puede ser una tupla (texto, html):

mail.send('you@example.com',
  'Message subject',
  ('Plain text body', <html>html body</html>))

CC y correo BCC

1
2
3
4
5
mail.send('you@example.com',
  'Message subject',
  'Plain text body',
  cc=['other1@example.com', 'other2@example.com'],
  bcc=['other3@example.com', 'other4@example.com'])

Adjuntos

mail.send('you@example.com',
  'Message subject',
  '<html><img src="cid:photo" /></html>',
  attachments = Mail.Attachment('/path/to/photo.jpg' content_id='photo'))

Múltiples Adjuntos

mail.send('you@example.com,
  'Message subject',
  'Message body',
  attachments = [Mail.Attachment('/path/to/fist.file'),
                 Mail.Attachment('/path/to/second.file')])

X509 y encriptado PGP

Es posible enviar correos encriptados X509 (SMIME) usando los siguientes ajustes:

1
2
3
4
5
6
7
mail.settings.cipher_type = 'x509'
mail.settings.sign = True
mail.settings.sign_passphrase = 'your passphrase'
mail.settings.encrypt = True
mail.settings.x509_sign_keyfile = 'filename.key'
mail.settings.x509_sign_certfile = 'filename.cert'
mail.settings.x509_crypt_certfiles = 'filename.cert'

Es posible enviar correos encriptados PGP usando los siguientes ajustes:

1
2
3
4
5
from gpgme import pgp
mail.settings.cipher_type = 'gpg'
mail.settings.sign = True
mail.settings.sign_passphrase = 'your passphrase'
mail.settings.encrypt = True

Lo último requiere el paquete python-pyme.

Autorización

Una vez que un nuevo usuario es registrado, un nuevo grupo es creado para contener al usuario. El rol del nuevo usuario es convencionalmente “user_[id]” donde [id] es el id del usuario creado. La creación del grupo puede ser deshabilitada con

1
auth.settings.create_user_groups = False

aunque no recomendamos hacerlo.

Los usuarios tienen membresía en los grupos. Cada grupo es identificado por un nombre/rol. Los grupos tienen permisos. Los usuarios tienen permisos debido a los grupos a los cuales pertenecen.

Usted puede crear grupos, dar membresía y permisos por medio de appadmin o de manera programática usando los siguientes métodos:

1
auth.add_group('role', 'description')

Retorna el id del nuevo grupo creado.

1
auth.del_group(group_id)

Borra el grupo con group_id.

1
auth.del_group(auth.id_group('user_7'))

Borra el grupo con rol “user_7”, es decir, el grupo asociado únicamente al usuario número 7.

1
auth.user_group(user_id)

Retorna el id del grupo únicamente asociado al usuario identificado por user_id.

auth.add_membership(group_id, user_id)

Le da a user_id membresía del grupo group_id. Si el user_id no es especificado, entonces web2py asume el usuario que está en sesión.

1
auth.del_membership(group_id, user_id)

Revoca la membresía de user_id en el grupo group_id. Si el user_id no es especificado, entonces web2py asume el usuario que está en sesión.

1
auth.has_membership(group_id, user_id, role)

Verifica si user_id tiene membresía en el grupo group_id o en el grupo con el rol especificado. Sólo group_id o role debe ser pasado a la función, no ambos. Si el user_id no es especificado, entonces web2py asume el del usuario que está en sesión.

1
auth.add_permission(group_id, 'name', 'object', record_id)

Da permiso “name” (definido por el usuario) en el objeto “object” (también definido por el usuario) a miembros del grupo group_id. Si “object” es un nombre de tabla, entonces el permiso puede referirse a toda la tabla (record_id==0) o a un registro en específico (record_id>0). Al dar permisos en tablas, es común usar un nombre de permiso en el conjunto (crear ‘create’, leer ‘read’, actualizar ‘update’, borrar ‘delete’, seleccionar ‘select’) ya que estos permisos son entendidos y pueden hacerse cumplir por CRUD.

1
auth.del_permission(group_id, 'name', 'object', record_id)

Revoca el permiso.

1
auth.has_permission('name', 'object', record_id, user_id)

Verifica si el usuario identificado por user_id tiene membresía en un grupo con el permiso requerido.

1
2
rows = db(accessible_query('read', db.mytable, user_id))\
    .select(db.mytable.ALL)

Retorna todas las filas de la tabla “mytable” en la cual el usuario user_id tiene permiso de lectura “read”. Si el usuario user_id no es especificado, entonces web2py asume el usuario que ha iniciado sesión. La consulta accesible_query(...) puede ser combinada con otras consultas para construid unas más complejas. accesible_query(...) es el único método Auth que requiere una unión JOIN, así que no funciona con el Motor de Aplicaciones de Google.

Asumiendo las siguientes definiciones:

1
2
3
4
5
6
>>> from gluon.tools import Auth
>>> auth = Auth(globals(), db)
>>> auth.define_tables()
>>> secrets = db.define_table('document', Field('body'))
>>> james_bond = db.auth_user.insert(first_name='James',
                                     last_name='Bond')

He aquí un ejemplo:

1
2
3
4
5
6
7
8
>>> doc_id = db.document.insert(body = 'top secret')
>>> agents = auth.add_group(role = 'Secret Agent')
>>> auth.add_membership(agents, james_bond)
>>> auth.add_permission(agents, 'read', secrets)
>>> print auth.has_permission('read', secrets, doc_id, james_bond)
True
>>> print auth.has_permission('update', secrets, doc_id, james_bond)
False

Decoradores

La manera más común de chequear los permisos no es con llamados explícitos a los métodos listados anteriormente, sino decorando las funciones de manera que los permisos sean chequeados relativos al visitante que ha iniciado sesión. Aquí hay algunos ejemplos:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def function_one():
    return 'this is a public function'
@auth.requires_login()
def function_two():
    return 'this requires login'
@auth.requires_membership(agents)
def function_three():
    return 'you are a secret agent'
@auth.requires_permission('read', secrets)
def function_four():
    return 'you can read secret documents'
@auth.requires_permission('delete', 'any file')
def function_five():
    import os
    for file in os.listdir('./'):
        os.unlink(file)
    return 'all files deleted'
@auth.requires(auth.user_id==1 or request.client=='127.0.0.1')
def function_six():
    return 'you can read secret documents'
@auth.requires_permission('add', 'number')
def add(a, b):
    return a + b
def function_six():
    return add(3, 4)

Note que el acceso a todas las funciones a excepto de la primera es restringido basado en permisos que el visitante pueda o no tener.

Si el visitante no ha iniciado sesión, entonces los permisos no pueden ser verificados; el visitante es redireccionado a la página de inicio de sesión y luego direccionado de vuelta a la página que requirió los permisos.

Si el visitante no tiene permisos para accesar a una función dada, entonces es redirigido al URL definido por

1
2
auth.settings.on_failed_authorization = \
    URL('user',args='on_failed_authorization')

Usted puede cambiar esta variable y redirigir al usuario a cualquier otro lugar.

Combinando requerimientos

En ocasiones puede ser necesario combinar requerimientos. Ésto puede ser hecho por medio del decorador genérico requires el cual toma un solo argumento, una condición verdadero o falso. Por ejemplo, para dar acceso a los agentes, pero sólo los martes:

1
2
3
4
@auth.requires(auth.has_membership(group_id=agents) \
               and request.now.weekday()==1)
def function_seven():
    return 'Hello agent, it must be Tuesday!'

O de manera equivalente:

1
2
3
4
@auth.requires(auth.has_membership(role='Secret Agent') \
               and request.now.weekday()==1)
def function_seven():
    return 'Hello agent, it must be Tuesday!'

Autorización y CRUD

Usar decoradores y/o verificadores explícitos proporciona una manera de implementar control de acceso.

Otra manera de implementar control de acceso es siempre usar CRUD (en oposición a SQLFORM) para accesar la base de datos y para pedir a CRUD que haga cumplir el control de acceso a las tablas y registros de la base de datos. Ésto es hecho vinculando Auth y CRUD con la siguiente sentencia:

1
crud.settings.auth = auth

Ésto evitará que el visitante acceda al alguna de las funciones CRUD a menos que el visitante haya iniciado sesión y tenga acceso explícito. Por ejemplo, para permitir que un visitante envíe comentarios, pero que sólo pueda actualizar los propios (asumiendo que crud, auth y db.comment están definidos):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
def give_create_permission(form):
    group_id = auth.id_group('user_%s' % auth.user.id)
    auth.add_permission(group_id, 'read', db.comment)
    auth.add_permission(group_id, 'create', db.comment)
    auth.add_permission(group_id, 'select', db.comment)
def give_update_permission(form):
    comment_id = form.vars.id
    group_id = auth.id_group('user_%s' % auth.user.id)
    auth.add_permission(group_id, 'update', db.comment, comment_id)
    auth.add_permission(group_id, 'delete', db.comment, comment_id)
auth.settings.register_onaccept = give_create_permission
crud.settings.auth = auth
def post_comment():
   form = crud.create(db.comment, onaccept=give_update_permission)
   comments = db(db.comment.id>0).select()
   return dict(form=form, comments=comments)
def update_comment():
   form = crud.update(db.comment, request.args(0))
   return dict(form=form)

Usted también puede seleccionar registros específicos (aquellos a los cuales tiene acceso de lectura):

1
2
3
4
5
def post_comment():
   form = crud.create(db.comment, onaccept=give_update_permission)
   query = auth.accessible_query('read', db.comment, auth.user.id)
   comments = db(query).select(db.comment.ALL)
   return dict(form=form, comments=comments)

Los nombres de permisos que se hacen cumplir por:

1
crud.settings.auth = auth

Son lectura “read”, crear “create”, actualizar “update”, borrar “delete”, seleccionar “select”, suplantar “impersonate”.

Autorización y Descargas

El uso de decoradores y el uso de crud.settings.auth no hace cumplir las autorizaciones en archivos descargados por la función de descarga usual

1
def download(): return response.download(request, db)

Si uno desea lograr esto, se debe declarar explícitamente cuales campos cargados contienen archivos que necesitan control de acceso para descarga. Por ejemplo:

db.define_table('dog',
   Field('small_image', 'upload')
   Field('large_image', 'upload'))
db.dog.large_image.authorization = lambda record: \
   auth.is_logged_in() and \
   auth.has_permission('read', db.dog, record.id, auth.user.id)

El atributo authorization de archivos cargados puede ser None (predeterminado) o una función que decide si el usuario que ha iniciado sesión tiene permiso para leer el registro. En este ejemplo, no hay restricción para descargar las imágenes vinculadas por el campo “small_image”, pero requerimos control de acceso en las imágenes vinculadas por el campo “large_image”.

Control de Acceso y Autenticación Básica

En ocasiones puede ser necesario exponer acciones que tienen decoradores que requieren control de acceso como servicios, es decir, llamarlos desde un programa o script y aún poder usar autenticación para chequear autorización.

Auth habilita el inicio de sesión por medio de autenticación básica:

1
auth.settings.allow_basic_login = False

Con este conjunto, una acción como

1
2
3
4
@auth.requires_login()
def give_me_time():
    import time
    return time.ctime()

Puede ser llamada, por ejemplo, desde un comando en la línea de comandos:

wget --user=[username] --password=[password]
    http://.../[app]/[controller]/give_me_time

Inicio de sesión básico es a menudo la única opción para servicios (descritos en el siguiente capítulo), pero es deshabilitado por defecto.

Ajustes y Mensajes

Aquí está una lista con todos los parámetros que pueden ser personalizados para Auth

Los siguiente debe apuntar al objeto gluon.tools.Mail para permitir que auth envíe correos:

1
auth.settings.mailer = None

Lo siguiente debe ser el nombre del controlador que definió la acción user:

1
auth.settings.controller = 'default'

El siguiente es un ajuste muy importante:

1
auth.settings.hmac_key = None

Debe ser establecido en algo como “sha512:a-pass-phrase” y será pasado al validador CRYPT para el campo “password” de la tabla auth_user. Será el algoritmo y frase-para-clave usado para hacer el hash de la clave.

Para deshabilitar una acción agregue su nombre a esta lista:

1
auth.settings.actions_disabled = []

Por ejemplo:

1
auth.settings.actions_disabled.append('register')

deshabilitará el registro.

Si quiere recibir un correo para verificar el registro establezca esto a True:

1
auth.settings.registration_requires_verification = False

Si a los nuevos solicitantes de registro no se les debe permitir el inicio de sesión antes de la aprobación del registro establezca esto a True:

1
auth.settings.registration_requires_approval = False

La aprobación consiste en ajustar registration_key==’‘ por medio de la aplicación de administración o de manera programática.

Si usted no quiere un nuevo grupo para cada nuevo usuario establezca los siguiente a False:

1
auth.settings.create_user_groups = True

El siguiente ajuste determina métodos y formularios de inicio de sesión alternativos, como fué discutido previamente:

1
2
auth.settings.login_methods = [auth]
auth.settings.login_form = auth

¿Quiere permitir inicio de sesión básico?

1
auth.settings.allows_basic_login = False

El siguiente es el URL de la acción login:

1
auth.settings.login_url = URL('user', args='login')

Si el usuario trató de acceder a la página de registro pero ya ha iniciado sesión, será redirigido al siguiente URL:

1
auth.settings.logged_url = URL('user', args='profile')

Éste debe apuntar al URL de la acción de descarga, en caso de que el perfil contenga imágenes:

1
auth.settings.download_url = URL('download')

Éstos deben apuntar al URL al cual quiere redireccionar a sus usuarios después de las posibles acciones Auth (en caso de que no haya referentes):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
auth.settings.login_next = URL('index')
auth.settings.logout_next = URL('index')
auth.settings.profile_next = URL('index')
auth.settings.register_next = URL('user', args='login')
auth.settings.retrieve_username_next = URL('index')
auth.settings.retrieve_password_next = URL('index')
auth.settings.change_password_next = URL('index')
auth.settings.request_reset_password_next = URL('user', args='login')
auth.settings.reset_password_next = URL('user', args='login')
auth.settings.verify_email_next = URL('user', args='login')

Éste es el URL al cual quiere ser redireccionado al fallar la autorización:

1
2
auth.settings.on_failed_authorization = \
    URL('user',args='on_failed_authorization')

Éstas son listas de llamadas de retorno que deben ser ejecutadas después de la validación de los formularios para cada acción correspondiente antes de cualquier transacción con la base de datos:

1
2
3
4
5
auth.settings.login_onvalidation = []
auth.settings.register_onvalidation = []
auth.settings.profile_onvalidation = []
auth.settings.retrieve_password_onvalidation = []
auth.settings.reset_password_onvalidation = []

Cada llamada de retorno debe ser una función que toma el objeto form y que puede modificar los atributos de dicho objeto antes de que se realice transacciones de entrada o salida en la base de datos.

Éstas son listas de llamadas de retorno que deben ser ejecutadas después de que se realizan las transacciones con la base de datos y antes de la redirección:

1
2
3
4
auth.settings.login_onaccept = []
auth.settings.register_onaccept = []
auth.settings.profile_onaccept = []
auth.settings.verify_email_onaccept = []

He aquí un ejemplo:

auth.settings.register_onaccept.append(lambda form:\
   mail.send(to='you@example.com',subject='new user',
              message="new user email is %s'%form.vars.email))

Usted puede habilitar captcha para cualquier acción auth:

auth.settings.captcha = None auth.settings.login_captcha = None auth.settings.register_captcha = None auth.settings.retrieve_username_captcha = None auth.settings.retrieve_password_captcha = None

Si el ajuste .captcha apunta a gluon.tools.Recaptcha, todos los formularios para los cuales la opción correspondiente (como .login_captcha) está establecida en None, tendrán un captcha, mientras que esos para los cuales la opción correspondiente esté establecida en False no lo tendrán. Si en cambio .captcha está establecido en None, sólo aquellos formularios cuya opción correspondiente esté establecida al objeto gluon.tools.Recaptcha tendrán captcha mientras que las otras no.

Éste es el tiempo de expiración de sesión iniciada:

1
auth.settings.expiration = 3600  # seconds

Usted puede cambiar el nombre del campo de clave (en Firebird por ejemplo “password” es una palabra clave y no puede ser usada para nombrar un campo):

1
auth.settings.password_field = 'password'

Normalmente el formulario de inicio de sesión trata de validar el correo. Ésto puede ser deshabilitado cambiando este ajuste:

1
auth.settings.login_email_validate = True

¿Quiere usted mostrar el registro id en la página de edición del perfil?

1
auth.settings.showid = False

Para formularios personalizados usted puede querer deshabilitar notificaciones de errores automáticas en los formularios:

1
auth.settings.hideerror = False

También puede cambiar el estilo de los formularios personalizados:

1
auth.settings.formstyle = 'table3cols'

(puede ser “table2cols”, “divs” y “ul”)

Por defecto el formulario de inicio de sesión da la opción de extender la sesión por medio de la opción recuerdame “remember me”. El tiempo de expiración puede ser cambio o la opción deshabilitada por medio de los siguientes ajustes:

1
2
auth.settings.long_expiration = 3600*30 # one month
auth.settings.remember_me_form = True

Usted también puede personalizar los siguientes mensajes cuyo uso y contexto deben ser obvios:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
auth.messages.submit_button = 'Submit'
auth.messages.verify_password = 'Verify Password'
auth.messages.delete_label = 'Check to delete:'
auth.messages.function_disabled = 'Function disabled'
auth.messages.access_denied = 'Insufficient privileges'
auth.messages.registration_verifying = 'Registration needs verification'
auth.messages.registration_pending = 'Registration is pending approval'
auth.messages.login_disabled = 'Login disabled by administrator'
auth.messages.logged_in = 'Logged in'
auth.messages.email_sent = 'Email sent'
auth.messages.unable_to_send_email = 'Unable to send email'
auth.messages.email_verified = 'Email verified'
auth.messages.logged_out = 'Logged out'
auth.messages.registration_successful = 'Registration successful'
auth.messages.invalid_email = 'Invalid email'
auth.messages.unable_send_email = 'Unable to send email'
auth.messages.invalid_login = 'Invalid login'
auth.messages.invalid_user = 'Invalid user'
auth.messages.is_empty = "Cannot be empty"
auth.messages.mismatched_password = "Password fields don't match"
auth.messages.verify_email = ...
auth.messages.verify_email_subject = 'Password verify'
auth.messages.username_sent = 'Your username was emailed to you'
auth.messages.new_password_sent = 'A new password was emailed to you'
auth.messages.password_changed = 'Password changed'
auth.messages.retrieve_username = 'Your username is: %(username)s'
auth.messages.retrieve_username_subject = 'Username retrieve'
auth.messages.retrieve_password = 'Your password is: %(password)s'
auth.messages.retrieve_password_subject = 'Password retrieve'
auth.messages.reset_password = ...
auth.messages.reset_password_subject = 'Password reset'
auth.messages.invalid_reset_password = 'Invalid reset password'
auth.messages.profile_updated = 'Profile updated'
auth.messages.new_password = 'New password'
auth.messages.old_password = 'Old password'
auth.messages.group_description = \
    'Group uniquely assigned to user %(id)s'
auth.messages.register_log = 'User %(id)s Registered'
auth.messages.login_log = 'User %(id)s Logged-in'
auth.messages.logout_log = 'User %(id)s Logged-out'
auth.messages.profile_log = 'User %(id)s Profile updated'
auth.messages.verify_email_log = 'User %(id)s Verification email sent'
auth.messages.retrieve_username_log = 'User %(id)s Username retrieved'
auth.messages.retrieve_password_log = 'User %(id)s Password retrieved'
auth.messages.reset_password_log = 'User %(id)s Password reset'
auth.messages.change_password_log = 'User %(id)s Password changed'
auth.messages.add_group_log = 'Group %(group_id)s created'
auth.messages.del_group_log = 'Group %(group_id)s deleted'
auth.messages.add_membership_log = None
auth.messages.del_membership_log = None
auth.messages.has_membership_log = None
auth.messages.add_permission_log = None
auth.messages.del_permission_log = None
auth.messages.has_permission_log = None
auth.messages.label_first_name = 'First name'
auth.messages.label_last_name = 'Last name'
auth.messages.label_username = 'Username'
auth.messages.label_email = 'E-mail'
auth.messages.label_password = 'Password'
auth.messages.label_registration_key = 'Registration key'
auth.messages.label_reset_password_key = 'Reset Password key'
auth.messages.label_registration_id = 'Registration identifier'
auth.messages.label_role = 'Role'
auth.messages.label_description = 'Description'
auth.messages.label_user_id = 'User ID'
auth.messages.label_group_id = 'Group ID'
auth.messages.label_name = 'Name'
auth.messages.label_table_name = 'Table name'
auth.messages.label_record_id = 'Record ID'
auth.messages.label_time_stamp = 'Timestamp'
auth.messages.label_client_ip = 'Client IP'
auth.messages.label_origin = 'Origin'
auth.messages.label_remember_me = "Remember me (for 30 days)"

Los registros de membresía add|del|has permiten el uso de “%(user_id)s” y “%(group_id)s”.

Los registros de permisología add|del|has permiten el uso de “%(user_id)s”, “%(name)s”, “%(table_name)s” y “%(record_id)s”.

Servicio Central de Autenticación

web2py proporciona soporte para autenticación y autorización por medio de dispositivos. Aquí discutimos el dispositivo cas para el Servicio Central de Autorización (CAS de sus siglas en inglés). Note que al momento de escribir el libro, CAS es distinto y no trabaja con Auth. Ésto cambiará en el futuro.

CAS es un protocolo abierto para autenticación distribuida y trabaja de la siguiente manera: Cuando un visitante arriba a nuestro sitio web, nuestra aplicación chequea en la sesión si el usuario ya ha sido autenticado (por ejemplo por medio del objeto session.token). Si el usuario no está autenticado, el controlador redirecciona al visitante desde el dispositivo CAS, donde el usuario puede iniciar sesión, registrarse, y gestionar sus credenciales (nombre, correo y clave). Si el usuario se registra, recibe un correo, y el registro no se completa hasta que responda el correo. Una vez que el usuario se ha registrado exitosamente y ha iniciado sesión, el dispositivo CAS redirecciona el usuario a nuestra aplicación junto con una llave. Nuestra aplicación usa la llave para obtener las credenciales del usuario por medio de una solicitud HTTP en segundo plano al servidor CAS.

Usando este mecanismo, múltiples acciones puede usar el inicio de sesión único por medio de un sólo servidor CAS. El servidor que provee autenticación es llamado el proveedor de servicio. Aplicaciones que busquen autenticar a los visitantes son llamadas consumidores de servicio.

CAS es similar a OpenID, con una diferencia principal. En el caso de OpenID, el visitante elige el proveedor de servicio. En CAS, nuestra aplicación es la que lleva a cabo esta elección, haciendo que CAS sea más seguro.

Usted puede ejecutar sólo el consumidor, sólo el proveedor, o ambos (en una o varias aplicaciones).

Para ejecutar CAS como consumidor usted debe descargar el archivo https://www.web2py.com/cas/static/cas.py

y almacenarlo como un archivo de modelo llamado “cas.py”. Luego usted debe editar el controlador que necesita autenticación (por ejemplo “default.py”) y, el principio, agregar el siguiente código:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
CAS.login_url='https://www.web2py.com/cas/cas/login'
CAS.check_url='https://www.web2py.com/cas/cas/check'
CAS.logout_url='https://www.web2py.com/cas/cas/logout'
CAS.my_url='http://127.0.0.1:8000/myapp/default/login'
if not session.token and not request.function=='login':
    redirect(URL('login'))
def login():
    session.token=CAS.login(request)
    id,email,name=session.token
    return dict()
def logout():
    session.token=None
    CAS.logout()

Usted debe editar los atributos del objeto CAS mostrado arriba. Por defecto ellos apuntan al proveedor CAS que se ejecuta en “https://mdp.cti.depaul.edu”. Nosotros proporcionamos éste servicio principalmente para fines de prueba. El CAS.my_url debe ser el URL completo de la acción de inicio de sesión definida en su aplicación y mostrada en el código. El proveedor CAS necesita redireccionar su buscador a esta acción.

Nuestro proveedor CAS retorna un token que contiene una tupla (id, email, name), donde id es el registro único id del visitante (como fué asignado por la base de datos del proveedor), email es la dirección de correo del visitante (como fué declarada por el visitante al proveedor y luego verificada por el proveedor), y name es el nombre del visitante (como fué elegido por el visitante y no hay garantías de que éste sea un nombre real).

Si usted visita el url local:

/myapp/default/login

Usted será redireccionado a la página de inicio de sesión de CAS: https://mdp.cti.depaul.edu/cas/cas/login

que luce así:

cas

Usted también puede usar servicios de terceros, pero puede necesitar editar la línea 10 arriba, debido a que proveedores CAS diferentes pueden retornar token que contengan valores diferentes. Para más detalles chequee la documentación del servicio CAS al cual usted necesita acceder. La mayoría de los servicios solo retornan (id, nombre de usuario).

Luego de un inicio de sesión exitoso, usted es redirigido a la acción de inicio de sesión local. La vista de la acción de inicio de sesión local es ejecuta solo después de un inicio de sesión CAS exitoso.

Usted puede descargar el dispositivo proveedor CAS desde ref.(33) y ejecutarlo usted mismo. Si usted elige hacerlo, debe también editar las primeras líneas del modelo “email.py” en el dispositivo, de manera que apunte a su servidor SMTP.

Usted también puede combinar los archivos del dispositivo proveedor CAS con los archivos de su aplicación (modelos bajo models, etc.) siempre que no haya conflictos con los nombres de los archivos.