Suivez notre exemple Tutoriel d’un Blog, imaginons que nous souhaitions sécuriser l’accès de certaines URLs, basées sur la connexion de l’user. Nous avons aussi une autre condition requise, qui est d’autoriser notre blog à avoir des auteurs multiples, afin que chacun d’eux puisse créer ses propres posts, les modifier et les supprimer selon le besoin interdisant d’autres auteurs à apporter des modifications sur ses messages.
Premièrement, créeons une nouvelle table dans notre base de données du blog pour contenir les données de notre user:
CREATE TABLE users (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50),
password VARCHAR(255),
role VARCHAR(20),
created DATETIME DEFAULT NULL,
modified DATETIME DEFAULT NULL
);
Nous avons respecté les conventions de CakePHP pour le nommage des tables, mais nous profitons d’une autre convention: en utilisant les colonnes du nom d’user et du mot de passe dans une table users, CakePHP sera capable de configurer automatiquement la plupart des choses pour nous quand on réalisera la connexion de l’user.
La prochaine étape est de créer notre model User, qui a la responsablilité de trouver, sauvegarder et valider toute donnée d’user:
// app/Model/User.php
App::uses('AppModel', 'Model');
class User extends AppModel {
public $name = 'User';
public $validate = array(
'username' => array(
'required' => array(
'rule' => 'notBlank',
'message' => 'Un nom d\'utilisateur est requis'
)
),
'password' => array(
'required' => array(
'rule' => 'notBlank',
'message' => 'Un mot de passe est requis'
)
),
'role' => array(
'valid' => array(
'rule' => array('inList', array('admin', 'auteur')),
'message' => 'Merci de rentrer un rôle valide',
'allowEmpty' => false
)
)
);
}
Créeons aussi notre UsersController, le contenu suivant correspond à la classe cuisinée basique UsersController en utilisant les utilitaires de génération de code fournis avec CakePHP:
// app/Controller/UsersController.php
class UsersController extends AppController {
public function beforeFilter() {
parent::beforeFilter();
$this->Auth->allow('add', 'logout');
}
public function index() {
$this->User->recursive = 0;
$this->set('users', $this->paginate());
}
public function view($id = null) {
if (!$this->User->exists($id)) {
throw new NotFoundException(__('User invalide'));
}
$this->set('user', $this->User->findById($id));
}
public function add() {
if ($this->request->is('post')) {
$this->User->create();
if ($this->User->save($this->request->data)) {
$this->Flash->success(__('L\'user a été sauvegardé'));
return $this->redirect(array('action' => 'index'));
} else {
$this->Flash->error(__('L\'user n\'a pas été sauvegardé. Merci de réessayer.'));
}
}
}
public function edit($id = null) {
$this->User->id = $id;
if (!$this->User->exists()) {
throw new NotFoundException(__('User Invalide'));
}
if ($this->request->is('post') || $this->request->is('put')) {
if ($this->User->save($this->request->data)) {
$this->Flash->success(__('L\'user a été sauvegardé'));
return $this->redirect(array('action' => 'index'));
} else {
$this->Flash->error(__('L\'user n\'a pas été sauvegardé. Merci de réessayer.'));
}
} else {
$this->request->data = $this->User->findById($id);
unset($this->request->data['User']['password']);
}
}
public function delete($id = null) {
// Avant 2.5, utilisez
// $this->request->onlyAllow('post');
$this->request->allowMethod('post');
$this->User->id = $id;
if (!$this->User->exists()) {
throw new NotFoundException(__('User invalide'));
}
if ($this->User->delete()) {
$this->Flash->success(__('User supprimé'));
return $this->redirect(array('action' => 'index'));
}
$this->Flash->error(__('L\'user n\'a pas été supprimé'));
return $this->redirect(array('action' => 'index'));
}
}
Modifié dans la version 2.5: Depuis 2.5, utilisez CakeRequest::allowMethod()
au lieu de
CakeRequest::onlyAllow()
(dépréciée).
De la même façon, nous avons créé les vues pour nos posts de blog ou en utilisant l’outil de génération de code, nous exécutons les vues. Dans le cadre de ce tutoriel, nous allons juste montrer le add.ctp:
<!-- app/View/Users/add.ctp -->
<div class="users form">
<?php echo $this->Form->create('User');?>
<fieldset>
<legend><?php echo __('Ajouter User'); ?></legend>
<?php echo $this->Form->input('username');
echo $this->Form->input('password');
echo $this->Form->input('role', array(
'options' => array('admin' => 'Admin', 'auteur' => 'Auteur')
));
?>
</fieldset>
<?php echo $this->Form->end(__('Ajouter'));?>
</div>
Nous sommes maintenant prêt à ajouter notre couche d’authentification. Dans
CakePHP, c’est géré par AuthComponent
, une classe responsable
d’exiger la connexion pour certaines actions, de gérer la connexion et la
déconnexion, et aussi d’autoriser aux users connectés les actions
que l’on souhaite leur voir autorisées.
Pour ajouter ce component à votre application, ouvrez votre fichier
app/Controller/AppController.php
et ajoutez les lignes suivantes:
// app/Controller/AppController.php
class AppController extends Controller {
//...
public $components = array(
'Flash',
'Auth' => array(
'loginRedirect' => array('controller' => 'posts', 'action' => 'index'),
'logoutRedirect' => array('controller' => 'pages', 'action' => 'display', 'home')
)
);
public function beforeFilter() {
$this->Auth->allow('index', 'view');
}
//...
}
Il n’y a pas grand chose à configurer, puisque nous avons utilisé les
conventions pour la table des users. Nous avons juste configuré les
URLs qui seront chargées après que la connexion et la déconnexion des actions
sont effectuées, dans notre cas, respectivement à /posts/
et /
.
Ce que nous avons fait dans la fonction beforeFilter
a été de dire au
AuthComponent de ne pas exiger un login pour toutes les actions index
et view
, dans chaque controller. Nous voulons que nos visiteurs soient
capables de lire et lister les entrées sans s’inscrire dans le site.
Maintenant, nous avons besoin d’être capable d’inscrire des nouveaux users, de sauvegarder leur nom d’user et mot de passe, et plus important de hasher leur mot de passe afin qu’il ne soit pas stocké en texte plain dans notre base de données. Disons à AuthComponent de laisser des users non-authentifiés d’accéder à la fonction add des users et de réaliser l’action connexion et deconnexion:
// app/Controller/UsersController.php
public function beforeFilter() {
parent::beforeFilter();
// Permet aux utilisateurs de s'enregistrer et de se déconnecter
$this->Auth->allow('add', 'logout');
}
public function login() {
if ($this->request->is('post')) {
if ($this->Auth->login()) {
return $this->redirect($this->Auth->redirectUrl());
} else {
$this->Flash->error(__("Nom d'user ou mot de passe invalide, réessayer"));
}
}
}
public function logout() {
return $this->redirect($this->Auth->logout());
}
Le hash du mot de passe n’est pas encore fait, ouvrez votre fichier de model
app/Model/User.php
et ajoutez ce qui suit:
// app/Model/User.php
App::uses('AppModel', 'Model');
App::uses('SimplePasswordHasher', 'Controller/Component/Auth');
class User extends AppModel {
// ...
public function beforeSave($options = array()) {
if (isset($this->data[$this->alias]['password'])) {
$passwordHasher = new SimplePasswordHasher();
$this->data[$this->alias]['password'] = $passwordHasher->hash(
$this->data[$this->alias]['password']
);
}
return true;
}
// ...
Ainsi, maintenant à chaque fois qu’un user est sauvegardé, le mot de passe est hashé en utilisant la classe SimplePasswordHasher. Il nous manque juste un fichier template de vue pour la fonction de connexion:
//app/View/Users/login.ctp
<div class="users form">
<?php echo $this->Flash->render('auth'); ?>
<?php echo $this->Form->create('User'); ?>
<fieldset>
<legend>
<?php echo __('Please enter your username and password'); ?>
</legend>
<?php echo $this->Form->input('username');
echo $this->Form->input('password');
?>
</fieldset>
<?php echo $this->Form->end(__('Login')); ?>
</div>
Vous pouvez maintenant inscrire un nouvel user en rentrant l’URL
/users/add
et vous connecter avec ce profil nouvellement créé en allant
sur l’URL /users/login
. Essayez aussi d’aller sur n’importe quel URL
qui n’a pas été explicitement autorisée telle que /posts/add
, vous verrez
que l’application vous redirige automatiquement vers la page de connexion.
Et c’est tout! Cela semble trop simple pour être vrai. Retournons en arrière un
peu pour expliquer ce qui s’est passé. La fonction beforeFilter
dit au
component AuthComponent de ne pas exiger de connexion pour l’action add
en plus des actions index
et view
qui étaient déjà autorisées dans
la fonction beforeFilter
de l’AppController.
L’action login
appelle la fonction $this->Auth->login()
dans
AuthComponent, et cela fonctionne sans autre config car nous suivons les
conventions comme mentionnées plus tôt. C’est-à-dire, avoir un model
User avec les colonnes username et password, et
utiliser un formulaire posté à un controller avec les données d’user.
Cette fonction retourne si la connexion a réussi ou non, et en cas de succès,
alors nous redirigeons l’user vers l’URL configuré de redirection que
nous utilisions quand nous avons ajouté AuthComponent à notre application.
La déconnexion fonctionne juste en allant à l’URL /users/logout
et
redirigera l’user vers l’Url de Déconnexion configurée décrite
précedemment. Cette URL est le résultat de la fonction
AuthComponent::logout()
en cas de succès.
Comme mentionné avant, nous convertissons ce blog en un outil multi-user à autorisation, et pour ce faire, nous avons besoin de modifier un peu la table posts pour ajouter la référence au model User:
ALTER TABLE posts ADD COLUMN user_id INT(11);
Aussi, un petit changement dans PostsController est nécessaire pour stocker l’user connecté courant en référence pour le post créé:
// app/Controller/PostsController.php
public function add() {
if ($this->request->is('post')) {
$this->request->data['Post']['user_id'] = $this->Auth->user('id'); //Ligne ajoutée
if ($this->Post->save($this->request->data)) {
$this->Flash->success(__('Votre post a été sauvegardé.'));
$this->redirect(array('action' => 'index'));
}
}
}
La fonction user()
fournie par le component retourne toute colonne à partir
de l’user connecté courant. Nous avons utilisé cette méthode pour
ajouter les données dans les infos requêtées qui sont sauvegardées.
Sécurisons maintenant notre app pour empêcher certains auteurs de modifier ou supprimer les posts des autres. Des règles basiques pour notre app sont que les users admin peuvent accéder à tout URL, alors que les users normaux (le role auteur) peuvent seulement accéder aux actions permises. Ouvrez encore la classe AppController et ajoutez un peu plus d’options à la config de Auth:
// app/Controller/AppController.php
public $components = array(
'Flash',
'Auth' => array(
'loginRedirect' => array('controller' => 'posts', 'action' => 'index'),
'logoutRedirect' => array(
'controller' => 'pages',
'action' => 'display',
'home'
),
'authenticate' => array(
'Form' => array(
'passwordHasher' => 'Blowfish'
)
),
'authorize' => array('Controller') // Ajout de cette ligne
)
);
public function isAuthorized($user) {
// Admin peut accéder à toute action
if (isset($user['role']) && $user['role'] === 'admin') {
return true;
}
// Refus par défaut
return false;
}
Nous venons de créer un mécanisme très simple d’autorisation. Dans ce cas, les
users avec le role admin
sera capable d’accéder à tout URL dans le
site quand ils sont connectés, mais les autres (par ex le role auteur
) ne
peut rien faire d’autre par rapport aux users non connectés.
Ce n’est pas exactement ce que nous souhaitions, donc nous avons besoin de
déterminer et fournir plus de règles à notre méthode isAuthorized()
. Mais
plutôt que de le faire dans AppController, déleguons à chaque controller la
fourniture de ces règles supplémentaires. Les règles que nous allons ajouter
à PostsController permettront aux auteurs de créer des posts mais empêcheront
l’édition des posts si l’auteur ne correspond pas. Ouvrez le fichier
PostsController.php
et ajoutez le contenu suivant:
// app/Controller/PostsController.php
public function isAuthorized($user) {
// Tous les users inscrits peuvent ajouter les posts
if ($this->action === 'add') {
return true;
}
// Le propriétaire du post peut l'éditer et le supprimer
if (in_array($this->action, array('edit', 'delete'))) {
$postId = (int) $this->request->params['pass'][0];
if ($this->Post->isOwnedBy($postId, $user['id'])) {
return true;
}
}
return parent::isAuthorized($user);
}
Nous surchargeons maintenant l’appel isAuthorized()
de AppController’s et
vérifions à l’intérieur si la classe parente autorise déjà l’user.
Si elle ne le fait pas, alors nous ajoutons juste l’autorisation d’accéder
à l’action add, et éventuellement accés pour modifier et de supprimer.
Une dernière chose à que nous avons oubliée d’exécuter est de dire si
l’user à l’autorisation ou non de modifier le post, nous appelons
une fonction isOwnedBy()
dans le model Post. C’est généralement une
bonne pratique de déplacer autant que possible la logique dans les models.
Laissons la fonction s’exécuter:
// app/Model/Post.php
public function isOwnedBy($post, $user) {
return $this->field('id', array('id' => $post, 'user_id' => $user)) !== false;
}
Ceci conclut notre tutoriel simple sur l’authentification et les autorisations. Pour sécuriser l’UsersController, vous pouvez suivre la même technique que nous faisions pour PostsController, vous pouvez aussi être plus créatif et coder quelque chose de plus général dans AppController basé sur vos propres règles.
Si vous avez besoin de plus de contrôle, nous vous suggérons de lire le guide complet Auth dans la section Authentification où vous en trouverez plus sur la configuration du component, la création de classes d’autorisation personnalisée, et bien plus encore.
Génération de code avec Bake Génération basique CRUD de code
Authentification: Inscription d’user et connexion