Chapter 9: Contrôle d'accès
Contrôle d'accès
web2py inclut un mécanisme puissant et personnalisable de Role Based Access Control (RBAC).
Voici une définition de Wikipedia (traduit) :
"Role-Based Access Control (RBAC) est une approche pour restreindre l'accès au système à des utilisateurs autorisés. C'est une nouvelle approche alternative au Mandatory Access Control (MAC) et Discretionary Access Control (DAC). RBAC est parfois référé comme une sécurité basée sur les rôles.
RBAC est une technologie de contrôle d'accès de pratique neutre et flexible suffisamment puissante pour simuler DAC et MAC. Inversement, MAC peut simuler RBAC si le graphique de rôle est restreint à un arbre plutôt qu'à un ensemble partiellement ordonné.
Avant le développement de RBAC, MAC et DAC étaient considérés comme étant les seuls modèles connus pour le contrôle d'accès : si un modèle n'était pas MAC, il était considéré comme étant un modèle DAC, et vice versa. La recherche à la fin des années 1990 a démontré que RBAC n'était dans aucune catégorie.
Au sein d'une organisation, les rôles sont créés pour des fonctions variées. Les permissions pour effectuer certaines opérations sont assignées à des rôles spécifiques. Les membres de l'équipe (ou tout autre utilisateur du système) se voient assignés à des rôles particuliers, et à travers ces assignements ils acquièrent des droits pour effectuer certaines fonctions systèmes particulières. Contrairement au Context-Based Access Control (CBAC), RBAC ne regarde pas le contexte du message (telle que la source de connexion).
Tant que les utilisateurs n'ont pas de permissions directes, mais en ont seulement acquis via leur rôle (ou rôles), la gestion des droits utilisateurs individuels revient simplement à bien associer les rôles à l'utilisateur ; cela simplifie les opérations habituelles, telle que l'ajout d'un utilisateur, ou le changement de département d'un utilisateur.
RBAC diffère des Access Control Lists (ACLs) utilisés dans les DAC traditionnels du fait qu'il assigne les permissions à des opérations spécifiques ce qui signifie dans l'organisation, plutôt qu'à des objets de données de plus bas niveau. Par exemple, un ACL pourrait être utilisé pour autoriser ou interdire les accès en écriture à un système de fichier particulier, mais ne dicterait pas la façon dont ce fichier pourrait être changé."
La classe web2py qui implémente RBAC est appelée Auth.
Auth a besoin (et définit) les tables suivantes :
auth_user
stocke les nom, email, mot de passe et statut (en attente d'enregistrement, accepté, bloqué) des utilisateursauth_group
stocke les groupes ou rôles pour les utilisateurs dans une structure many-to-many. Par défaut, chaque utilisateur est dans son propre groupe, mais un utilisateur peut être dans plusieurs groupes, et chaque groupe peut contenir plusieurs utilisateurs. Un groupe est identifié par un rôle et une description.auth_membership
lie les utilisateurs et les groupes dans une structure many-to-many.auth_permission
lie les groupes et permissions. Une permission est identifiée par un nom, et optionnellement une table et un enregistrement. Par exemple, les mbmbres d'un certain groupe peuvent avoir des permissions "update" sur un enregistrement spécifique ou une table spécifique.auth_event
log les changements dans les autres tables et les accès validés via CRUD aux objets contrôlés par RBAC.auth_cas
est utilisé pour le Central Authentication Service (CAS). Toute application web2py est un fournisseur CAS et peut éventuellement être un client CAS.
Le schéma est reproduit de manière graphique dans l'image ci-après :
En principe, il n'y a pas de restriction sur les noms des rôles et les noms des permissions ; le développeur peut les créer pour corriger les rôles et permissions dans l'organisation. Une fois qu'ils ont été créés, web2py fournit une API pour vérifier si un utilisateur est connecté, si un utilisateur est membre d'un groupe donné, et/ou si l'utilisateur est un membre de n'importe quel groupe qui a une permission donnée requise.
web2py fournit également les décorateurs pour restreindre l'accès à n'importe quelle fonction basée sur le login, l'appartenance et les permissions.
web2py comprend également quelques permissions spécifiques, i.e., celles qui ont un nom qui correspond aux méthods CRUD (create, read, update, delete) et peut les forcer automatiquement sans le besoin d'utiliser des décorateurs.
Dans ce chapitre, nous allons discuter des différentes parties de RBAC une par une.
Authentification
Afin d'utiliser RBAC, les utilisateurs ont besoin d'être identifiés. Cela signifie qu'ils ont besoin de s'enregistrer (ou d'être enregistré) et se connecter.
Auth fournit de mutiples méthodes de connexion. Celle par défaut consiste en l'identification des utilisateurs basée sur la table locale auth_user
. De façon alternative, il est également possible d'identifier des utilisateurs avec des systèmes d'authentification tiers et des fournisseurs de Single Sign On tels que Google, PAM, LDAP, Facebook, LinkedIn, Dropbox, OpenID, OAuth, etc..
Pour commencer à utiliser Auth
, vous avez au moins besoin de ce code dans un fichier modèle, qui est également fourni avec l'application web2py "welcome" et suppose un objet de connexion db
:
from gluon.tools import Auth
auth = Auth(db)
auth.define_tables(username=False,signature=False)
Par défaut, web2py utilise un email pour le login. Si au lieu de cela, vous voulez utiliser un nom d'utilisateur, définissez auth.define_tables(username=True)
Définir signature=True
ajoute un tampon utilisateur et date à la table d'authentification, pour suivre les modifications.
Auth a un argument optionnel secure=True
, qui forcera les pages authentifiées à fonctionner sur HTTPS.
Par défaut, Auth protège les connexions contre les Cross-Site Request Forgeries (CSRF). C'est en fait un mécanisme fourni par la protection standard CSRF web2py dès lors que des formulaires sont générés dans une session. Cependant, dans certains cas, la surcharge de création d'une session pour une requête login/passord et les tentatives de reset peuvent être indésirables. Les attaques DOS sont théoriquement possibles. La protection CSRF peut être désactivée pour les formulaires Auth (depuis la version 2.6) :
Auth = Auth(..., csrf_prevention = False)
Notez que faire cela pour purement éviter la surcharge de session sur un site occupé n'est pas recommandé car cela introduit une faille de sécurité. Au lieu de cela, voyez le chapitre sur le déploiement pour des conseils sur la réduction des overheads de session.
Le champ
password
de la tabledb.auth_user
est dotée par défaut d'un validateurCRYPT
, qui nécessite unhmac_key
. Sur les applications web2py legacy, vous pouvez voir un argument complémentaire passé au constructeur Auth :hmac_key = Auth.get_or_create_key()
. Ce dernier est une fonction qui lit la clé HMAC depuis un fichier "private/auth.key" au sein du dossier de l'application. Si le fichier n'existe pas, il créé unhmac_key
aléatoire. Si de multiples applications partagent la même base de données d'authentification, assurez-vous qu'ils utilisent le mêmehmac_key
. Il n'est plus nécessaire pour les nouvelles applications puisque les mots de passe sont salés avec un sel aléatoire.
Si plusieurs applications partagent la même base de données auth, vous pouvez vouloir désactiver les migrations : auth?define_tables(migrate=False)
.
Pour exposer Auth, vous avez également besoin de la fonction suivante dans un contrôleur (par exemple dans "default.py") :
def user(): return dict(form=auth())
L'objet
auth
et l'actionuser
sont déjà définis dans l'application de référence.
web2py inclut également une vue exemple "welcome/views/default/user.html" pour rendre cette fonction qui ressemble à :
{{extend 'layout.html'}}
<h2>{{=T( request.args(0).replace('_',' ').capitalize() )}}</h2>
<div id="web2py_user_form">
{{=form}}
{{if request.args(0)=='login':}}
{{if not 'register' in auth.settings.actions_disabled:}}
<br/><a href="{{=URL(args='register')}}">register</a>
{{pass}}
{{if not 'request_reset_password' in auth.settings.actions_disabled:}}
<br/>
<a href="{{=URL(args='request_reset_password')}}">lost password</a>
{{pass}}
{{pass}}
</div>
Notez que cette fonction affiche simplement un form
et donc peut être personnalisé en utilisant la syntax personnalisée de formulaire. La seule limitation et que le formulaire affiché par form=auth()
dépend de request.args(0)
; donc, si vous remplacez le formulaire d'authentification par défaut auth()
avec un formulaire de connexion personnalisé, vous pouvez avoir besoin d'une déclaration if
comme celle dans la vue :
{{if request.args(0)=='login':}}...custom login form...{{pass}}
Le contrôleur ci-dessus expose de multiples actions :
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 permet aux utilisateurs de s'enregistrer. C'est intégré avec un mécanisme de CAPTCHA, bien qu'il soit désactivé par défaut. Il est également intégré avec un calculateur d'entropie côté client défini dans "web2py.js". Le calculateur indique la force du nouveau mot de passe. Vous pouvez utiliser le validateur
IS_STRONG
pour empêcher web2py d'accepter des mots de passe faibles. - login permet aux utilisateurs qui sont enregistrés de se connecter (si l'enregistrement est vérifié ou ne nécessite pas de vérification, s'il a été approuvé ou ne nécessite pas d'approbation, et s'il n'a pas été bloqué).
- logout fait ce qu'il est supposé faire mais également, comme les autres méthodes, log les événements et peut être utilisé pour déclencher d'autres événements.
- profile permet aux utilisateurs d'éditer leur profil, i.e. le contenu de la table
auth_user
. Notez que cette table n'a pas de structure fixée et peut être personnalisée. - change_password permet aux utilisateurs de changer leur mot de passe de manière sécurisée.
- verify_email. Si la vérification d'email est activée, alors les visiteurs, lors de l'enregistrement, reçoivent un email avec un lien pour vérifier leur information d'email. Le lien pointe vers cette action.
- retrieve_username. Par défaut, Auth utilise l'email et le mot de passe pour se connecter, mais peut, optionnellement, utiliser le nom d'utilisateur au lieu de l'email. Dans ce cas, si un utilisateur oublie son nom d'utilisateur, la méthode
retrieve_username
lui permet de taper l'adresse email et de retrouver le nom d'utilisateur par l'email. - request_reset_password. Permet aux utilisateurs qui oublient leur mot de passe de faire une requête pour un nouveau mot de passe. Ils obtiendront un mail de confirmation pointant sur reset_password.
- impersonate permet à un utilisateur de "se faire passer pour" un autre utilisateur. C'est important pour des raisons de debug et de support.
request.args[0]
est l'id de l'utilisateur qui doit être imité. C'est uniquement permis si l'utilisateur connectéhas_permission('impersonate', db.auth_user, user_id)
. Vous pouvez utiliserauth.is_impersonating()
pour vérifier si l'utilisateur courant se fait passer pour quelqu'un d'autre. - groups liste les groupes dont l'utilisateur courant est membre.
- not_authorized affiche un message d'erreur lorsque le visiteur essaie de faire quelque chose qu'il/elle n'a pas le droit de faire.
- navbar est un helper qui génère une barre avec les liens login/register/etc...
Logout, profile, change_password, impersonate, et groups ont besoin de la connexion.
Par défaut, elles sont toutes exposées, mais il est possible de restreindre l'accès à seulement certaines de ces actions.
Toutes ces méthodes ci-dessus peuvent être étendues ou remplacées en surchargeant Auth.
Toutes ces méthodes peuvent être utilisées dans des actions séparées. Par exemple :
def mylogin(): return dict(form=auth.login())
def myregister(): return dict(form=auth.register())
def myprofile(): return dict(form=auth.profile())
...
Pour restreindre l'accès aux fonctions aux visiteurs seulement connectés, décorez la fonction comme dans l'exemple suivant
@auth.requires_login()
def hello():
return dict(message='hello %(first_name)s' % auth.user)
Toute fonction peut être décorée, pas juste les actions exposées. Bien sûr, c'est encore un très simple exemple de contrôle d'accès. Des exemples plus complexex seront présentés plus tard.
auth.user_groups
.
auth.user
contient une copie des enregistrementsdb.auth_user
pour l'utilisateur courant connecté ouNone
sinon. Il y a aussiauth.user_id
qui est le même queauth.user.id
(i.e. l'id de l'utilisateur connecté courant) ouNone
. De même,auth.user_groups
contient un dictionnaire où chaque clé est l'id d'un groupe dont l'utilisateur courant connecté est membre, la valeur est le rôle du groupe correspondant.
Le décorateur auth.requires_login()
comme les autres décorateurs auth.requires_*
prennent un argument optionnel otherwise
. Il peut être défini en une chaîne où rediriger l'utilisateur si l'enregistrement échoue ou vers un objet appelable. Il est appelé si l'enregistrement échoue.
Restrictions sur l'enregistrement
Si vous voulez autoriser les visiteurs à s'enregistrer mais pas à se connecter tant que l'enregistrement n'a pas été approuvé par l'administrateur :
auth.settings.registration_requires_approval = True
Vous pouvez approuver un enregistrement via l'interface appamin. Regardez dans la table auth_user
. Les enregistrements en attente ont un champ registration_key
défini à "pending". Un enregistrement est approuvé lorsque ce champ est vide.
Via l'interface appadmin, vous pouvez aussi bloquer un utilisateur. Localisez l'utilisateur dans la table auth_user
et définissez la registration_key
à "blocked". Les utilisateurs "blocked" ne sont pas autorisés à se connecter. Notez que ceci empêchera un visiteur de se connecter mais ne forcera pas un utilisateur déjà connecté à se déconnecter. Le mot "disabled" peut être utilisé au lieu de "blocked" si préféré, avec exactement le même fonctionnement.
Vous pouvez aussi bloquer l'accès à la page d'enregistrement totalement avec cette déclaration :
auth.settings.actions_disabled.append('register')
Si vous voulez autoriser des gens à s'enregistrer et automatiquement se connecter après enregistrement mais toujours vouloir envoyer un email pour la vérification afin qu'ils ne puissent pas se reconnecter après s'être déconnecté, tant qu'ils n'ont pas complété les instructions envoyées par mail, vous pouvez le faire de cette manière :
auth.settings.registration_requires_verification = True
auth.settings.login_after_registration = True
Les autres méthods de Auth peuvent être restreintes de la même manière.
Intégration avec OpenID, Facebook, etc.
Vous pouvez utiliser le Role Base Access Control de web2py et authentifier avec d'autres services comme OpenID, Facebook, LinkedIn, Google, Dropbox, MySpace, Flickr, etc. Le moyen le plus simple est d'utiliser Janrain Engage (formellement RPX) (Janrain.com).
Dropbox est présenté comme un cas spécial dans le Chapitre 14 puisqu'il permet plus qu'une simple connexion, il fournit également des services de stockage pour les utilisateurs connectés.
Janrain Engage est un service qui fournit un middleware d'authentification. Vous pouvez vous enregistrer sur Janrain.com, enregistrez un domaine (le nom de votre application) et définir les URLs que vous utiliserez, et ils vous fourniront une clé API.
Maintenant éditez le modèle de votre application web2py et placez les lignes suivantes quelque part après la définition de l'objet auth
:
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://your-external-address/%s/default/user/login" % request.application)
La première ligne importe la nouvelle méthode de connexion, la seconde désactive l'enregistrement local et la troisième demande à web2py d'utilier la méthode de connexion RPX. Vous devez insérer votre propre api_key
fournie par Janrain.com, le domain que vous avez choisi lors de l'enregistrement l'url
externe de votre page d'identification. Pour obtenir la connexion à janrain.com, allez alors vers [Deployment][Application Settings]. Sur le côté droit, il y a "Application Info", la clé api est appelée "API Key (Secret)".
Le domaine est "Application Domain" sans "https://" et sans le slash à la fin ".rpxnow.com/" Par exemple : si vous vous êtes enregistré comme "secure.mywebsite.org", Janrain le transforme en en domaine d'application "https://secure-mywebsite.rpxnow.com".
Lorsqu'un nouvel utilisateur se connecte pour la première fois, web2py créé un nouvel enregistrement db.auth_user
associé à l'utilisateur. Il utilisera le champ registration_id
pour stocker l'id unique pour l'utilisateur. La plupart des méthodes d'authentification fourniront également un nom d'utilisateur, un prénom et un nom mais ce n'est pas garanti. Les champs fournis dépendent de la méthode délectionnée par l'utilisateur. Si le même utilisateur se connecte deux fois en utilisant deux mécanismes d'authentification (par exemple, une première fois avec OpenID et une fois avec Facebook), Janrain ne peut pas le reconnaître comme étant le même utilisateur et utilisera différents registration_id
.
Vous pouvez personnaliser le mapping entre les données fournies par Janrain et les données stockées dans db.auth_user
. Voici un exemple pour Facebook :
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"])
Les clés dans le dictionnaire sont les champs dans db.auth_user
et les valeurs sont les entrées de données dans l'objet de profil fourni par Janrain. Regardez la documentation en ligne de Janrain pour les détails.
Janrain conservera également des statistiques sur les connexions des utilisateurs.
Ce formulaire d'authenticiation est totalement intégré avec le Role Base Access Control de web2py et vous pouvez toujours créer des groupes, y insérer des membres, assigner des permissions, bloquer des utilisateurs, ...
Le service basique gratuit de Janrain permet jusqu'à 2500 utilisateurs uniques enregistrés de se connecter annuellement. Pour un besoin avec plus d'utilisateurs, vous aurez besoin de l'un des services payants tiers.
Si vous préférez ne pas utiliser Janrain et que vous voulez utiliser une méthode de connexion différente (LDAP, PAM, Google, OpenID, OAuth/Facebook, LinkedIn, etc.) vous pouvez également. L'API pour cela est décrite plus loin dans le chapitre.
CAPTCHA et reCAPTCHA
Voici ce dont vous avez besoin pour utiliser reCAPTCHA :
- Enregistrez-vous avec reCAPTCHA[recaptcha] et obtenez un couple (PUBLIC_KEY, PRIVATE_KEY) pour votre compte. Ce sont juste deux chaînes.
- Ajoutez le code suivant à votre modèle après que l'objet
auth
ait été défini :
from gluon.tools import Recaptcha
auth.settings.captcha = Recaptcha(request,
'PUBLIC_KEY', 'PRIVATE_KEY')
reCAPTCHA peut ne pas fonctionner si vous accédez au site web en 'localhost' ou '127.0.0.1', car il est défini pour fonctionner uniquement sur des sites web visiblement publiques.
Le constructeur Recaptcha
prend quelques arguments optionnels :
Recaptcha(..., use_ssl=False, error_message='invalid', label='Verify:', options='')
Il y a un argument expérimental, ajax=True
, qui utilise l'API ajax pour recaptcha. Elle peut être utilisée avec n'importe quel recaptcha, mais a spécialement été ajoutée pour autoriser les champs de recaptcha à fonctionner dans les formulaires LOAD (voir chapitre 12 pour plus d'informations sur LOAD, qui permet à web2py de 'plugger' des composants d'une page avec ajax). C'est expérimental car peut être remplacé avec une détection automatique lorsque ajax est requis.
Notez que use_ssl=False
est par défaut.
options
peut être une chaîne de configuration, e.g. options="theme:'white', lang:'fr'"
Plus de détails : reCAPTCHA[recaptchagoogle] et personnalisation .
Si vous ne voulez pas utiliser reCAPTCHA, regarder dans la définition de la classe Recaptcha
dans "gluon/tools.py", puisqu'il est facile d'utiliser d'autres systèmes de CAPTCHA.
Notez que Recaptcha
est juste un helper qui étend DIV
. Il génère un champ aléatoire qui valide en utilisant le service reCaptcha
et, donc, il peut être utilisé dans n'importe quel formulaire, incluant les FORMs définis par l'utilisateur :
form = FORM(INPUT(...),Recaptcha(...),INPUT(_type='submit'))
Vous pouvez l'utiliser dans tous les types de SQLFORM en injection :
form = SQLFORM(...) or SQLFORM.factory(...)
form.element('table').insert(-1,TR('',Recaptcha(...),''))
Personnaliser Auth
L'appel à
auth.define_tables()
définit toutes les tables Auth qui n'ont pas déjà été définies. Cela signifie que si vous souhaitez le faire, vous pouvez définir votre propre table auth_user
.
Il y a de nombreux moyens de personnaliser auth. Le moyen le plus simple est d'ajouter des champs complémentaires :
## after auth = Auth(db)
auth.settings.extra_fields['auth_user']= [
Field('address'),
Field('city'),
Field('zip'),
Field('phone')]
## before auth.define_tables(username=True)
Vous pouvez déclarer des champs complémentaires non seulement pour la table "auth_user" mais aussi pour les autres tables "auth_". Utiliser extra_fields
est le moyen recommandé car il ne cassera aucun mécanisme interne.
Un autre moyen de faire cela, même s'il n'est pas réellement recommandé, consiste à définir vos tables d'authentification par vous-même. Si une table est déclarée avant auth.define_tables()
elle est utilisée au lieu de celle par défaut. Voici comment faire cela :
## after auth = Auth(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), # required
Field('password', 'password', length=512, # required
readable=False, label='Password'),
Field('address'),
Field('city'),
Field('zip'),
Field('phone'),
Field('registration_key', length=512, # required
writable=False, readable=False, default=''),
Field('reset_password_key', length=512, # required
writable=False, readable=False, default=''),
Field('registration_id', length=512, # required
writable=False, readable=False, default=''))
## do not forget validators
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()
Vous pouvez ajouter n'importe quel champ que vous souhaitez, et vous pouvez changer les validateurs mais ne pouvez pas supprimer les champs marqués comme "requis" dans cet exemple.
Il est important de rendre les champs "password", "registration_key", "reset_password_key" et "registration_id" en readable=False
et writable=False
, puisqu'un visiteur ne doit pas être autorisé à les modifier.
Si vous ajoutez un champ appelé "username", il sera utilisé à la place de "email" pour le login. Si vous le faite, vous aurez également besoin d'ajouter un validateur comme ceci :
auth_table.username.requires = IS_NOT_IN_DB(db, auth_table.username)
Renommer les tables Auth
[renaming_auth_tables]
Les noms actuels des tables Auth
sont stockés dans
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'
Les noms de table peuvent être changés en re-assignant les variables ci-dessus après que l'objet auth
ait été défini et avant que les tables Auth ne soient définies. Par exemple :
auth = Auth(db)
auth.settings.table_user_name = 'person'
#...
auth.define_tables()
Les tables actuelles peuvent aussi être référencées, indépendamment de leurs noms actuels par
auth.settings.table_user
auth.settings.table_group
auth.settings.table_membership
auth.settings.table_permission
auth.settings.table_event
Note : auth.signature est défini lorsque Auth est initialisé, ce qui est avant que vous ayez défini les noms de table personnalisés. Pour éviter ceci, faites :
auth = Auth(db, signature=False)
Dans ce cas, auth.signature sera défini à la place lorsque vous appelez auth.define_tables(), à ce moment les noms de table personnalisés seront déjà définis.
Autres méthodes de login et formulaires de login
Auth fournit de multiples méthodes de login et se permet de créer de nouvelles méthodes de login. Chaque méthode d'authentifications supportée correspond à un fichier dans le dossier
gluon/contrib/login_methods/
Référez-vous à la documentation dans les fichiers eux-mêmes pour chaque méthode de login, mais voici quelques exemples.
Tout d'abord, nous avons besoin de faire une distinction entre deux types de méthodes d'authentification :
- les méthodes de login qui utilisent un formulaire de login web2py (même si les identifiants sont vérifiés en dehors de web2py). Un exemple est LDAP.
- les méthodes de login qui nécessitent un formulaire externe de single-sign-on (un exemple est Google et Facebook).
Dans le dernier cas, web2py n'obtient jamais les identifiants de connexion, seulement un jeton envoyé par le fournisseur de service. Le jeton est stocké dans db.auth_user.registration_id
.
Considérons les exemples du premier cas :
Basic
Disons que vous avez un service d'authentification, par exemple à l'URL
https://basic.example.com
qui accepte un accès en authentification basic. Cela signifie que le serveur accepte les requêtes HTTP avec un en-tête de la forme :
GET /index.html HTTP/1.0
Host: basic.example.com
Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
où la dernière chaîne est l'encodage en base64 de la chaîne username:password. Le service répond 200 OK si l'utilisateur est autorisé et 400, 401, 402, 403 ou 404 sinon.
Vous voulez entrer le nom d'utilisateur et le mot de passe en utilisant le formulaire d'identification standard Auth
et vérifier les identifiants avec un tel service. Tout ce dont vous avez besoin est d'ajouter le code suivant à votre application
from gluon.contrib.login_methods.basic_auth import basic_auth
auth.settings.login_methods.append(
basic_auth('https://basic.example.com'))
Notez que auth.settings.login_methods
est une liste de méthodes d'authentification qui sont exécutées séquentiellement. Par défaut défini à
auth.settings.login_methods = [auth]
Quand une méthode alternative est ajoutée, par exemple basic_auth
, Auth essaie d'abord d'authentifier le visiteur en se basant sur le contenu de auth_user
, et quand ceci échoue, il essaie la méthode suivante dans la liste. Si une méthode réussit à authentifier le visiteur, et si auth.settings.login_methods[0]==auth
, Auth
prend les actions suivantes :
- si l'utilisateur n'existe pas dans
auth_user
, un nouvel utilisateur est créé et le nom d'utilisateur/email et mot de passe sont stockés. - si l'utilisateur existe dans
auth_user
mais que le nouveau mot de passe ne correspond pas à l'ancien mot de passe stocké, l'ancien mot de passe est remplacé par le nouveau (notez que les mots de passe sont toujours stocké en hachés sauf si spécifié autrement).
Si vous ne souhaitez pas stocker le nouveau mot de passe dans auth_user
, alors il suffit de changer l'ordre des méthodes d'authentification, ou d'enlever auth
de la liste. Par exemple :
from gluon.contrib.login_methods.basic_auth import basic_auth
auth.settings.login_methods = [basic_auth('https://basic.example.com')]
De même pour toute autre méthode d'authentification décrite ici.
SMTP et Gmail
Vous pouvez vérifier les identifiants de connexion en utilisant un serveur SMTP distant, par exemple GMail ; i.e., un utilisateur est connecté lorsque l'email et le mot de passe qu'ils fournissent sont des identifiants valides pour accéder au serveur SMTP GMail (smtp.gmail.com:587
). Tout ce qui est nécessaire pour cela est le code suivant :
from gluon.contrib.login_methods.email_auth import email_auth
auth.settings.login_methods.append(
email_auth("smtp.gmail.com:587", "@gmail.com"))
Le premier argument de email_auth
est la combinaison adresse:port du serveur SMTP. Le second est le domaine de l'email.
Ceci fonctionne avec n'importe quel serveur SMTP qui nécessite une authentification TLS.
PAM
L'authentification en utilisant Pluggable Authentication Modules (PAM) fonctionne dans les cas précédents. Il permet à web2py d'authentifier les utilisateurs en utilisant les comptes des systèmes d'exploitation :
from gluon.contrib.login_methods.pam_auth import pam_auth
auth.settings.login_methods.append(pam_auth())
LDAP
L'authentification en utilisant LDAP fonctionne exactement comme dans les cas précédents.
Pour utiliser l'identification LDAP avec MS Active Directory :
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'))
Pour utiliser l'identification avec Lotus Notes et Domino :
auth.settings.login_methods.append(ldap_auth(mode='domino',
server='my.domino.server'))
Pour utiliser l'identification LDAP avec OpenLDAP (avec UID) :
auth.settings.login_methods.append(ldap_auth(server='my.ldap.server',
base_dn='ou=Users,dc=domain,dc=com'))
Pour utiliser l'identification LDAP avec OpenLDAP (avec CN) :
auth.settings.login_methods.append(ldap_auth(mode='cn',
server='my.ldap.server', base_dn='ou=Users,dc=domain,dc=com'))
Google App Engine
L'authentification utilisant Google lorsque l'application fonctionne sur Google App Enfine nécessite de passer le formulaire d'identification web2py, d'être redirigé vers la page d'identification de Google, et revenir en cas de succès. Puisque le comportement est différent que dans les exemples précédents, l'API est un peu différente.
from gluon.contrib.login_methods.gae_google_login import GaeGoogleAccount
auth.settings.login_form = GaeGoogleAccount()
OpenID
Nous avons précédemment parlé de l'intégration avec Janrain (qui supporte OpenID) et c'est le moyen le plus simple d'utiliser OpenID. Cependant, parfois, vous ne voulez pas utiliser de service tiers et vous voulez accéder au fournisseur OpenID directement depuis le client (votre application).
Voici un exemple :
from gluon.contrib.login_methods.openid_auth import OpenIDAuth
auth.settings.login_form = OpenIDAuth(auth)
OpenIDAuth
nécessite l'installation du module python-openid séparément. En arrière-plan, cette méthode d'authentification définit la table suivante :
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))
qui stocke le nom d'utilisateur openid pour chaque utilisateur. Si vous souhaitez afficher les openids pour l'utilisateur courant connecté :
{{=auth.settings.login_form.list_user_openids()}}
OAuth2.0
Nous avons précédemment parlé de l'intégration avec Janrain, mais parfois vous ne souhaitez pas passer par un service tiers et vous préférez accéder au fournisseur OAuth2.0 directement ; par exemple Facebook, Linkedin, Twitter, Google, tous ceux-là fournissent un service d'authentification OAuth2.0. web2py gère le flux OAuth2.0 de manière transparente afin qu'un utilisateur puisse être vérifié avec n'importe quel fournisseur OAuth2.0 durant l'identification. Autre que l'authentification, un fournisseur OAuth2.0 peut autoriser l'accès à n'importe quelle application web2py à des ressources utilisateurs avec un accès restreint avec une API propriétaire. Google, Twitter, Facebook ou autre, ont tous des APIs qui peuvent être facilement récupérées par une application web2py.
Il doit être souligné que OAuth2.0 est limité seulement à l'authentification et à l'autorisation (pour l'instant, CAS a plus de fonctionnalités), ce qui signifie que chaque fournisseur OAuth2.0 a un moyen différent de recevoir un id unique depuis leur base de données utilisateur via l'une de leurs APIs. Les méthodes spécifiques sont bien expliquées dans la documentation respective de chaque fournisseur, et consistent en général en de très simples appels REST. C'est pourquoi pour chaque fournisseur, il y a le besoin d'écrire quelques lignes de code.
Avant d'écrire quelconque instruction dans le modèle de l'application, une première étape est nécessaire pour n'importe quel fournisseur : enregistrer une nouvelle application ; ce qui est habituellement fait sur le site du fournisseur et expliqué dans la documentation du fournisseur.
Il y a quelques choses qui ont besoin d'être connues une fois qu'il est nécessaire d'ajouter le nouveau fournisseur OAuth2.0 à votre application : 1. L'URI d'Authorization; 2. L'URI de requête Token; 3. Le token d'application et secret reçus lors de l'enregistrement de la nouvelle application; 4. les permissions que le fournisseur doit autoriser pour l'application web2py, i.e. le "scope" (voir la documentation du fournisseur); 5. l'appel API pour recevoir l'UID de l'utilisateur à authentifier, comme expliqué dans la documentation du fournisseur.
Les points 1 à 4 sont utilisés pour initialiser la terminaison de l'autorisation utilisée par web2py pour communiquer avec le fournisseur OAuth2.0. L'id unique est récupéré par web2py avec un appel à la méthode get_user() lorsque nécessaire durant le flux d'identification ; c'est où l'appel API du point 5 est nécessaire.
Ce sont les modification essentielles qui doivent être faites dans votre modèle : a. importer la classe OAuthAccount; b. définir une implémentation dérivée OAuthClass; c. surcharger la méthode __init__() de cette classe; d. surcharger la méthode get_user() de cette classe; e. instancier la classe avec les données des points 1 à 4 de la liste ci-dessus;
Une fois la classe instanciée, et l'utilisateur authentifié, l'application web2py peut accéder à l'API du fournisseur à n'importe quel moment en utilisant le jeton d'accès OAuth2.0 en appelant la méthode accessToken() de cette classe.
Ce qui suit est un exemple de ce qui peut être utilisé avec Facebook. C'est un exemple basique utilisant Facebook Graph API, repensez cela, en écrivant une méthode propre get_user(), de très nombreuses choses peuvent être faites. L'exemple montre comment le jeton d'accès OAuth2.0 peut être utilisé en appelant l'API distante du provider.
Tout d'abord vous devez installer le SDK Python Facebook.
Ensuite vous avez besoin du code suivant dans votre modèle :
## Define oauth application id and secret.
FB_CLIENT_ID='xxx'
FB_CLIENT_SECRET="yyyy"
## import required modules
try:
import json
except ImportError:
from gluon.contrib import simplejson as json
from facebook import GraphAPI, GraphAPIError
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):
OAuthAccount.__init__(self, None, FB_CLIENT_ID, FB_CLIENT_SECRET,
self.AUTH_URL, self.TOKEN_URL,
scope='email,user_about_me,user_activities, user_birthday, user_education_history, user_groups, user_hometown, user_interests, user_likes, user_location, user_relationships, user_relationship_details, user_religion_politics, user_subscriptions, user_work_history, user_photos, user_status, user_videos, publish_actions, friends_hometown, friends_location,friends_photos',
state="auth_provider=facebook",
display='popup')
self.graph = None
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()))
user = None
try:
user = self.graph.get_object("me")
except GraphAPIError, e:
session.token = None
self.graph = None
if user:
if not user.has_key('username'):
username = user['id']
else:
username = user['username']
if not user.has_key('email'):
email = '%s.fakemail' %(user['id'])
else:
email = user['email']
return dict(first_name = user['first_name'],
last_name = user['last_name'],
username = username,
email = '%s' %(email) )
## use the above class to build a new login form
auth.settings.login_form=FaceBookAccount()
Nous avons précédemment parlé de l'intégration avec Janrain (qui supporte LinkedIn) et qui est le meilleur moyen d'utiliser OAuth. Mais cependant parfois, vous pouvez ne pas vouloir utiliser un service tiers et préférer accéder à Linkedin directement pour obtenir plus d'information que ce que fournit Janrain.
Voici un exemple :
from gluon.contrib.login_methods.linkedin_account import LinkedInAccount
auth.settings.login_form=LinkedInAccount(request,KEY,SECRET,RETURN_URL)
LinkedInAccount
nécessite l'installation du module "python-linkedin" séparément.
X509
Vous pouvez également vous connecter en passant à la page un certificat x509 et votre identifiant sera extrait du certificat. Ceci nécessite que M2Crypto
soit installé depuis
http://chandlerproject.org/bin/view/Projects/MeTooCrypto
Une fois que M2Crypto est installé, vous pouvez faire :
from gluon.contrib.login_methods.x509_auth import X509Account
auth.settings.actions_disabled=['register','change_password','request_reset_password']
auth.settings.login_form = X509Account()
Vous pouvez maintenant vous authentifier dans web2py en passant votre certificat x509. Comme faire cela dépend du navigateur utilisé, mais vous être plus probablement amené à utiliser les certificats pour les web services. Dans ce cas, vous pouvez utiliser l'exemple cURL
pour essayer votre authentification :
curl -d "firstName=John&lastName=Smith" -G -v --key private.key --cert server.crt https://example/app/default/user/profile
Ceci fonctionne automatiquement avec Rocket (le serveur web intégré à web2py) mais vous pouvez avoir besoin de configurations complémentaire côté serveur si vous en utilisez un autre. En particulier, vous avez besoin d'indiquer au serveur web où les certificats sont localisés sur l'hôte local puisqu'il a besoin de vérifier les certificats vendant des clients. Le procédé étant différent selon les serveurs web, il n'est pas présenté ici.
Multiples formulaires de connexion
Certaines méthodes de connexion modifient le login_form, d'autres non. Lorsqu'elles font cela, elles peuvent ne pas pouvoir co-exister. Maintenant, certaines co-existent en fournissant plusieurs formulaires d'authentification dans la même page. web2py fournit un moyen de faire cela. Voici un exemple mixant un login normal (auth) et un login RPX (janrain.com) :
from gluon.contrib.login_methods.extended_login_form import ExtendedLoginForm
other_form = RPXAccount(request, api_key='...', domain='...', url='...')
auth.settings.login_form = ExtendedLoginForm(auth, other_form, signals=['token'])
Si les signaux sont définis et un paramètre dans la requête correspond à n'importe quel signal, il retournera à la place l'appel à other_form.login_form
. other_form
peut gérer quelques situations particulières, par exemple, de multiples étapes de la connexion OpenID à l'intérieur de other_form.login_form
.
Autrement, il rendra le formulaire de connexion normal ensemble avec other_form
.
Versioning d'enregistrement
Vous pouvez utiliser Auth pour activer le versioning complet d'enregistrement :
db.enable_record_versioning(db,
archive_db=None,
archive_names='%(tablename)s_archive',
current_record='current_record'):
Ceci indique à web2py de créer une table archive pour chacune des tables dans db
et stocke une copie de chaque enregistrement dès qu'il est modifié. L'ancienne copie est stockée. La nouvelle copie ne l'est pas.
Les trois derniers paramètres sont optionnels :
archive_db
permet de spécifier une autre base de donnée où les tables archive doivent être stockées. Le définir àNone
revient à le définir àdb
.archive_names
fournit un pattern pour le nommage de chaque table d'archive.current_record
spécifie le nom du champ de référence à utiliser dans la table archive pour se référer à l'enregistrement original, non modifié. Notez que siarchive_db!=db
alors le champ de référence est juste un champ entier puisque les références croisées entre base de données ne sont pas possibles.
Seules les tables avec les champs modified_by
et modified_on
(comme créées pour l'exemple par auth.signature) seront archivées.
Lorsque vous enable_record_versioning
, si les enregistrement ont un champ is_active
(également créé par auth.signature), les enregistrement ne seront jamais supprimés mais seront marqués avec is_active=False
. En fait, enable_record_versioning
ajoute un common_filter
à toute table versionnée qui filtre les enregistrements avec is_active=False
et deviennent essentiellement invisibles.
Si vous enable_record_versioning
, vous ne devriez pas utiliser auth.archive
ou crud.archive
sinon vous finirez avec des enregistrements dupliqués. Ces fonctions font explicitement ce que enable_record_versioning
fait automatiquement et seront dépréciées.
Mail
et Auth
Vous pouvez lire plus au sujet des API web2py pour les emails et la configuration d'email dans le Chapitre 8. Nous limitons la présentation ici à l'interaction entre Mail
et Auth
.
Définissez un mailer avec
from gluon.tools import Mail
mail = Mail()
mail.settings.server = 'smtp.example.com:25'
mail.settings.sender = '[email protected]'
mail.settings.login = 'username:password'
ou utilisez simplement le mailer fourni par auth
:
mail = auth.settings.mailer
mail.settings.server = 'smtp.example.com:25'
mail.settings.sender = '[email protected]'
mail.settings.login = 'username:password'
Vous avez besoin de remplacer le mail.settings avec les paramètres pour votre serveur SMTP. Définissez mail.settings.login = None
si le serveur SMTP ne requiert pas d'authentification. Si vous ne voulez pas utiliser TLS, définissez mail.settings.tls = False
Dans Auth
, par défaut, la vérification d'email est désactivée. Pour activer l'email, ajoutez les lignes suivantes dans le modèle où auth
est défini :
auth.settings.registration_requires_verification = True
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'
Dans les deux auth.messages
ci-dessus, vous pouvez avoir besoin de remplater la portion d'URL de la chaine avec la bonne URL complète de l'action. Ceci est nécessaire car web2py peut être installé derrière un proxy, et ne peut pas déterminer ses propres URLs publiques avec une certitude absolue. Les exemples ci-dessus (qui sont les valeurs par défaut) devraient, cependant, fonctionner dans la plupart des cas.
Vérification en deux étapes
La vérification en deux étapes (ou authentification à double facteur) est un moyen d'améliorer la sécurité de l'authentification. Le paramètre ajoute une étape complémentaire dans le processus de connexion. Dans la première étape, les utilisateurs voient le formulaire standard de nom d'utilisateur/mot de passe. S'ils réussissent à passer cette étape en envoyant le bon nom d'utilisateur et le bon mot de passe associé, et que l'authentification en deux étapes est activée pour l'utilisateur, alors le serveur va présenter un second formulaire avant de terminer l'authentification. Ce formulaire va demander à l'utilisateur un code à 6 chiffres qui a été envoyé par email sur son compte (le serveur envoie par mail le code si les identifiants sont corrects). L'utilisateur a alors 4 tentatives pour entrer ce code correctement. Si le code est incorrect, la seconde étape de vérification est considérée comme échouée et l'utilisateur doit à nouveau remplir le premier formulaire (username/password).
Cette fonctionnalité peut être activée par utilisateur en :
- Créant un groupe (aussi connu comme rôle) pour la vérification en deux étapes. Dans cet exemple, il sera appelé
auth2step
et la description peut êtreTwo-step verification
. - Donnand à un utilisateur l'appartenance à ce rôle (groupe).
- Ajoutant le paramètre suivant dans le modèle où vous avez créé et configuré votre objet auth (probablement dans le modèle db.py) :
auth.settings.two_factor_authentication_group = "auth2step"
- N'oubliez pas de configurer le serveur mail dans db.py
Autorisation
Une fois un nouvel utilisateur enregistré, un nouveau groupe est créé pour contenir l'utilisateur. Le role du nouvel utilisateur est conventionnellement "user_[id]" où [id] est l'id du nouvel utilisateur créé. La création du groupe peut être dsactivé avec
auth.settings.create_user_groups = None
bien que nous ne suggérions pas de le faire ainsi. Notez que create_user_groups
n'est pas un booléen (bien s'il peut être False
) mais par défaut à :
auth.settings.create_user_groups="user_%(id)s"
Il stocke un template pour le nom du coup créé pour l'utilisateur id
.
Les utilisateur ont une appartenance aux groupes. Chaque groupe est identifié par un nom/role. Les groupes ont des persmissions. Les utilisateurs ont des permissions selon les groupes auxquels ils appartiennent. Par défaut, chaque utilisateur est rendu membre de son propre groupe.
Vous pouvez aussi faire
auth.settings.everybody_group_id = 5
pour rendre n'importe quel utilisateur automatiquement membre d'un groupe numéro 5. Ici 5 est utilisé comme exemple et nous supposons que le groupe a déjà été créé.
Vous pouvez créér des groupes, donner l'appartenance et les permissions via appadmin ou de manière codée en utilisant les méthodes suivantes :
auth.add_group('role', 'description')
qui retourne l'id d'un nouveau groupe créé.
auth.del_group(group_id)
qui supprime le groupe avec group_id
.
auth.del_group(auth.id_group('user_7'))
qui supprime le groupe avec le role "user_7", i.e., le groupe uniquement associé à l'utilisateur numéro 7.
auth.user_group(user_id)
qui retourne l'id du groupe uniquement associé à l'utilisateur identifié par user_id
.
auth.add_membership(group_id, user_id)
donne l'appartenance au groupe group_id
à l'utilisateur user_id
. Si user_id
n'est pas spécifié, alors web2py utilise l'utilisateur courant connecté.
auth.del_membership(group_id, user_id)
révoque l'appartenance de user_id
du groupe group_id
. Si user_id
n'est pas spécifié, alors web2py utilise l'utilisateur courant connecté.
auth.has_membership(group_id, user_id, role)
vérifie si user_id
a l'appartenance au groupe group_id
ou au groupe avec le role spécifié. Seulement group_id
ou role
devraient être passés à la fonction, pas les deux. Si user_id
n'est pas spécifié, alors web2py utilise l'utilisateur courant connecté.
auth.add_permission(group_id, 'name', 'object', record_id)
donne la permission "name" (définie par l'utilisateur) sur l'objet "object" (aussi défini par l'utilisateur) aux membres du groupe group_id
. Si "object" est un nom de table alors la permission peut se référer à la table entière en définissant recored_id
à une valeur de zéro, ou la permission peut se référer à un enregistrement spécifique en spécifiant une valeur reocrd_id
plus grande que zéro. Lorsque des permissions sont données sur des tables, il est habituel d'utiliser un nom de permission dans l'ensemble ('create', 'read', 'update', 'delete', 'select') puisque ces permissions peuvent être comprises et forcées par les APIs CRUD.
Si group_id
est zéro, web2py utiliser le groupe uniquement associé à l'utilisateur courant connecté.
Vous pouvez aussi utiliser auth.id_group(role="...")
pour obtenir l'id d'un groupe en donnant son nom.
auth.del_permission(group_id, 'name', 'object', record_id)
révoque la permission.
auth.has_permission('name', 'object', record_id, user_id)
vérifie si l'utilisateur identifié par user_id
a l'appartenance à un groupe avec la permission requise.
rows = db(auth.accessible_query('read', db.mytable, user_id)) .select(db.mytable.ALL)
retourne toutes les lignes de la table "mytable" donc l'utilisateur user_id
a la permission "read" dessus. Si le user_id
n'est pas spécifié, alors web2py utilise l'utilisateur courant connecté. Le accessible_query(...)
peut être combiné avec d'autres requêtes pour en construire des plus complexes. accessible_query(...)
est la seule méthode Auth à nécessiter un JOIN, et ne fonctionne donc pas sur Google App Engine.
Supposant les définitions suivantes :
>>> from gluon.tools import Auth
>>> auth = Auth(db)
>>> auth.define_tables()
>>> secrets = db.define_table('document', Field('body'))
>>> james_bond = db.auth_user.insert(first_name='James',
last_name='Bond')
Voici un exemple :
>>> 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
Décorateurs
Le moyen le plus commun de vérifier la permission n'est pas d'effectuer des appels explicites aux méthodes ci-dessus, mais de décorer des fonctions afin que les permissions soient vérifiées de façon relative avec le visiteur connecté. Voici quelques exemples :
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', requires_login=True)
def function_six():
return 'you can read secret documents'
@auth.requires_permission('add', 'number')
def add(a, b):
return a + b
def function_seven():
return add(3, 4)
L'argument conditionnel de @auth.requires(condition)
peut être appelable et à moins que la condition soit simple, il est mieux de passer un appelable qu'une condition puisque ce sera plus rapide, comme la condition sera sulement évaluée si nécessaire. Par exemple
@auth.requires(lambda: check_condition())
def action():
....
@auth.requires
prend également un argument optionnel requires_login
qui est par défaut à True
. Si défini à False, il ne requiert pas d'authentification avec d'évaluer la condition comme true/false. La condition peut être une valeur booléenne ou une fonction évaluant un booléen.
Notez que l'accès à toutes les fonctions sauf la première est restreinte selon les permissions que le visiteur peut avoir ou ne pas avoir.
Si le visiteur n'est pas connecté, alors la permission ne peut pas être vérifiée ; le visiteur est redirigé vers la page d'identification et ensuite renvoyé à la page qui nécessite les permissions.
Combiner les pré-requis
Occasionnellement, il est nécessaire de combiner les pré-requis. Ceci peut être fait via un décorateur requires
qui prend un simple argument, une condition true ou false. Par exemple, pour donner accès à des agents, mais seulement le Mardi :
@auth.requires(auth.has_membership(group_id='agents') and request.now.weekday()==1)
def function_seven():
return 'Hello agent, it must be Tuesday!'
ou de façon équivalente :
@auth.requires(auth.has_membership(role='Secret Agent') and request.now.weekday()==1)
def function_seven():
return 'Hello agent, it must be Tuesday!'
Autorisation et CRUD
L'utilisation de décorateurs et/ou la vérification explicite fournit un moyen d'implémenter le contrôle d'accès.
Un autre moyen d'implémenter le contrôle d'accès et de toujours utiliser CRUD (en opposé à SQLFORM
) pour accéder à la base de données et demander à CRUD de forcer le contrôle d'accès sur des tables et enregistrements de la base. Ceci est fait en liant Auth
et CRUD avec la déclaration suivante :
crud.settings.auth = auth
Ceci empêchera le visiteur d'accéder à n'importe quelle fonction CRUD à moins que le visiteur ne soit connecté et n'ait un accès explicite. Par exemple, pour autoriser un visiteur à poster des commentaires, mais seulement mettre à jour ses propres commentaires (supposant que crud, auth et db.comment sont définis) :
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).select()
return dict(form=form, comments=comments)
def update_comment():
form = crud.update(db.comment, request.args(0))
return dict(form=form)
Vous pouvez aussi sélectionner des enregistrements spécifique (ceux qui ont l'accès 'read') :
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)
Les noms de permission forcés par :
crud.settings.auth = auth
sont "read", "create", "update", "delete", "select", "impersonate".
Autorisation et téléchargements
L'utilisation de décorateurs et l'usage de crud.settings.auth
ne force pas l'autorisation sur les fichiers téléchargés par la fonction de téléchargement habituelle
def download(): return response.download(request, db)
Si l'on souhaite faire cela, il faut déclarer explicitement quels champs "upload" contiennent des fichiers qui ont besoin de contrôle d'accès lors du téléchargement. Par exemple :
db.define_table('dog',
Field('small_image', 'upload'),
Field('large_image', 'upload'))
db.dog.large_image.authorize = lambda record: auth.is_logged_in() and auth.has_permission('read', db.dog, record.id, auth.user.id)
L'attribut authorize
du champ upload peut être None (par défaut) ou une fonction qui décide si l'utilisateur est connecté et a le droit de 'read' l'enregistrement courant. Dans cet exemple, il n'y a aucune restriction sur le téléchargement d'images liées par le champ "small_image", mais nous avons besoin du contrôle d'accès sur les images liées par le champ "large_image".
Contrôle d'accès et Basic Authentication
De temps en temps il peut être nécessaire d'exposer des actions qui ont des décorateurs qui nécessitent le contrôle d'accès comme services ; i.e., pour les appeler depuis un programme ou un script et toujours être capable d'utiliser l'authentification pour vérifier l'autorisation.
Auth active l'identification via l'authentification basique :
auth.settings.allow_basic_login = True
Avec cet ensemble, une action comme
@auth.requires_login()
def give_me_time():
import time
return time.ctime()
peut être appelée, par exemple, depuis une commande shell :
wget --user=[username] --password=[password]
http://.../[app]/[controller]/give_me_time
Il est également posisble de se connecter en appelant auth.basic()
plutôt que d'utiliser un décorateur @auth
:
def give_me_time():
import time
auth.basic()
if auth.user:
return time.ctime()
else:
return 'Not authorized'
La connexion Basic est souvent la seule option pour les services (décrits dans le chapitre suivant), mais est désactivé par défaut.
Gestion d'Application via les utilisateurs privilégiés (expérimental)
Normallement les fonctions d'administration telle que définir les utilisateurs et les groupes sont gérés par l'administrateur du serveur. Cependant, vous pouvez vouloir qu'un groupe d'utilisateurs privilégiés ait ces droits d'administration pour une application spécifique. Ceci est possible avec la version web2py v2.5.1 (mettre à jour une application existante nécessite le nouveau contrôleur appadmin et la nouvelle vue appadmin.html, copiés depuis l'application welcome. De même, les applications créées avant web2py 2.6 ont besoin du nouveau fichier javascript dans welcome/static/js/web2py.js).
Le concept permet différents paramètres de gestion, chacun d'eux permet à un groupe d'utilisateur d'éditer un certain ensemble de tables dans cette application.
Exemple : Premièrement, créez un groupe (également connu comme un role) pour vos utilisateurs privilégiés. Dans cet exemple, il sera appelé admin. Donnez l'appartenance à ce groupe à un utilisateur. Ensuite, pensez à un nom pour décrire ce paramètre de management, tel que db_admin.
Ajoutez le paramètre suivante dans le modèle où vous avez créé et configuré votre objet auth (probablement dans le modèle db) :
auth.settings.manager_actions = dict(db_admin=dict(role='admin',heading='Manage Database',tables = db.tables))
Un objet de menu a l'URL comme ci-dessous, passez le nom du paramètre de management comme un argument :
URL('appadmin','manage',args=['db_admin'])
Cette URL apparaît comme /appadmin/manage/auth.
Usage avancé
Ce mécanisme permet de multiples paramètres de gestion ; chaque paramètre de gestion complémentaire est juste une autre clé définie dans auth.settings.manager_actions.
Par exemple, vous pouvez vouloir qu'un groupe d'utilisateurs (tel que 'Super') ait accès à toutes les tables dans le paramètre de management appelé "db_admin", et un autre groupe (tel que 'Content Manager') un accès admin aux tables relatives au contenu dans le paramètre de management appelé "content_admin".
Ceci peut être fait comme ceci :
auth.settings.manager_actions = dict(
db_admin=dict(role='Super', heading='Manage Database', tables=db.tables),
content_admin=dict(role='Content Manager', tables=[content_db.articles, content_db.recipes, content_db.comments])
content_mgr_group_v2 = dict(role='Content Manager v2', db=content_db,
tables=['articles','recipes','comments'],
smartgrid_args=dict(
DEFAULT=dict(maxtextlength=50,paginate=30),
comments=dict(maxtextlength=100,editable=False)
)
)
(La clé d'en-tête est optionnelle. Si absente, une valeur par défaut intelligente sera utilisée)
Vous pouvez alors faire deux nouveaux objets de menu avec ces URLs :
URL('appadmin','manage',args=['db_admin'])
URL('appadmin','manage',args=['content_admin'])
Le paramètre de management appelé "content_mgr_group_v2" montre quelques possibilités avancées. La clé smartgrid_args est passée à la smartgrid utilisée pour éditer ou voir les tables. Sauf depuis la clé spéciale DEFAULT, les noms de tables sont passées comme clés (telle que la table appelée "comments"). La syntaxe dans cet exemple nomme les tables comme une liste de chaînes, utilisant la clé db=content_db pour spécifier la base de données.
Authentification manuelle
Parfois, vous pouvez vouloir implémenter votre propre logique et effectuer une identification utilisateur "manuelle". Ceci peut être fait également en appelant la fonction :
user = auth.login_bare(username,password)
login_bare
retourne l'utilisateur si l'utilisateur existe et que le mot de passe est valide, et retourne False sinon. username
est l'email si la table "auth_user" n'a pas de champ "username".
Paramètres de Auth et messages
Voici une liste de tous les paramètres qui peuvent être personnalisés pour Auth
Le suivant doit pointer sur un objet gluon.tools.Mail
pour permettre à auth
d'envoyer des emails :
auth.settings.mailer = None
Apprenez-en plus sur le paramètrage du mail ici : Mail et Authentification
Le suivant doit être le nom du contrôleur qui a défini l'action user
:
auth.settings.controller = 'default'
Le suivant était un paramètre très important dans les anciennes versions de web2py :
auth.settings.hmac_key = None
où il était défini à quelque chose comme "sha512:a-pass-phrase" et passé au validateur CRYPT pour le champ "password" de la table auth_user
, fournissant l'algorithme et "a-pass-phrase" pour hacher le mot de passe. Cependant, web2py n'a plus besoin de ce paramètre car il le gère automatiquement.
Par défaut, auth nécessite aussi un mot de passe d'une longueur minimum de 4 caractères. Ceci peut être changé avec :
auth.settings.password_min_length = 4
Pour désactiver une action, ajoutez son nom à cette liste :
auth.settings.actions_disabled = []
Par exemple :
auth.settings.actions_disabled.append('register')
désactivera l'enregistrement.
Si vous voulez recevoir un email pour vérifier l'enregistrement définissez cette clé à True
:
auth.settings.registration_requires_verification = False
Pour connecter automatiquement quelqu'un après enregistrement, même s'ils n'ont pas complété le process de vérification d'email, définissez la clé suivante à True
:
auth.settings.login_after_registration = False
Si de nouveaux enregistrés doivent attendre une approbation avant de pouvoir se connecter, définissez cette clé à True
:
auth.settings.registration_requires_approval = False
L'approbation consiste à définir registration_key==''
via l'interface appadmin ou de façon codée.
Si vous ne voulez pas qu'un nouveau groupe soit créé pour chaque nouvel utilisateur, définissez la clé suivante à False
:
auth.settings.create_user_groups = True
Les paramètres suivants déterminent des méthodes de connexion et des formulaires de connexion alternatifs, comme présenté précédemment :
auth.settings.login_methods = [auth]
auth.settings.login_form = auth
Voulez-vous autoriser l'authentification basic ?
auth.settings.allows_basic_login = False
La clé suivante est l'URL de l'action login
:
auth.settings.login_url = URL('user', args='login')
Si l'utilisateur a essayé d'accéder à la page d'enregistrement mais est déjà connecté, il sera automatiquement redirigé vers cette URL :
auth.settings.logged_url = URL('user', args='profile')
Ceci doit pointer sur l'URL de l'action de téléchargement, dans le cas où le profil contient des images :
auth.settings.download_url = URL('download')
Ceux-ci doivent pointer vers l'URL où vous souhaitez rediriger vos utilisateurs après les différentes actions auth
(dans le cas où il n'y a pas de referrer) :
Note : Si votre application est basée sur l'application de référence Welcome, vous utilisez auth.navbar. Pour que les paramètres ci-après aient un effet, vous avez besoin d'éditer layout.html et définir l'argument referrer_actions = None.
auth.navbar(mode='dropdown',referrer_actions=None)
Il est également possible de conserver referrer_actions pour certains événements d'authentification. Par exemple
auth.navbar(referrer_actions=['login', 'profile'])
Si le comportement par défaut est laissé inchangé, auth.navbar utilise le paramètre _next de l'URL, et utilise pour renvoyer l'utilisateur à la page en référence. Cependant, si le comportement par défaut d'auto-référencement est changé, les paramètres ci-dessous vont prendre effet.
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')
Si le visiteur n'est pas connecté, et appelle une fonction qui nécessite une authentificaiton, l'utilisateur est redirigé vers auth.settings.login_url
qui renvoie par défaut sur URL('default','user/login')
. On peut remplacer ce comportement en redéfinissant :
auth.settings.on_failed_authentication = lambda url: redirect(url)
C'est la fonction appelée pour la redirection. L'argument url
passé à cette fonction est l'url de la page de connexion.
Si le visiteur n'a pas les droits d'accès pour une fonction donnée, le visiteur est redirigé vers l'URL définie par
auth.settings.on_failed_authorization = URL('user',args='on_failed_authorization')
Vous pouvez changer cette variable et rediriger l'utilisateur ailleurs.
Souvent on_failed_authorization
est une URL mais peut aussi être une fonction qui retourne l'URL et qui sera appelé en cas d'autorisation refusée.
Voici la liste de callbacks qui devraient être exécutées après la validation d'un formulaire pour chacune des actions correspondantes avant n'importe quel IO sur la base de données :
auth.settings.login_onvalidation = []
auth.settings.register_onvalidation = []
auth.settings.profile_onvalidation = []
auth.settings.retrieve_password_onvalidation = []
auth.settings.reset_password_onvalidation = []
Chaque callback doit être une fonction qui prend un objet form
et il peut modifier les attributs de l'objet form avant que l'IO de la base de données soit effectué.
Voici la liste de callbacks qui devraient être exécutées après que l'IO sur la base de données ait été effectué et avant la redirection :
auth.settings.login_onaccept = []
auth.settings.register_onaccept = []
auth.settings.profile_onaccept = []
auth.settings.verify_email_onaccept = []
Voici un exemple :
auth.settings.register_onaccept.append(lambda form: mail.send(to='[email protected]',subject='new user',
message='new user email is %s'%form.vars.email))
Vous pouvez activer le captcha pour n'importe quelle action 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 le paramètre .captcha
pointe sur un gluon.tools.Recaptcha
, tous les formulaires pour lesquels l'option correspondante (comme .login_captcha
) est définie à None
auront un captcha, tant que cet objet gluon.tools.Recaptcha
aura le captcha et les autres non.
Ceci est le temps d'expiration d'une session de connexion :
auth.settings.expiration = 3600 # seconds
Vous pouvez changer le nom du champ de mot de passe (dans Firebird par exemple, "password" est un mot-clé et ne peut pas être utilisé pour un nom de champ) :
auth.settings.password_field = 'password'
Normalement le formulaire de connexion essaie de valider un email. Ceci peut être désactivé en changeant ce paramètre :
auth.settings.login_email_validate = True
Voulez-vous montrer l'id de l'enregistrement dans la page d'édition du profil ?
auth.settings.showid = False
Pour les formulaires personnalisés, vous pouvez souhaiter désactiver la notification automatique d'erreur dans les formulaires :
auth.settings.hideerror = False
Egalement pour les formulaires personnalisés, vous pouvez changer le style :
auth.settings.formstyle = 'table3cols'
(ce peut être "table2cols", "divs" et "ul")
Et vous pouvez définir le séparateur pour les formulaires auto-générés :
auth.settings.label_separator = ':'
Par défaut, le formulaire de connexion donne l'option pour allonger la connexion via l'option "remember me". Le temps d'expiration de session peut être changé ou même désactivé avec ces paramètres :
auth.settings.long_expiration = 3600*24*30 # one month
auth.settings.remember_me_form = True
Vous pouvez aussi personnaliser les messages suivants, dont l'usage et le contexte devraient être évidents :
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)"
Les logs d'appartenance add|del|has
permettent l'usage de "%(user_id)s" et "%(group_id)s". Les logs de permission add|del|has
permettent l'usage de "%(user_id)s", "%(name)s", "%(table_name)s", et "%(record_id)s".
Central Authentication Service
web2py fournit le support pour l'authentification tierce et le single sign on. Nous présentons ici Central Authentication Service (CAS) qui est un standard de l'industrie et aussi bien le client que le serveur sont présents dans web2py.
CAS est un protocole ouvert pour l'authentificaiton distribuée et fonctionne de la manière suivante : lorsqu'une visiteur arrive sur notre site web, notre application vérifie dans la session si l'utilisateur est déjà authentifié (par exemple via un objet session.token
). Si l'utilisateur n'est pas authentifié, le contrôleur redirige le visiteur depuis l'appliance CAS, où l'utilisateur peut se connecter, s'enregistrer et gérer ses identifiants (nom, email, mot de passe). Si l'utilisateur s'enregistre, il reçoit un mail, et l'enregistrement n'est pas complété tant qu'il ne répond pas à cet email. Une fois que l'utilisateur s'est correctemnet enregistré et connecté, l'appliance CAS redirige l'utilisateur vers notre application avec une clé. Notre application utilise la clé pour obtenir les identifiants de l'utilisateur via une requête HTTP en arrière-plan vers le serveur CAS.
En utilisant ce mécanisme, de multiples applications peuvent utiliser le single-sign-on via un simple serveur CAS. Le serveur fournissant l'authentification est appelé un Service Provider. L'application cherchant à authentifier les visiteurs est appelée Service Consumer.
CAS est similaire à OpenIN, avec un différence principale. Dans le cas d'OpenID, le visiteur choisit le Service Provider. Dans le cas de CAS, notre application fait ce choix, rendant CAS plus sécurisé.
Démarrer un fournisseur CAS web2py est aussi simple que de copier l'application de référence. En fait, toute application web2py expose l'action
## in provider app
def user(): return dict(form=auth())
est un fournisseur CAS 2.0 et ses services peuvent être accédés via l'URL
http://.../provider/default/user/cas/login
http://.../provider/default/user/cas/validate
http://.../provider/default/user/cas/logout
(nous supposons que l'application est appelée "provider").
Vous pouvez accéder à ce service depuis toute autre application web (client) en déléguant simplement l'authentification au fournisseur :
## in consumer app
auth = Auth(db,cas_provider = 'http://127.0.0.1:8000/provider/default/user/cas')
Lorsque vous visitez l'url de connexion de l'application cliente, vous serez redirigé vers l'application fournisseur qui effectuera l'authentification et vous redirigera vers le client. Tous les processus d'enregistrement, déconnexion, changement de mot de passe, récupération de mot de passe, doivent être complétés sur le fournisseur de l'application. Une entrée sur l'utilisateur connecté sera créée côté client afin que vous puissiez ajouter des champs complémentaires et avoir un profil local. Grâce à CAS 2.0 tous les champs qui sont lisibles sur le fournisseur et qui ont un champ correspondant à un champ de la table auth_user
du client seront copiés automatiquement.
Auth(...,cas_provider='...')
fonctionne avec des fournisseurs tiers et supporte CAS 1.0 et 2.0. La version est détectée automatiquement. Par défaut, il construit les URLs du provider depuis une base (l'url cas_provider
ci-dessus) et en ajoutant
/login
/validate
/logout
Ils peuvent être changés dans le client et dans le fournisseur
## in consumer or provider app (must match)
auth.settings.cas_actions['login']='login'
auth.settings.cas_actions['validate']='validate'
auth.settings.cas_actions['logout']='logout'
Si vous voulez vous connecter à un fournisseur CAS web2py depuis un domaine différent, vous devez les activer en les ajoutant à la liste des domaines autorisés :
## in provider app
auth.settings.cas_domains.append('example.com')
Utiliser web2py pour autoriser des applications non-web2py
Ceci est possible mais dépendant du serveur web. Nous assumons ici que deux application fonctionnent sur le même serveur web : Apache avec mod_wsgi
. L'une de ces application est web2py avec une application effectuant le contrôle d'accès via Auth. L'autre peut être un script CGI, un programme PHP ou n'importe quoi d'autre. Nous voulons indiquer au serveur web qu'il doit demander la permission à la première application lorsqu'un client demande l'accès au second.
Tout d'abord, nous avons besoin de modifier l'application web2py et ajouter le contrôleur suivant :
def check_access():
return 'true' if auth.is_logged_in() else 'false'
qui retourne true
si l'utilisateur est connecté et false
sinon. Maintenant démarrez un processus web2py en arrière-plan :
nohup python web2py.py -a '' -p 8002
Le port 8002 est bien est il n'y a pas besoin d'activer l'administration donc pas de mot de passe admin.
Ensuite nous avons besoin d'éditer la configuration Apache (par exemple "/etc/apache2/sites-available/default") et l'indiquer à Apache afin que lorsqu'un programme non web2py est appelé, il puisse appeler l'action check
à la place, et seulement si elle retourne true
il devrait procéder et répondre à la requête, sinon il doit simplement refuser l'accès.
Puisque web2py et l'application non-web2py fonctionnent sur le même domaine, si l'utilisateur est connecté dans l'application web2py, le cookie de session web2py sera passé à Apache même lorsque l'autre application est demandée et autorisera donc la vérification d'identifiants.
Afin de faire cela, nous avons besoin d'un script, "web2py/scripts/access.wsgi" qui peut prendre en charge cette astuce. Le script est livré avec web2py. Tout ce que nous avons besoin de faire est d'indiquer à Apache d'appeler ce script, l'URL de l'application nécessitant le contrôle d'accès, et la localisation du script :
<VirtualHost *:80>
WSGIDaemonProcess web2py user=www-data group=www-data
WSGIProcessGroup web2py
WSGIScriptAlias / /home/www-data/web2py/wsgihandler.py
AliasMatch ^myapp/path/needing/authentication/myfile /path/to/myfile
<Directory /path/to/>
WSGIAccessScript /path/to/web2py/scripts/access.wsgi
</Directory>
</VirtualHost>
Ici "^myapp/path/needing/authentication/myfile" est l'expression régulière qui devrait matcher la requête entrante et "/path/to/" est le chemin absolu du dossier web2py.
Le script "access.wsgi" contient la ligne suivante :
URL_CHECK_ACCESS = 'http://127.0.0.1:8002/%(app)s/default/check_access'
qui pointe sur l'application web2py que nous avons demandée mais vous pouvez l'éditer pour pointer vers une application spécifique, en démarrant un autre port que 8002.
Vous pouvez aussi changer l'action check_access()
et rendre sa logique plus complexe. Cette action peut retrouver l'URL qui était originalement demandée en utilisant la variable d'environnement
request.env.request_uri
et vous pouvez implémenter des règles plus complexes :
def check_access():
if not auth.is_logged_in():
return 'false'
elif not user_has_access(request.env.request_uri):
return 'false'
else:
return 'true'