review askeet Day 6
ログインフォームのバリデーション
YAMLに定義
場所: askeet2/apps/frontend/modules/user/validate/login.yml
methods: post: [nickname, password] names: nickname: required: true required_msg: ニックネームの入力が必要です。 validators: nicknameValidator password: required: true required_msg: パスワードの入力が必要です。 nicknameValidator: class: sfStringValidator param: min: 5 min_error: ニックネームは5文字以上の入力が必要です。
エラーの処理を入れる
まずはアクションの処理
場所: askeet2/apps/frontend/modules/user/actions/actions.class.php
public function handleError() { return sfView::ERROR; } public function handleErrorLogin() { return sfView::SUCCESS; }
テンプレートの変更
場所: askeet2/apps/frontend/modules/user/templates/loginSuccess.php
<?php echo form_tag('user/login') ?> <fieldset> <?php use_helper('Validation') ?> <div class="form-row"> <?php echo form_error('nickname') ?> <label for="nickname">ニックネーム:</label> <?php echo input_tag('nickname',$sf_params->get('nickname')) ?> </div> <div class="form-row"> <?php echo form_error('password') ?> <label for="password">パスワード:</label> <?php echo input_password_tag('password') ?> </div> </fieldset> <?php echo input_hidden_tag('referer',$sf_request->getAttribute('referer')) ?> <?php echo submit_tag('サインイン') ?> </form>
ユーザーの認証を付ける
先ほど実装したモノはあくまでも入力の有無を確認するだけで
認証まで行いませんでした。なので、認証機能を付けます。
まずはバリデータを書き直す。
場所: askeet2/apps/frontend/modules/user/validate/login.yml
methods: post: [nickname, password] names: nickname: required: true required_msg: ニックネームの入力が必要です。 validators: [nicknameValidator, userValidator] password: required: true required_msg: パスワードの入力が必要です。 nicknameValidator: class: sfStringValidator param: min: 5 min_error: ニックネームは5文字以上の入力が必要です。 userValidator: class: myLoginValidator param: password: password login_error: このアカウントは存在しないか入力したパスワードが間違っています。
パスワードを保存する
今回はセキュリティ対策も含めて
2重に暗号化します。そして、そのアカウント毎にカギ(salt)を用意します。
そのためDBに二つ記録しておく必要があります。
ということでschema.ymlを書き直す。
場所: askeet2/config/schema.yml
ask_user: _attributes: { phpName: User, idMethod: native } id: { type: integer, required: true, primaryKey: true, autoIncrement: true } nickname: { type: varchar(50), required: true, index: true } first_name: varchar(100) last_name: varchar(100) email: varchar(100) sha1_password: varchar(40) salt: varchar(32) created_at: ~
書き直したら
symfony propel-build-model symfony propel-build-sql symfony propel-insert-sql
setPasswordメソッドを追加する
場所: askeet2/lib/model/User.php
public function setPassword($password) { $salt = md5(rand(100000,999999).$this->getNickname().$this->getEmail()); $this->setSalt($salt); $this->setSha1Password(sha1($salt.$password)); }
テストデータにパスワードを追加する
場所: askeet2/data/fixtures/test_data.yml
User: anonymous: nickname: anonymous first_name: Anonymous last_name: Coward crazyup: nickname: crazyup first_name: Shota last_name: Enomoto password: shota dinotaro: nickname: dino first_name: Taro last_name: Dino password: dino
追加したら
php batch/load_data.php
カスタムバリデータ
場所: askeet2/apps/frontend/lib/myLoginValidator.class.php
<?php class myLoginValidator extends sfValidator { public function initialize($context,$parameters = null) { // 親クラスを初期化 parent::initialize($context); // デフォルトを設定 $this->setParameter('login_error','入力が無効です。'); $this->getParameterHolder()->add($parameters); return true; } public function execute(&$value,&$error) { $password_param = $this->getParameter('password'); $password = $this->getContext()->getRequest()->getParameter($password_param); $login = $value; // anonymousはエラーにする if($login == 'anonymous') { $error = $this->getParameter('login_error'); return false; } $c = new Criteria(); $c->add(UserPeer::NICKNAME,$login); $user = UserPeer::doSelectOne($c); // nicknameが存在するか if($user) { // passwordは合っているか? if(sha1($user->getSalt().$password) == $user->getSha1Password()) { $this->getContext()->getUser()->setAuthenticated(true); $this->getContext()->getUser()->addCredential('subscriber'); $this->getContext()->getUser()->setAttribute('subscriber_id',$user->getId(),'subscriber'); $this->getContext()->getUser()->setAttribute('nickname',$user->getNickname().'subscriber'); return true; } } $error = $this->getParameter('login_error'); return false; } }
アクションからカスタムバリデータを呼び出す
場所: askeet2/apps/frontend/modules/user/actions/actions.class.php
public function executeLogin() { if($this->getRequest()->getMethod() != sfRequest::POST) { // フォームを表示する $this->getRequest()->setAttribute('referer',$this->getRequest()->getReferer()); } else { // フォーム投稿を取り扱う // 最後のページにリダイレクトする return $this->redirect($this->getRequestParameter('referer','@homepage')); } }
アクセス制限する
質問登録画面が登録ユーザーだけ登録できるようにしたいので
質問登録画面はまだ作っていませんが、アクセス制限だけ書く。
場所: askeet2/apps/frontend/modules/question/config/security.yml
add: is_secure: on credentials: subscriber all: is_secure: off
signInとsignOutをメソッド化する。
myUser.class.phpに書く
場所: askeet2/apps/frontend/lib/myUser.class.php
<?php class myUser extends sfBasicSecurityUser { public function signIn($user) { $this->setAttribute('subscriber_id',$user->getId(),'subscriber'); $this->setAuthenticated(true); $this->addCredential('subscriber'); $this->setAttribute('nickname',$user->getNickname(),'subscriber'); } public function signOut() { $this->getAttributeHolder()->removeNamespace('subscriber'); $this->setAuthenticated(false); $this->clearCredentials(); } }
signIn()に置き換える。
場所: askeet2/apps/frontend/lib/myLoginValidator.class.php
<?php class myLoginValidator extends sfValidator { public function initialize($context,$parameters = null) { // 親クラスを初期化 parent::initialize($context); // デフォルトを設定 $this->setParameter('login_error','入力が無効です。'); $this->getParameterHolder()->add($parameters); return true; } public function execute(&$value,&$error) { $password_param = $this->getParameter('password'); $password = $this->getContext()->getRequest()->getParameter($password_param); $login = $value; // anonymousはエラーにする if($login == 'anonymous') { $error = $this->getParameter('login_error'); return false; } $c = new Criteria(); $c->add(UserPeer::NICKNAME,$login); $user = UserPeer::doSelectOne($c); // nicknameが存在するか if($user) { // passwordは合っているか? if(sha1($user->getSalt().$password) == $user->getSha1Password()) { $this->getContext()->getUser()->signIn($user); return true; } } $error = $this->getParameter('login_error'); return false; } }
signOut()に置き換える
場所: askeet2/apps/frontend/modules/user/actions/actions.class.php
public function executeLogout() { $this->getUser()->signOut(); // トップページにリダイレクト $this->redirect('@homepage'); }
よく使うセッション属性もメソッド化
場所: askeet2/apps/frontend/lib/myUser.class.php
public function getSubscriberId() { return $this->getAttribute('subscriber_id','','subscriber'); } public function getSubscriber() { return UserPeer::retrieveByPk($this->getSubscriberId()); } public function getNickname() { return $this->getAttribute('nickname','','subscriber'); }
使う場所を早速直す
場所: askeet2/apps/frontend/templates/layout.php
<div id="header"> <ul> <?php if($sf_user->isAuthenticated()): ?> <li><?php echo link_to('サインアウト','user/logout') ?></li> <li><?php echo link_to($sf_user->getNickname().'のプロフィール','user/profile') ?></li> <?php else: ?> <li><?php echo link_to('サインイン/ユーザー登録','user/login') ?></li> <?php endif ?> <li><?php echo link_to('トップページ','@homepage') ?></li> </ul> <h1><?php echo link_to(image_tag('askeet_logo.gif','alt=askeet'),'@homepage') ?></h1> </div>