_onichannn

【AngularJS/Symfony2.3】AngularJS用のXSRFトークン発行/チェックサービス。

前回のエントリで若干触れたのでペタリ。

<?php

namespace Hoge\FugaBundle\Services;

use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Form\Extension\Csrf\CsrfProvider\SessionCsrfProvider;

class XsrfTokenManager
{
    private $request;
    
    private $provider;
    
    protected $_cookieName = 'XSRF-TOKEN';
    
    protected $_sessionName = 'S-XSRF-TOKEN';
    
    const RECEIVED_HEADER_NAME = 'X-XSRF-TOKEN';
    
    /**
     * Construct
     */
    public function __construct(Request $request, SessionCsrfProvider $provider)
    {
        $this->request = $request;
        $this->provider = $provider;
    }
    
    /**
     * check xsrf token matches
     * 
     * @return boolean
     */
    public function check()
    {
        $_header = $this->request->headers->get(self::RECEIVED_HEADER_NAME);
        
        if (empty($_header) || $_header != $this->create()) {
            
            throw new \Symfony\Component\Security\Core\Exception\AccessDeniedException();
        }
        
        return true;
    }
    
    /**
     * generate strings of xsrf token
     * 
     * @param string $salt
     * @return \Hoge\FugaBundle\Services\XsrfTokenManager
     */
    private function create($salt = 'csrf_token')
    {
        $request = $this->request;
        
        $token = null;
        
        if ($request->getSession() && $request->getSession()->has($this->_sessionName)) {
            $token = $request->getSession()->get($this->_sessionName);
        } else {
            $token =  $this->provider->generateCsrfToken($salt);
            if ($request->getSession()) {
                $request->getSession()->set($this->_sessionName, $token);
            }
        }
        
        return $token;
    }

    /**
     * set cookie for client
     * 
     * @return boolean|\Symfony\Component\HttpFoundation\Cookie
     */
    public function set()
    {
        $cookie = new Cookie($this->_cookieName, $this->create(), 0, '/', null, false, false);
        
        $response = new Response();
        $response->headers->setCookie($cookie);
        $response->send();
        
        return $cookie;
    }
    
    /**
     * setter for cookie name
     * 
     * @param unknown $name
     * @return \Hoge\FugaBundle\Services\XsrfTokenManager
     */
    public function setCookieName($name)
    {
        $this->_cookieName = $name;
        
        return $this;
    }
    
    /**
     * getter for cookie name
     * 
     * @return string
     */
    public function getCookieName()
    {
        return $this->_cookieName;
    }
    
    /**
     * setter for session name
     * 
     * @param unknown $name
     * @return \Hoge\FugaBundle\Services\XsrfTokenManager
     */
    public function setSessionName($name)
    {
        $this->_sessionName = $name;
        
        return $this;
    }
    
    /**
     * getter for session name
     * 
     * @return string
     */
    public function getSessionName()
    {
        return $this->_sessionName;
    }
    
}

これをService.ymlで下記のように定義してやり
※前回のエントリそのまんま。

services:
    xsrf.token.manager:
        class: Hoge\FugaBundle\Services\XsrfTokenManager
        arguments: [@request, @form.csrf_provider]
        scope: request
        tags:
            - { name: xsrf.token.manager }

レスポンスリスナあたりでset()メソッドを実行してやればOK。

services:
    listener.before.filter:
        class: Hoge\FugaBundle\EventListener\ResponseListener
        arguments: [@xsrf.token.manager]
        scope: request
        tags:
            - { name: kernel.event_listener, event: kernel.response, method: createToken }

ResponseListenerは下記の通り。

\Hoge\FugaBundle\EventListener\ResponseListener.php

<?php
namespace Hoge\FugaBundle\EventListener;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Hoge\FugaBundle\Services\XsrfTokenManager;

class ResponseListener
{
    
    protected $manager;
    
    /**
     * Construct
     */
    public function __construct(XsrfTokenManager $manager)
    {
        $this->manager = $manager;
    }
    
    /**
     * before filter event
     */
    public function createToken()
    {
        $this->manager->set();
    }
    
}

あとはトークンチェックが必要なコントローラーのアクション先頭でcheck()メソッドを呼んでやればOK。
※本来はここもリスナーでやったほうがなおベター。

 

【Symfony2.3】自作サービスにRequestオブジェクトを注入したい場合はスコープにリクエストを指定してやること。

標題の通り。

こんな感じ。

services:
    xsrf.token.manager:
        class: Hoge\FugaBundle\Services\XsrfTokenManager
        arguments: [@request, @form.csrf_provider]
        scope: request
        tags:
            - { name: xsrf.token.manager }

ちなみにXsrfTokenManagerはAngularJSがサーバーにリクエストを送る際にくっつけてくるTokenの正当性を評価するサービス。

次のエントリにでも貼っつけとこう。

 

【Symfony2.3】DoctrineのprePersistもしくはpreUpdateイベントをトリガーとするリスナーに対してSecurityContextを注入すると循環参照例外が発生する。

ので注意。

StackOverflowで解決策は唯一containerを注入してそこからSecurityContextを取得するようにするしかないと回答されていたが、公式でも問題として認識していた模様。

http://stackoverflow.com/questions/7561013/injecting-securitycontext-into-a-listener-prepersist-or-preupdate-in-symfony2-to

下記のような投稿が確認出来る。

I had similar problems and the only workaround was to pass the whole container in the constructor

As of Symfony 2.6 this issue should be fixed.

どうやらSymfony2.6でこの問題は解決されるとのこと。公式でも記事を発見。

http://symfony.com/blog/new-in-symfony-2-6-security-component-improvements

ということで2.3環境の自分はおとなしくコンテナを注入してやることにしたのであった。

 

【Git】git push origin master の意味を知る。

すごくわかりやすい纏めがあったのでペタリ。

http://dqn.sakusakutto.jp/2011/10/git_push_origin_master.html

これはわかりやすい。一瞬で理解できた。

 

【PHP】PC、スマホ(Android、iPhone)のUAを簡単に切り替えてWebページにアクセス出来るSocketクラスを作った。

超絶自分用。

<?php

class Socket {
    
    private $connect;
    
    private $ua;
    
    private $url;
    
    private $redirectCount;
    
    public function __construct()
    {
        $this->connect = curl_init();
    }
    
    public function get($autoReboot = true)
    {
        $this->setOptions();
        
        $contents = curl_exec($this->connect);
        
        $this->redirectCount = curl_getinfo($this->connect, CURLINFO_REDIRECT_COUNT);
        
        curl_close($this->connect);
        
        if ($autoReboot) {
            $this->reboot();
        }
        
        return $contents;
    }
    
    public function setUrl($url)
    {
        $this->url = $url;
        
        return $this;
    }
    
    /**
     * default user agent
     * @return Socket
     */
    public function setPcUa()
    {
        $this->ua = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.111 Safari/537.36';
        
        return $this;
    }
    
    /**
     * custom user agent
     * @param string $isAndroid
     * @return Socket
     */
    public function setSpUa($isAndroid = false)
    {
        if ($isAndroid) {
            $this->ua = 'Mozilla/5.0 (Linux; U; Android 4.1.1; ja-jp; Galaxy Nexus Build/JRO03H) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30';
        } else {
            $this->ua = 'Mozilla/5.0 (iPhone; CPU iPhone OS 8_0_2 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Version/8.0 Mobile/12A405 Safari/600.1.4';
        }
        
        return $this;
    }
    
    public function reboot()
    {
        $this->connect = curl_init();
        $this->setPcUa();
        $this->resetUrl();

        return $this;
    }
    
    public function getLastRedirectCount()
    {
        return $this->redirectCount;
    }
    
    private function resetUrl()
    {
        $this->url = null;
    }
    
    private function setOptions()
    {
        $this->validation();
        
        curl_setopt($this->connect, CURLOPT_URL, $this->url);
        curl_setopt($this->connect, CURLOPT_USERAGENT, $this->ua);
        curl_setopt($this->connect, CURLOPT_CONNECTTIMEOUT, 5);
        curl_setopt($this->connect, CURLOPT_TIMEOUT, 10);
        curl_setopt($this->connect, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($this->connect, CURLOPT_FAILONERROR, true);
        curl_setopt($this->connect, CURLOPT_FOLLOWLOCATION, true);
        curl_setopt($this->connect, CURLOPT_AUTOREFERER, true);
    }
    
    private function validation()
    {
        try {
            if (empty($this->url)) {
                throw new RuntimeException();
            }
        } catch (Exception $e) {
            die('Please set the url.');
        }
    }
    
}

使い方は下記の通り。

$socket = new Socket();

$url = 'http://www.yahoo.co.jp';

$result = $socket->setUrl($url)->get();

// リダイレクトカウントも取れるよ
$socket->getLastRedirectCount()

まぁメリットはcurlを使っているのでfile_get_contents()よりも速いよってくらいかな。

 

【PHP】Curlでページを取得した際のリダイレクト回数を取得する。

curl_getinfo()を用いる。

// 通信の際、CURLOPT_FOLLOWLOCATIONを有効にしてなければならない点に注意
$redirectCount = curl_getinfo($ch, CURLINFO_REDIRECT_COUNT);

ほかにも色々と取得出来るので詳しくは公式を参照されたし。

http://php.net/manual/ja/function.curl-getinfo.php

 

【PHP】任意の長さの文字列を生成する。

テストケースとか書いてる時、バリデーションに引っ掛けたくて超長い文字列(65000文字とか)が欲しかったので調べてみた。

冷静に考えてループで作成するのは重すぎるので却下。

■文字列のオフセットを指定して作成

$str = 'a';
$str[99] = 'a';
// 100バイトの文字列が出来上がる

■sprintfを用いて作成

$str = sprintf("%'065501s", 0)
// 65501バイトの文字列が出来上がる

個人的にはsprintfを用いたほうがエレガントで好き。

 

【PHP】文字列の中に指定文字列が何回出現するかを数える。

こんな便利な関数があったことを今更知った。

$text = 'This is a test';
echo substr_count($text, 'is')
// 2

細かい仕様と使い方は公式を参照されたし。

http://php.net/manual/ja/function.substr-count.php