add to hatena hatena.comment (0) add to del.icio.us (0) add to livedoor.clip (0) add to Yahoo!Bookmark (0) Total: 0

DAY6: セキュリティーとバリデーション

5日目までのaskeet

5日目でsfFormを使ったログインフォームの実装を行いました。
本家では6日目にバリデーションを行うのですが、すでにsfFormで設定を行っているので、確認しつつ進めていきましょう。

ログインバリデーション

syfmony1.0まではyamlで設定したバリデーションが一般的でした。そのため、アクション部分にバリデーションの処理が含まれていました。
5日目におこなったコーディングのように、symfony1.1以降からはsfFormにてフォームのバリデーションを定義できます。
そのため、アクションの中身はすっきりとし、見通しのよいものになりました。

5日目での実装ではパスワードの認証部分をスルーしていました。
なので、パスワード認証を実装していきましょう。

パスワードフィールドの追加

ask_userテーブルにはパスワードフィールドがありません。
なので、パスワードフィールドを追加することから始めましょう。

やり方は4日目で回答のサンプルデータを投入したときと同じ手順です。

物理的にテーブルにカラムを追加して、スキーマを再作成する方法と、スキーマに変更を加えてSQLを作成し、それを流し込む方法がありますが、ここではスキーマに修正を加える方法を行います。

スキーマの修正

スキーマのUser部分に以下の3項目を追加します。
./config/doctrine/schema.yml

CSS:
  1. User:
  2.   ....
  3.     email: string(100)
  4.     sha1_password: string(40)
  5.     salt: string(32)

モデルの修正

パスワード部分を作成するメソッドを用意します。

./lib/model/doctrine/User.class.php

PHP:
  1. public function setPassword($password)
  2. {
  3.   $salt = md5(rand(100000, 999999).$this->getNickname().$this->getEmail());
  4.   $this->setSalt($salt);
  5.   $this->setSha1Password(sha1($salt.$password));
  6. }

テストデータの修正

テストデータを修正して用意しておきます。
./data/fixtures/test/003.user.yml

CSS:
  1. anonymous:
  2.     nickname:   anonymous
  3.     first_name: Anonymous
  4.     last_name:  Coward
  5.   fabien:
  6.     nickname:   fabpot
  7.     first_name: Fabien
  8.     last_name:  Potencier
  9.     password:   symfony
  10.     email:      fp@example.com
  11.   francois:
  12.     nickname:   francoisz
  13.     first_name: François
  14.     last_name:  Zaninotto
  15.     password:   adventcal
  16.     email:      fz@example.com

コマンドで各変更を反映させる

4日目に用意したdrop.sql, doctrine:build-sql, doctrine:data-load を利用してモデルとデータを再生成します。

* モデルとフォームを再生成する

C:
  1. $ ./symfony doctrine:build-model
  2. $ ./symfony doctrine:build-form
  3. $ ./symfony cc

* テーブルを削除する

C:
  1. $ mysql -uyourname -p askeet <./data/sql/drop.sql
  2. Enter password:

* モデルからテーブルのCreate文を作成する

C:
  1. $ ./symfony doctrine:build-sql
  2. >> doctrine  generating sql for models

* 作成したSQLを読み込む

C:
  1. $ mysql -uyourname -p askeet <./data/sql/schema.sql
  2. Enter password:

* テストデータを再投入

C:
  1. $ ./symfony doctrine:data-load --dir="data/fixtures/test"
  2. >> doctrine  loading data fixtures from "Array"

これで変更作業はおわりです。

ログインフォームのバリデーションの修正

新しく追加したフィールドはフォーム画面で使いませんので、unsetしておきます。

./lib/form/doctrine/LoginUserForm.class.php

PHP:
  1. $unset_fields = array(
  2.                           'created_at',
  3.                           'first_name',
  4.                           'last_name',
  5.                           'email',
  6.                           'sha1_password',
  7.                           'salt',
  8.                           );

あとは、スルーさせていたパスワードチェックを実装します。

./lib/form/doctrine/LoginUserForm.class.php

PHP:
  1. $user = User::getValidUserByNickname($values['nickname']);
  2.     if ($user->count() != 1 || sha1($user[0]->getSalt().$values['password']) != $user[0]->getSha1Password()) {
  3.       throw new sfValidatorError($validator, '該当するユーザーがいません');
  4.       return $values;
  5.     }

これだけで終わりです。簡単ですね。

本家のaskeetでは「アクションからコードを削除します」という作業がありますが、
すでにsfFormに委託されているaskeet1.2ではその作業は必要ありあせん。

アクセス制限

さて、いままでsetLoginAuthで認証を与えていても実際にアクセスできていました。
それは、security.ymlを用意していなかったからです。

というわけで、security.ymlを用意し、質問の追加(question/add)はsubscriberという権限がないと駄目というルールを追加しましょう。

./apps/frontend/modules/question/config/security.yml

CSS:
  1. add:
  2.   is_security: on
  3.   credentials: subscriber
  4.  
  5. all:
  6.   is_secure: off

本家でUserクラスに対して signInとsiginOutを実装するコードがでてきます。
ネーミングが綺麗ですね。というわけで5日目に実装したコードを本家に合わせておきましょう。
ネーミングが悪くてすいません。。

./apps/frontend/lib/myUser.class.php

PHP:
  1. class myUser extends sfBasicSecurityUser
  2. {
  3.   public function signIn($user)
  4.   {
  5.     $this->setAuthenticated(true);
  6.     $this->addCredential('subscriber');
  7.     $this->setAttribute('subscriber_id', $user->getId(), 'subscriber');
  8.     $this->setAttribute('nickname', $user->getNickname(), 'subscriber');
  9.   }
  10.   public function signOut()
  11.   {
  12.     $this->setAuthenticated(false);
  13.     $this->clearCredentials();
  14.     $this->getAttributeHolder()->removeNamespace('subscriber');
  15.   }
  16. }

./lib/form/doctrine/LoginUserForm.class.php

PHP:
  1. // set auth
  2.     $sf_user = sfContext::getInstance()->getUser();
  3.     $sf_user->signIn($user[0]);

./apps/frontend/modules/user/actions/action.class.php

PHP:
  1. public function executeLogout(sfWebRequest $request)
  2.   {
  3.     $this->getUser()->signOut();
  4.     $this->redirect(@homepage);
  5.   }

さぁこれでかなりすっきりしました。

さらに、Userの属性にアクセスするメソッドも本家にあわせて用意しておきます。

./apps/frontend/lib/myUser.class.php

PHP:
  1. ...
  2.   public function getSubscriberId()
  3.   {
  4.     return $this->getAttribute('subscriber_id', '', 'subscriber');
  5.   }
  6.   public function getSubscriber()
  7.   {
  8.     return User::findById($this->getSubscriberId());
  9.   }
  10.   public function getNickname()
  11.   {
  12.     return $this->getAttribute('nickname', '', 'subscriber');
  13.   }

そして、モデルアクセス部分であるUser::findByIdメソッドをモデルに用意しておきます。

./lib/model/doctrine/User.class.php

PHP:
  1. ...
  2.   static public function findById($id)
  3.   {
  4.    return Doctrine::getTable('User')->find($id);
  5.   }

そして、ユーザープロフィールの変更画面へのリンクをレイアウトで以下のように書く事ができます。

./apps/frontend/templates/layout.php

PHP:
  1. <li><?php echo link_to($sf_user->getNickname(). ' profile', 'user/profile') ?></li>

このあたりはDoctrineを使っているので異なる点と、モデルにDQLを集約している点以外は本家askeetと同じですね。

また明日

今日はかなり少なくて済みました。
というのも、5日目にsfFormを使ってバリデーションを実装しているおかげとも言えます。

ここまでのソースは以下のリポジトリからチェックアウトすることができます。
http://svn.1ms.jp/public/symfony/askeet12/tags/release_day_6

関連するその他の記事

Comments

Leave a Reply