GANCHIKU.com

conditionalBasicSecurityFilterってのもアリかな

2009年10月30日

はじめに

conditionalシリーズをもう少し考えてみました。今回はキャッシュではなくて、securityFilterです。

symfonyでは、settings.ymlを用いて、ユーザがログインしていないときに呼び出すアクションを指定することができます。その際に使用するのが、login_moduleとlogin_actionです。

また、ログインはしているが、権限がない(Credentialがない)ときに呼び出すアクションも指定することができます。その際に使用するのが、secure_module, secure_actionです。この二つとsecurity.ymlを使うことによって、is_secureなアクションにログインしていないユーザがアクセスした際に指定したログイン画面を出すことが可能となります。

今回は、この2つを拡張してみることにします。つまり、ユーザがログインしていないときに呼び出すアクションをもう少し条件を付けて、特定のページには違うlogin_actionを渡すようなことです。

問題

確かにsymfonyではlogin_module、login_actionで指定することができるのいいのですが、もっと細かに指定したいときってどうしたらいいんだろうという話を、先日耳にしました。今まで私はそんな需要がなかったので、考えたことはなかったのですが、「私だったらこうやるかな」という方法をいきなり午前1時より作り始めてしまいましたので、その勢いでブログにも書いていますw
secureの方の動作は確認していないのでアレなのですが、loginの方は確認しましたので、これで対応することができたと思います。実装環境は相変わらずsymfony1.3-ALPHA2なのですが、たぶんsymfony1.2系でも動くと思います。

方法

login_module, login_actionなどの指定が実際どこで呼ばれているかを調査しますと、sfBasicSecurityFilter.class.phpの中で使われています。このsfBasicSecurityFilterは、filters.ymlに書かれているsecurityのデフォルトとして使われるfilterです。このsfBasicSecurityFilterを読んでみると、login_module, login_actionは固定値としてsfConfig::get(‘sf_login_module’)のように使われていますので、簡単には変更することができないようになっています。というわけでこれではダメなので、考えてみました。

さて、symfonyのプラグインの中で一番ダウンロードが多いのがsfGuardPluginなのですが、そこでfilterのsecurityFilterを書き換えますよね?securityFilterは変更可能なのですね。というわけで、同様に、今回指定したconditionalBasicSecurityFilterというものをsecurityFilterとして使用できるように指定します。そして、一緒に渡すパラメータもfilters.ymlで指定してみます。yamlで指定する値が増えすぎてわからなくなってしまうかもしれないのですが、conditionalCacheFilterと同じようにpageごとに指定ができるようにしてみました。

ソースコード

/apps/frontend/config/filters.yml

rendering: ~
#security:  ~
security:
  class: conditionalBasicSecurityFilter
  param:
    pages:
      - { module: post, action: new, login: { module: user, action: loginNew } secure: { module: default, action: error403 } }
      - { module: post, action: edit, login: { module: user, action: loginEdit } secure: { module: default, action: error403 } }

# insert your own filters here

cache:     ~
execution: ~
~

/apps/frontend/lib/conditionalBasicSecurityFilter.class.php

class conditionalBasicSecurityFilter extends sfBasicSecurityFilter
{

  public function execute($filterChain)
  {
    if (!$pages = ($this->getParameter('pages', false))) {
      parent::execute($filterChain);
      return;
    }

    foreach ($pages as $page) {
      $module = isset($page['module']) ? $page['module'] : null;

      $action = isset($page['action']) ? $page['action'] : $this->context->getActionName();

      $login = isset($page['login']) ? $page['login'] : null;

      $secure = isset($page['secure']) ? $page['secure'] : null;

      // skip
      if ($module !== $this->context->getModuleName()) {
        continue;
      }

      // skip
      if ($action != $this->context->getActionName()) {
        continue;
      }

      if (isset($login) and !$this->context->getUser()->isAuthenticated()) {
        $loginModule = isset($login['module']) ? $login['module'] : null;
        $loginAction = isset($login['action']) ? $login['action'] : null;
        if (isset($loginModule) and isset($loginAction)) {
          $this->forwardToAction($loginModule, $loginAction);
        }
      }

      $credential = $this->getUserCredential();
      if (isset($secure) and !is_null($credential) and !$this->context->getUser()->hasCredential($credential)) {
        $secureModule = isset($secure['module']) ? $secure['module'] : null;
        $secureAction = isset($secure['action']) ? $secure['action'] : null;
        if (isset($secureModule) and isset($secureAction)) {
          $this->forwardToAction($secureModule, $secureAction);
        }
      }
    }

    parent::execute($filterChain);
  }

  protected function forwardToAction($module, $action)
  {
    $this->context->getController()->forward($module, $action);

    throw new sfStopException();
  }
}

解説

filters.ymlでsecurityFilterで使用するクラスとしてconditionalBasicSecurityFilterを指定します。そして、その中で受け取るパラメータを同時に指定させます。今回はいいサンプルのアイデアがなかったのですが、postモジュールで、アクションがnewするときとeditするときに違うloginフォームを出したい、という要望があったとして考えてみました。つまり、newの時はuserモジュールのloginNewアクションを呼びたいとします。そして、editの時はuserモジュールのloginEditアクションを呼びたいとします。secureは、とりあえずこんな感じで指定できるかな、という感じで書いてみましたので、今回は説明しません。

conditionalBasicSecurityFilter.class.phpに関しては、前回書きましたconditionalCacheFilterと同じロジックでpagesの分だけループで回し、それがloginが必要なものであったり、credentialのチェックが必要なものであった際には、forwardをするというようにしました。これで、newのときには、loginNewにforwardされ、editのときにはloginEditにforwardされるようになります。

まとめ

今回は、そのsfBasicSecurityFilterを拡張してconditionalBasicSecurityFilterを作ってみました。そこでは、モジュールやアクションによって、login_module, login_action,security_module, security_actionを分けて指定ができるようにしました。
今回は、午前1時になっていきなり、思い立ったように書いてみましたので、適当なことを言っているかもしれないのですが、せっかくなので、ソースコードまで落として、さらにブログにも書いてみました。ログインの際のロジックを拡張したい際にはsfBasicSecurityFilterを拡張して対応するというのは、結構使えるのではないかな、と思います。
しかし、ここまで書いた後に、よく考えてみるとこれはアプリケーションを別にして作ればある程度は回避できる問題なんだろうなぁ、と少し思っています。それでも、上に書いたように分けたい場合はこんな感じで対応してみてはどうでしょうか?

そろそろ寝ないと。。。勢いで書いたので、間違っているところもあると思います。それは後ほど修正します。

Shin Ohno 2003-2012