# Authentification avec une table de base de données

## Introduction

`Zend_Auth_Adapter_DbTable` fournit la possibilité d'authentifier sur la base de crédits stockés dans une table de base de données. Comme `Zend_Auth_Adapter_DbTable` requiert qu'une instance de `Zend_Db_Adapter_Abstract` soit fournie à son constructeur, chaque instance est liée à une connexion de base de données particulière. Les autres options de configuration peuvent être réglées grâce au constructeur ou au travers de différentes méthodes, une pour chaque option.

Les options de configuration disponibles incluent :

- tableName : il s'agit du nom de la table dans la base de données qui contient les crédits d'authentification, et envers laquelle la requête d'authentification sera réalisée.

- identityColumn : il s'agit du nom de la colonne dans la table utilisée pour représenter l'identité. La colonne d'identité doit contenir une valeur unique, comme un "username" ou une adresse émail.

- credentialColumn : il s'agit du nom de la colonne dans la table utilisée pour représenter le crédit. Dans le cas d'une simple authentification par identité / mot de passe, la valeur de crédit correspond au mot de passe. Voir aussi l'option credentialTreatment.

- credentialTreatment : dans la plupart des cas, les mots de passe et autres données sensibles sont cryptés, hachés, encodés, masqués, ou sinon traités à travers une fonction ou un algorithme. En spécifiant un traitement paramétrable de chaîne avec cette méthode, comme `MD5(?)` ou `PASSWORD(?)`, un développeur peut appliquer un code SQL arbitraire sur les données de crédit fournies. Comme ces fonctions sont spécifiques à chaque gestionnaire de base de données (SGBD), vérifiez le manuel de la base de données pour vérifier la disponibilité de ces fonctions dans votre système.

### Utilisation basique

Comme expliqué dans l'introduction, le constructeur de `Zend_Auth_Adapter_DbTable` requiert une instance de `Zend_Db_Adapter_Abstract` qui est utilisée comme connexion à la base de données à laquelle l'instance d'adaptateur d'authentification est liée. Avant tout, la connexion à la base de donnée devrait être crée.

Le code suivant crée un adaptateur pour une base de données en mémoire, crée un schéma avec une table unique, et insère une ligne sur laquelle nous pouvons réaliser une requête d'authentification plus tard. Cet exemple requiert que l'extension PDO SQLite soit disponible :

    // Crée une connexion de base de données SQLite en mémoire
    $dbAdapter = new Zend_Db_Adapter_Pdo_Sqlite(array('dbname' =>
                                                      ':memory:'));

    // Construit une requête de création de table
    $sqlCreate = 'CREATE TABLE [users] ( '
               . '[id] INTEGER  NOT NULL PRIMARY KEY, '
               . '[username] VARCHAR(50) UNIQUE NOT NULL, '
               . '[password] VARCHAR(32) NULL, '
               . '[real_name] VARCHAR(150) NULL)';

    // Crée la table de crédits d'authentification
    $dbAdapter->query($sqlCreate);

    // Construit la requête pour insérer une ligne pour laquelle
    // l'authentification pourra réussir
    $sqlInsert = "INSERT INTO users (username, password, real_name) "
               . "VALUES ('my_username', 'my_password', 'My Real Name')";

    // Insertion des données
    $dbAdapter->query($sqlInsert);

Avec une connexion de base de données et des données disponibles dans la table, une instance de `Zend_Auth_Adapter_DbTable` peut être créée. Les valeurs d'options de configuration peuvent être fournies au constructeur ou en tant que paramètres aux méthodes de réglage après l'instanciation :

    // Configure une instance avec des paramètres de constructeur ...
    $authAdapter = new Zend_Auth_Adapter_DbTable($dbAdapter,
                                                 'users',
                                                 'username',
                                                 'password');

    // ... ou configure l'instance avec des méthodes de réglage
    $authAdapter = new Zend_Auth_Adapter_DbTable($dbAdapter);
    $authAdapter->setTableName('users')
                ->setIdentityColumn('username')
                ->setCredentialColumn('password');

A cet instant, l'instance de l'adaptateur d'authentification est prête à recevoir des requêtes d'authentification. Dans le but de réaliser une requête d'authentification, les valeurs des crédits requêtés sont fournies à l'adaptateur avant d'appeler la méthode `authenticate()` :

    // Règle les valeurs d'entrées des crédits
    // (en général, à partir d'un formulaire d'enregistrement)
    $authAdapter->setIdentity('my_username')
                ->setCredential('my_password');

    // Réalise la requête d'authentification, et sauvegarde le résultat
    $result = $authAdapter->authenticate();

En plus de la disponibilité de la méthode `getIdentity()` pour récupérer l'objet du résultat d'authentification, `Zend_Auth_Adapter_DbTable` supporte aussi la récupération de la ligne de la table qui a réussi l'authentification :

    // Affiche l'identité
    echo $result->getIdentity() . "\n\n";

    // Affiche la ligne de résultat
    print_r($authAdapter->getResultRowObject());

    /* Affiche:
    my_username

    Array
    (
        [id] => 1
        [username] => my_username
        [password] => my_password
        [real_name] => My Real Name
    )
    */

Puisque la ligne de la table contient la valeur de crédit, il est important de garantir ces valeurs contre l'accès fortuit.

## Utilisation avancée : maintenir persistant l'objet de résultat DbTable

Par défaut, `Zend_Auth_Adapter_DbTable` retourne l'identité fournie à l'objet Auth en cas d'authentification couronnée de succès. Un autre scénario d'utilisation, où les développeurs veulent stocker dans le mécanisme de stockage persistant du `Zend_Auth` un objet d'identité contenant d'autres informations utiles, est résolu en utilisant la méthode `getResultRowObject()` retournant un objet stdClass. Le petit bout de code suivant illustre cette utilisation :

    // authentifie avec Zend_Auth_Adapter_DbTable
    $result = $this->_auth->authenticate($adapter);

    if ($result->isValid()) {

        // stocke l'identité comme objet dans lequel seulement username et
        // real_name sont retournés
        $storage = $this->_auth->getStorage();
        $storage->write($adapter->getResultRowObject(array('username',
                                                           'real_name')));

        // stocke l'identité comme objet dans lequel la colonne password
        // a été omis
        $storage->write($adapter->getResultRowObject(null, 'password'));

        /* ... */

    } else {

        /* ... */

    }

## Utilisation avancée par l'exemple

Bien que le but initial de `Zend_Auth` (et par extension celui de `Zend_Auth_Adapter_DbTable`) est principalement l'authentification et non l'autorisation (ou contrôle d'accès), il existe quelques exemples et problèmes qui franchissent la limite des domaines auxquels ils appartiennent. Selon la façon dont vous avez décidé d'expliquer votre problème, il semble parfois raisonnable de résoudre ce qui pourrait ressembler à un problème d'autorisation dans l'adaptateur d'authentification.

Ceci étant dit, `Zend_Auth_Adapter_DbTable` possède des mécanismes qui sont construits de telle sorte qu'ils peuvent être démultipliés pour ajouter des contrôles supplémentaires au moment de l'authentification pour résoudre quelques problèmes communs d'utilisateur.

    // La valeur du champs "etat" d'un compte
    // ne doit pas être égal à "compromis"
    $adapter = new Zend_Auth_Adapter_DbTable($db,
                                             'utilisateurs',
                                             'login',
                                             'password',
                                             'MD5(?) AND etat != "compromis"');

    // La valeur du champs "actif" d'un compte
    // doit être égal à "TRUE"
    $adapter = new Zend_Auth_Adapter_DbTable($db,
                                             'utilisateurs',
                                             'login',
                                             'password',
                                             'MD5(?) AND actif = "TRUE"');

Un autre scénario possible est l'implantation d'un mécanisme de "salting". "Salting" est un terme se référant une technique qui peut fortement améliorer la sécurité de votre application. C'est basé sur l'idée que concaténer une chaîne aléatoire à tout mot de passe rend impossible la réussite d'une attaque de type "brute force" sur la base de données en utilisant des valeurs préalablement hashées issues d'un dictionnaire.

Par conséquent nous devons modifier notre table pour stocker notre chaîne de "salt" aléatoire :

    $sqlAlter = "ALTER TABLE [users] "
              . "ADD COLUMN [password_salt] "
              . "AFTER [password]";

    $dbAdapter->query($sqlAlter);

Voici une méthode simple pour générer une chaîne aléatoire pour chaque utilisateur à leur enregistrement :

    for ($i = 0; $i < 50; $i++)
    {
        $dynamicSalt .= chr(rand(33, 126));
    }

Et maintenant, construisons l'adaptateur :

    $adapter = new Zend_Auth_Adapter_DbTable(
        $db,
        'users',
        'username',
        'password',
        "MD5(CONCAT('"
        . Zend_Registry::get('staticSalt')
        . "', ?, password_salt))"
    );

Vous pouvez encore améliorer la sécurité en utilisant une chaîne de "salt" statique codée en dur dans votre application. Dans le cas ou la base de données est compromise (par exemple par une attaque de type injection SQL) mais que votre serveur Web est intact, les données sont inutilisables par l'attaquant.

Une autre alternative est d'utiliser la méthode `getDbSelect()` de la classe `Zend_Auth_Adapter_DbTable` après la construction de l'adaptateur. Cette méthode retournera une instance d'objet `Zend_Db_Select` utilisée pour réaliser la routine `authenticate()`. Il est important de noter que cette méthode retournera toujours le même objet, que la méthode `authenticate()` a été appelée ou non. Cet objet ne comportera aucune information d'identité ou de crédit puisque celles-ci sont placées dans l'objet select au moment de `authenticate()`.

Un exemple de situation nécessitant l'utilisation de la méthode `getDbSelect()` serait de vérifier le statut d'un utilisateur, en d'autres termes pour voir si le compte d'un utilisateur est activé.

    // En continuant avec l'exemple ci-dessus
    $adapter = new Zend_Auth_Adapter_DbTable(
        $db,
        'users',
        'username',
        'password',
        'MD5(?)'
    );

    // Récupérer l'objet select (par référence)
    $select = $adapter->getDbSelect();
    $select->where('active = "TRUE"');

    // Authentification, ceci s'assure que users.active = TRUE
    $adapter->authenticate();