お客様の案件の場合、認証機能を持たせないことはまず無いので毎回お世話になるのがAuthコンポーネント。
組み込みのつど、他のプロジェクトからコピーしては貼り付けてるので細かいところを忘れてしまって「これってどうするんだっけ」ってことが結構あります。
そんなわけで今回は、認証機能を提供するAuthコンポーネントでパスワードをvalidationするためのメモ。
まずはちょっとおさらいから。
※CakePHP1.3系の話です。*1
コンポーネントのフックメソッドについて
Authコンポーネントの前に、コンポーネントのフックメソッドについておさらいしましょう。
調べようと思ったら素晴らしいまとめがあったので、こちらを参照しましょう。
参考:CakePHP 目で見るフックメソッド – Shin x blog
Authコンポーネントの場合は、initializeとstartupが使われていますが、特にstartupの処理がポイントです。
initializeはコントローラのbeforeFilterの前に、startupは後に実行されるフックメソッドです。
Authコンポーネントのおさらい
Authコンポーネントはstartupメソッドで、必要な処理の大半を行なっています。*2 Auth認証が必要なページにアクセスすれば、ログインセッションがあるかどうかをチェックし、なければログインページにリダイレクトしたりするのもそうですが、重要なのはPOSTされたデータ$this->dataにAuthのpasswordがある場合にハッシュ化してしまう処理もstartupで行なっているということです。
ユーザの登録画面や編集画面からpasswordをポストするとbeforeFilterの処理後にはハッシュ化されてしまうため、もしユーザモデルでpasswordに文字数制限のvalidationをかけていても、ハッシュ化された後のパスワードが評価されてしまいます。*3
Authのパスワードをvalidateする
デフォルトであれば、$this->data[‘User’][‘password’]がポストされるとハッシュ化されてしまいますから、ユーザの登録・編集画面ではフィールド名を「password_confirm」などとして、$this->Auth->hashPasswords()の対象とならないようにします。
この時のvalidate対象はpasswordではなくpassword_confirmにするのをお忘れなく。
/view/users/add.ctp
<?PHP
echo $this->Form->create('User');
echo $this->Form->input('username');
// password→password_confirmに
// なお、password_confirmというフィールド名だと(たぶん)自動でtypeがpasswordにならないためoptionsで指定している
echo $htis->Form->input('password_confirm', array('type' => 'password'));
echo $this->Form->submit();
echo $this->Form->end();
?>
/model/user.php
// passwordのvalidateのとこだけ抜粋
var $validate = array(
'password_confirm' => array(
'between' => array(
'rule' => array('between', 6, 16),
'message' => 'パスワードは6文字以上16文字以下で入力してください',
),
'alphaNumeric' => array(
'rule' => 'alphaNumeric',
'message' => 'パスワードは半角英数字のみで入力してください',
),
'notempty' => array(
'rule' => array('notempty'),
'message' => 'パスワードは必ず入力してください',
'on' => 'create',
),
),
);
受け取ったpassword_confirmで先にvalidatesして、成功した後にハッシュ化してpasswordに入れてあげます。
/controller/users_controller.php
function add() {
if (!empty($this->data)) {
$this->User->create();
// 先にvalidateしちゃう
$this->User->set($this->data);
if (!$this->User->validates()) {
return;
}
// validateが通った後にパスワードをハッシュ化する
$this->data['User']['password'] = $this->Auth->password($this->data['User']['password_confirm']);
if ($this->User->save($this->data, false)) { // validateは通してあるので第2引数をfalseに
$this->Session->setFlash('ユーザを登録しました');
$this->redirect(array('action' => 'index'));
} else {
$this->Session->setFlash('入力内容に誤りがあります<br />入力内容をご確認ください');
}
} else {
$this->data['User']['deleted'] = 0;
}
}
更新(update)の時は?
更新の際のパスワード変更には2通りの方法をとっています。
ひとつは、パスワードだけ別画面で更新させる方法、もうひとつは編集画面でそのまま入力させる方法です。
前者は一般ユーザが利用するWEBサービスなどで、間違って登録された時に管理者が修正することができない*4 場合にこの方法をとります。この場合は「パスワード忘れ」の処理も必須になりますね。
後者は社内システム等の場合などで利用者が特定できるシステムで採用しています。
パスワードがわからなくなったら超管理者的な人が仮のパスワードを再設定してあげて「ログインしたらパスワード変えて?」って言える場合ですね。
パスワードは更新時に入力があれば変更し、未入力なら変更しません。
これなら別画面を作る必要もないし、パスワード忘れの処理も必要ないので簡単です。
パスワード忘れの処理は長くなりそうなので、今回は簡単な方で説明します。
ビューはhiddenでidを埋め込む以外は登録と共通です。
コントローラの処理だけ書きますね。
/controller/users_controller.php
function admin_edit($id = null) {
if (!$id && empty($this->data)) {
$this->Session->setFlash('更新対象の指定がありません', 'warning');
$this->redirect(array('action' => 'index'));
}
if (!empty($this->data)) {
// password_confirmがなければunsetしてやって無かったことにする
// validateでは必ず必要なフィールドrequiredはデフォルトでfalseのため、
// unsetしてやればvalidateには引っかからない。※空でも配列が存在すれば引っかかる。
if (empty($this->data['User']['password_confirm'])) {
unset($this->data['User']['password_confirm']);
}
$this->User->set($this->data);
if (!$this->User->validates()) {
return;
}
// password_confirmが存在すれば登録と同じようにハッシュ化したパスワードの置き換え
if (!empty($this->data['User']['password_confirm'])) {
$this->data['User']['password'] = $this->Auth->password($this->data['User']['password_confirm']);
}
if ($this->User->save($this->data)) {
$this->Session->setFlash($this->data['User']['l_name'].'さんの情報を更新しました', 'success');
$this->redirect(array('action' => 'index'));
} else {
$this->Session->setFlash('入力内容に誤りがあります<br />入力内容をご確認ください', 'error');
}
}
if (empty($this->data)) {
$this->data = $this->User->read(null, $id);
}
}
と、こんな感じ。
