Nous allons voir comment mettre en place l’authentification et l’autorisation lorsque l’on souhaite travailler avec Zend et le protocole AMF.
Au niveau des pré-requit, vous devez avoir une architecture Zend et AMF opérationnelle.
Aussi, je vous conseils de lire la documentation sur Zend_Auth et Zend_Acl.
Vous devez avoir compris la notion de rôle et de ressource.
Objectifs
Si vous fournissez des services à travers le réseau, et qu’ils peuvent avoir un impact sur la cohérence des données, vous vous devez de sécuriser l’accès à ces services.
Sécurisé l’accès signifie restreinte l’accès, mais aussi chiffrer les communications.
Nous ne traiterons pas la partie chiffrement des échanges, pour cela je vous laissez chercher de la documentation sur HTTPS.
Point de départ
Ci-dessous, un exemple de contrôleur Zend offrant des services AMF.
class ServiceController extends Zend_Controller_Action
{publicfunction init(){$this->_helper->viewRenderer->setNoRender();$this->_helper->layout->disableLayout();}publicfunction amfAction(){$server=new Zend_Amf_Server();//Liste des services$server->setClass("Service_User");$server->setClass("Service_Book");$server->setClass("ZendAmfServiceBrowser");//Mapping des objets PHP avec les objets ActionScript $server->setClassMap('UserDTO','Model_DTO_User');$server->setClassMap('BookDTO','Model_DTO_Book');$server->setProduction(false);
ZendAmfServiceBrowser::$ZEND_AMF_SERVER=$server;echo($server->handle());}}
Nous avons donc deux services, et deux types d’objets pouvant être utilisés.
Vous remarquerez l’utilisation de ZamfBrowser, ce service couplé avec une application Adobe AIR permet de travailler facilement avec les services.
Voici à quoi ressemble les prototypes d’un de mes services.
L’autre service est composé lui aussi des méthodes CRUD (Create, Read, Update, Delete), ainsi que d’autres fonctions métiers.
Sachant que mon application cliente Flash utilise ces services, il est très facile à un utilisateur de découvrir l’URI de la passerelle AMF.
En l’état, une personne ayant l’accès à ma passerelle AMF pourrai utiliser toutes les fonctions de mes services. Ce qui implique qu’il puisse modifier ma base de données.
Remarquer que si l’on supprime le service de ZamfBrowser, l’utilisateur n’aura pas accès à la liste exhaustive des services. Si vous êtes en production, je vous conseils donc de supprimer ce service pour des raisons de sécurité.
Mise en place de l’authentification
Nous allons commencer par mettre en place l’authentification,
Sachant que nous utilisons un client Flash, il est logique d’utiliser Zend_Amf_Adobe_Auth.
Le fichier password.xml contient les identifiants et les mots de passe des utilisateurs.
C’est à ce niveau que vous définissez les affectations d’identifiants à un rôle.
Il est à noter, que si vous ne spécifier aucun identifiant lors de votre connexion au serveur AMF vous serez considéré comme un invité : Zend_Amf_Constants::GUEST_ROLE
Mise en place de l’autorisation
Maintenant que nous avons correctement définit l’authentification, passons à l’autorisation.
C’est maintenant que nous allons donner les autorisations ou refus d’accès aux services.
$acl=new Zend_Acl();$server->setAcl($acl);
Avec ces deux lignes, vous avez définit une Acces Control List.
Vous le constatez, aucune autorisation n’est définit; mais il est important de noter que maintenant, plus personne n’a accès à vos services.
Pour donner l’accès à nos deux services à n’importe qui, nous allons procéder comme suit :
Pour aller plus loin dans l’explication, nous allons maintenant définir l’ensemble de nos autorisations.
Nous souhaitons que le rôle administrateur est accès à tout, et nous souhaitons que les utilisateurs non authentifiés n’est pas accès aux fonctions de mise à jour.
Rappelez-vous, nous avons vu plus haut que vous étiez connecter avec le rôle invité lorsque vous ne spécifiez aucun identifiant.
$acl=new Zend_Acl();//Définition les rôles.$acl->addRole(new Zend_Acl_Role(Zend_Amf_Constants::GUEST_ROLE));//Définition des autorisations$acl->allow();$acl->deny(Zend_Amf_Constants::GUEST_ROLE,null,"create");$acl->deny(Zend_Amf_Constants::GUEST_ROLE,null,"update");$acl->deny(Zend_Amf_Constants::GUEST_ROLE,null,"delete");$server->setAcl($acl);
Premièrement,
Nous avons dû définir le rôle invité pour pouvoir travailler ses autorisations.
Deuxièmement,
Nous avons utilisé le principe des listes noires. Si nous avions utilisez un système de liste blanche, nous aurions du définir l’ensemble des rôles, et l’ensemble des autorisations.
C’est donc en partant du principe que tout le monde a accès à tout, que nous avons fermé les portes aux anonymes.
Explication des fonctions Deny & Allow : Zend_Acl::deny et Zend_Acl::allow utilisent les mêmes arguments.
Le premier paramètre correspond à un rôle, cela ne change pas des ACL Zend classique.
Le deuxième paramètre correspond à une ressource. Avec Zend AMF, une ressource correspond à un Service.
Le troisième paramètre correspond ici à la fonction d’un service AMF.
Nous avons utiliser null au niveau du deuxième paramètre pour dire : tout les services.
initAcl
Ce dernier paragraphe pour vous parlez de mon opposition face aux spécifications de la documentation Zend_AMF_Server :
If the ACL object is set, and the class being called defines initAcl() method, this method will be called with the ACL object as an argument. The class then can create additional ACL rules and return TRUE, or return FALSE if no access control is required for this class.
En gros, il existe une autre manière de définir les autorisations,
Si vous avez définit un ACL au niveau de votre Serveur AMF. Ce dernier vérifiera l’existence d’une fonction initAcl sur les services lorsqu’ils seront appelés.
Cette fonction est ce que l’on appel un Hook, et vous permets de définir les autorisations et refus au sein de la définition même de vos services.
Autant vous dire que niveau maintenance et centralisation de l’information c’est très moyen..
De plus, si l’on pend l’exemple de ZendAmfServiceBrowser, vous allez devoir modifier la définition de ce service, et cela à chaque fois qu’il y aura une nouvelle version ?
Voici un exemple de fonctionnement
class Service_Book
{publicfunction initAcl ($acl){$acl->deny('editor','Services_Book','create');$acl->deny('editor','Services_Book','update');$acl->deny('editor','Services_Book','delete');returntrue;}}
Lorsque l’on dispose d’un serveur dédié, la mise en production d’une application Zend Framework est assez simple; il suffit de modifier le DocumentRoot du VirtualHost.
Si l’on dispose d’un hébergement mutualisé, il faut aussi pouvoir proposer à nos visiteurs un accès au site via www.domain.tld et non pas www.domain.tld/public/
Première solution
La première solution, consiste à déplacer le fichier /public/index.php à la racine du domaine et de modifier la définition de la constante APPLICATION_PATH de ce même fichier.
Le seul soucis de cette solution, c’est que les appels aux ressources de type CSS ou JS devront être modifiés pour pointer vers www.domain.tld/public/
Personnellement, je trouve cela complètement nul…
Deuxième solution
La deuxième solution – bien plus élégante – passe par l’ajout d’un .htaccess à la racine du domaine.
RewriteEngine On
# Only apply to URLs on this domain
RewriteCond %{HTTP_HOST} ^(www.)?domain.tld$
# Only apply to URLs that aren't already under folder.
RewriteCond %{REQUEST_URI} !^/public/
# Don't apply to URLs that go to existing files or folders.
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
# Rewrite all those to insert /folder.
RewriteRule ^(.*)$ /public/$1
# Also redirect the root folder.
RewriteCond %{HTTP_HOST} ^(www.)?domain.tld$
RewriteRule ^(/)?$ public/index.php [L]
Là, tout est magique et fonctionne sans aucune autre modification de l’application.
Pour protéger l’accès à vos autres répertoires, et principalement /application qui contient surement vos mots de passe BDD non chiffrés. Rajouter un fichier .htaccess contenant
Prenons le schéma ci-dessus.
Au niveau de la définition, nous avons deux entités : Bureau ( rooms ) et Utilisateur ( users ). Ces deux entités sont liées par une relation manyToMany car un utilisateur peut avoir plusieurs bureaux, et un bureau peut avoir plusieurs utilisateurs.
Pour pouvoir intégrer l’ordonnancement des utilisateurs au sein d’une pièce, nous avons ajouté le champs « index » sur la table de liaison.
Grâce à cette définition, nous pouvons avoir les cas suivants :
- Robert est à la place #2 dans le bureau d’accueil
- Robert est à la place #1 dans le bureau du président
- Annie est à la place #1 dans le bureau du président
Définition des Domain Model Object
Nous allons maintenant définir les liaisons Doctrine entre ces objets.
Je n’utiliserai pas la définition YAML.
abstract class Model_Base_Room extends Model_Base
{publicfunction setTableDefinition(){$this->setTableName('rooms');$this->hasColumn('id_room as idRoom','integer',4,array('type'=>'integer','fixed'=>0,'unsigned'=>false,'primary'=>true,'autoincrement'=>true,'length'=>'4',));$this->hasColumn('title','string',80,array('type'=>'string','fixed'=>0,'unsigned'=>false,'primary'=>false,'notnull'=>true,'autoincrement'=>false,'length'=>'80',));}publicfunction setUp(){
parent::setUp();$this->hasMany('Model_User as users',array('refClass'=>'Model_CompoRoomsUsers','local'=>'id_room','foreign'=>'id_user'));}}
abstract class Model_Base_User extends Model_Base
{publicfunction setTableDefinition(){$this->setTableName('users');$this->hasColumn('id_user as idUser','integer',4,array('type'=>'integer','fixed'=>0,'unsigned'=>false,'primary'=>true,'autoincrement'=>true,'length'=>'4',));$this->hasColumn('firstname','string',80,array('type'=>'string','fixed'=>0,'unsigned'=>false,'primary'=>false,'notnull'=>true,'autoincrement'=>false,'length'=>'80',));}publicfunction setUp(){
parent::setUp();$this->hasMany('Model_Room as rooms',array('refClass'=>'Model_CompoRoomsUsers','local'=>'id_user','foreign'=>'id_room'));}}
abstract class Model_Base_CompoRoomsUsers extends Model_Base
{publicfunction setTableDefinition(){$this->setTableName('compo_rooms_users');$this->hasColumn('id_room as idRoom','integer',4,array('type'=>'integer','fixed'=>0,'unsigned'=>false,'primary'=>true,'autoincrement'=>false,'length'=>'4',));$this->hasColumn('id_user as idUser','integer',4,array('type'=>'integer','fixed'=>0,'unsigned'=>false,'primary'=>true,'autoincrement'=>false,'length'=>'4',));$this->hasColumn('index','integer',4,array('type'=>'integer','fixed'=>0,'unsigned'=>false,'primary'=>false,'notnull'=>true,'autoincrement'=>false,'length'=>'4',));}publicfunction setUp(){$this->hasOne('Model_User as user',array('local'=>'id_user','foreign'=>'id_user'));$this->hasOne('Model_Room as room',array('local'=>'id_room','foreign'=>'id_room'));}}
Ordonner avec DQL
Après cette longue définition, nous pouvons passer au code DQL qui permettra d’ordonner les résultats en fonction de l’index de notre table de liaison.
Prenez note du fait que nous trions dans un premier temps par identifiant de bureau et ensuite par index. Si nous n’utilisions qu’un tri par index, l’ordre des bureaux serai incohérent.
Pour aller plus loin
Nous avons décrit le fonctionnement pour ordonner des résultats à partir d’une table de liaison.
Mais vous pouvez aussi utiliser cette méthode pour conditionner le résultat.
Le pattern Transfert ObjectTO, Data Transfert Object DTO, ou encore Value Object VO permet d’échanger des données entre deux applications.
Wikipédia : Son but est de simplifier les transferts de données entre les sous-systèmes d’une application logiciel. Les objets de transfert de données sont souvent utilisés en conjonction des objets d’accès aux données.
Le pattern Transfert Object Assembler permet de passer d’un DTO à un Objet d’accès au données, et inversement.
C’est le moyen le plus simple pour travailler avec le pattern Data Transert Object et un ORM.
Domain Object Model -> Data Transfert Object
Avec l’ORM Doctrine, le transfert d’un « Domain Object Model DOM » ou ici Doctrine_Record vers un DTO est très simple.
Il suffit de réaliser un parser qui analyse l’objet issu de Doctrine, et qui remplit les données du DTO.
Data Transfert Object -> Domain Object Model
Convertir un DTO vers un DOM peut s’avère plus complexe.
En effet, Doctrine utilise des collections d’objet qui lui sont propres, mais a aussi besoin de connaitre les clés primaires et leurs contenus pour pouvoir créer un objet. Car un objet issu de Doctrine_Record n’a pas les même propriétés quand il est existant en base de donnée, ou qu’il est en phase d’être sauvegardé.
Implémentation de Transfer Object Assembler
Une implémentation du pattern Transfert Object Assembler vient de voir le jour après moult et moult péripéties.
Cette implémentation permet donc le transférer de DTOs à partir de DOMs et inversement.
Associer au pattern Service Layer, cela peut s’avérer être très pratique.
Nous avons ici créé une nouvelle entrée dans la table programs, et réalisé une liaison entre ce nouvel objet et la vidéo existante d’identifiant 30.
Le tout one-shot !
L’intérêt de cette implémentation, c’est d’être fonctionnel avec :
Les objets persistant
Les objets non-persistant
Les compositions d’objets sur N niveau
Utilisation avec le pattern Service Layer
Nous allons voir l’exemple le plus simple, soit l’implémentation des méthodes CRUD pour un objet générique.
L’utilisation du nommage des classes provient de l’utilisation de Zend Framework.
Implémentation du pattern Transfer Object Assembler
Interface du pattern Transfer Object Assembler
Classe de définition d’un DTO
Pour faire fonctionner l’assembler. Vous devez définir une classe qui hérite de Assembler, et ainsi surchargez la propriété $_relationships avec vos liaisons entre DTO et DOM.
Pour ceux qui ne suivent pas l’actualité, Google tient en ce moment des conférences appelées Google Input/Ouput.
Format vidéo Internet
Pour le premier jour de conférence, nous avons eu droit – entre autre – à l’annonce du format vidéo WebM qui – normalement – devrai devenir le standard vidéo libre du net.
La raison de ce nouveau format, c’est que l’utilisation du H.264 est soumise à une licence payante. A cause de cela Firefox ne pouvait pas lire les vidéos H.264…
Android 2.2
Autre nouveauté, concernant Android.
Nous avons eu la confirmation de la nouvelle version Android 2.2 nommé Froyo, qui apporte son lot d’amélioration :
Support complet de Flash Player 10.1 et Adobe AIR 2.5
2 à 5 fois plus rapide que les anciennes version grâce à un nouveau compilateur Dalvik JIT
Navigation internet 2 à 3 fois plus rapide grâce à un nouveau moteur Javascript
Installation des applications sur la carte SD possible en natif
Mise à jour automatique des applications installées à partir du Market
Pour ceux qui développent pour Android, la version 2.2 du SDK est disponible.
Alors que Adobe AIR peut s’installer sur Android 2.1, FP 10.1 doit être installé sur Android 2.2.
C’est dommage car très – très – peu de gens disposent de la nouvelle version Android Froyo, mais cela annonce aussi que Google ne devrai pas tarder à mettre à jour les terminaux
Commentaires récents