Pages

2018年11月21日水曜日

[CakePHP3] 管理画面とそれ意外でCSRF用のCookieName(クッキー名)を変更したい

/src/Application.php のデフォルト指定をまず削除。
本当は設定を更新したかったが、方法が見つからず。。

$middlewareQueue->get(3) からできるかなと思ったけど無理だよね。。
てか名前も指定できるようになってたらいいのに。。
->add した瞬間にこれ実行してるのかな。。

まーとりあえず以下の方法で対応しました。

public function middleware($middlewareQueue)
{
    $middlewareQueue
        // Catch any exceptions in the lower layers,
        // and make an error page/response
        ->add(ErrorHandlerMiddleware::class)

        // Handle plugin/theme assets like CakePHP normally does.
        ->add(AssetMiddleware::class)

        // Add routing middleware.
        // Routes collection cache enabled by default, to disable route caching
        // pass null as cacheConfig, example: `new RoutingMiddleware($this)`
        // you might want to disable this cache in case your routing is extremely simple
        ->add(new RoutingMiddleware($this, '_cake_routes_'))

        // Add csrf middleware.
        ->add(new CsrfProtectionMiddleware([
            'cookieName' => 'csrf',
            'secure' => true,
            'httpOnly' => true,
        ]));

    return $middlewareQueue;
}

上記がデフォルトの内容となりますが、これから

// Add csrf middleware.
->add(new CsrfProtectionMiddleware([
    'cookieName' => 'csrf',
    'secure' => true,
    'httpOnly' => true,
]));

の部分を削除します。

public function middleware($middlewareQueue)
{
    $middlewareQueue
        // Catch any exceptions in the lower layers,
        // and make an error page/response
        ->add(ErrorHandlerMiddleware::class)

        // Handle plugin/theme assets like CakePHP normally does.
        ->add(AssetMiddleware::class)

        // Add routing middleware.
        // Routes collection cache enabled by default, to disable route caching
        // pass null as cacheConfig, example: `new RoutingMiddleware($this)`
        // you might want to disable this cache in case your routing is extremely simple
        ->add(new RoutingMiddleware($this, '_cake_routes_'));

    return $middlewareQueue;
}

するとこうなる。

/src/Application.php で分岐させても良いけど、Pluginのための分岐なので、
分岐はPlugin側でやる事に。
/plugins/Manage/config/bootstrap.php

ちなみに /plugins/Manage は管理画面用に作成したプラグイン。

plugins/Manage/config/bootstrap.php を更新。

以下の分岐を作成していたので、その中で

use Cake\Event\EventManager;
use Cake\Http\Middleware\CsrfProtectionMiddleware;

$cookieName = 'csrf';
if (!empty($_SERVER['REQUEST_URI']) && preg_match('/^\/manage*/', $_SERVER['REQUEST_URI'])) {
    $cookieName = 'mcsrf';
}

EventManager::instance()->on(
    'Server.buildMiddleware',
    function ($event, \Cake\Http\MiddlewareQueue $middlewareQueue) use ($cookieName) {
        $middlewareQueue->add(new CsrfProtectionMiddleware([
            'cookieName' => $cookieName,
            'secure' => true,
            'httpOnly' => true,
        ]));
    });

のように更新すればOK。

で、確認してたらどうも csrf、mcsrf の両方が登録されてしまう。

何故なんだろうとハマっていたら DebugBar のAjax?で呼んでいる様子。
なので、 DEBUG false にして確認したら無事 /manage 以下では mcsrf だけが利用されており、 /manage と それ以外 で別のクッキー値を参照するようになりましたとさ。

2018年11月20日火曜日

[CakePHP3] セッション(Session)のモデル(テーブル)を通常と特定ディレクトリで分ける方法

SessionのストレージをDatabaseとしている事として以下をメモしています。

管理画面(/manage/*) と その他(/manage/*以外) でセッションデータを保存するDBテーブルを分ける方法。

管理画面(/manage/*) はプラグイン(Plugin)機能を利用して作成。

config/bootstrap.php からプラグインを呼び出す。

Plugin::load('Manage', ['bootstrap' => true, 'routes' => true]);

この時に 'bootstrap' => true を忘れないように。

plugins/Manage/config/bootstrap.php ファイルを作成し、中身は

<?php
use Cake\Core\Configure;

if (!empty($_SERVER['REQUEST_URI']) && preg_match('/^\/manage*/', $_SERVER['REQUEST_URI'])) {
    Configure::write('Session.handler.model','ManageSessions');
    Configure::write('Session.cookiePath','/manage/');
}

DBテーブルは分けるので
デフォルトの sessions と manage_sessions を用意する事を想定する。

CREATE TABLE `sessions` (
  `id` char(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `created` datetime DEFAULT CURRENT_TIMESTAMP, -- optional, requires MySQL 5.6.5+
  `modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, -- optional, requires MySQL 5.6.5+
  `data` blob DEFAULT NULL, -- for PostgreSQL use bytea instead of blob
  `expires` int(10) unsigned DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `manage_sessions` (
  `id` char(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `created` datetime DEFAULT CURRENT_TIMESTAMP, -- optional, requires MySQL 5.6.5+
  `modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, -- optional, requires MySQL 5.6.5+
  `data` blob DEFAULT NULL, -- for PostgreSQL use bytea instead of blob
  `expires` int(10) unsigned DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

2018年11月7日水曜日

[CakePHP3] オリジナルのログイン認証を作成

ちょっとややこしい要件が発生した。

ログインIDが1つでユニークではなく、店舗コード+社員コード という感じで2つのカラムでユニークとなる内容でした。

DBテーブルはこんな感じとする。

table: users
columns: id, shop_id, code, password

table: shops
columns: id, shop_id, code

そして、リクエスト値はこんな感じとする。

code, shop_code, password


やりたいSQLとしては

SELECT *
FROM users
INNER JOIN shops ON users.shop_id = shops.id
WHERE users.code = :code AND shops.code = :shop_code;

で取得してからパスワードチェックするというイメージ。

$this->loadComponent('Auth', [
    'loginAction' => [
        'controller' => 'Users',
        'action' => 'login',
    ],
    'authenticate' => [
        'Form' => [
            'userModel' => 'Users',
            'fields' => [
                'username' => 'username',
                'password' => 'password',
            ],
            'contain' => 'Shops', // 3.1 で非推奨
            'scope' => [], // 3.1 で非推奨
            'finder' => 'login',
        ],
    ],
    'authError' => 'ログインをして、ご利用ください。',
    'loginRedirect' => [
        'controller' => 'Users',
        'action' => 'index'
    ],
    'logoutRedirect' => [
        'controller' => 'Users',
        'action' => 'login'
    ]
]);

って感じでやりたいけど、shop_id を渡せない。。
scope や finder 使ってWHEREに固定した条件を追加する事はできるけど username を追加みたいな事ができない。。

で仕方ないので、オリジナルの Authenticate クラスを作成する事にする。

/src/Auth/UserAuthenticate.php

を作成。

中身は

getTableLocator()->get('Users');

        $user = $userTable->query()
            ->contain(['Shops'])
            ->where([
                'Users.code' => $request->getData('code', ''),
                'Shops.code' => $request->getData('shop_code', ''),
            ])
            ->first();

        $password = $request->getData('password', '');
        if ($this->passwordHasher()->check($password, $user->password)) {
            return $user->toArray();
        }

        return false;
    }
}

作成したら、

$this->loadComponent('Auth', [
    'loginAction' => [
        'controller' => 'Users',
        'action' => 'login',
    ],
    'authenticate' => [
        'Form' => [
            'userModel' => 'Users',
            'fields' => [
                'username' => 'username',
                'password' => 'password',
            ],
            'contain' => 'Shops', // 3.1 で非推奨
            'scope' => [], // 3.1 で非推奨
            'finder' => 'login',
        ],
    ],
    'authError' => 'ログインをして、ご利用ください。',
    'loginRedirect' => [
        'controller' => 'Users',
        'action' => 'index'
    ],
    'logoutRedirect' => [
        'controller' => 'Users',
        'action' => 'login'
    ]
]);

の記述を

$this->loadComponent('Auth', [
    'loginAction' => [
        'controller' => 'Users',
        'action' => 'login',
    ],
    'authenticate' => [
        'User' => [],
    ],
    'authError' => 'ログインをして、ご利用ください。',
    'loginRedirect' => [
        'controller' => 'Users',
        'action' => 'index'
    ],
    'logoutRedirect' => [
        'controller' => 'Users',
        'action' => 'login'
    ]
]);

と修正して完了。

2018年11月6日火曜日

[CakePHP3] パスワードのバリデート (入力 値チェック)

基本的には bake で追加されるメソッド名でのバリデート形式の指定を行うが、正規表現を2回利用したい場合。

$validator
      ->minLength('password', 8, __('6文字以上を入力してください。'))
      ->maxLength('password', 50, __('20文字以下を入力してください。'))
      ->alphaNumeric('password', 'alphaNumeric', __('半角英数字のみ入力可能です。'))
     ->regex('password', '/[a-zA-Z]+/', __('半角英字を1文字以上利用してください。'))
     ->regex('password', '/[0-9]+/', __('半角数字を1文字以上利用してください。'))
      ->requirePresence('password', 'create')
      ->notEmpty('password');

こんな場合は上書きされてしまって、半角英字チェックは動かないので、
こういう場合のみ add で名前を変えて指定するようにして重複を回避する。

$validator
      ->minLength('password', 8, __('6文字以上を入力してください。'))
      ->maxLength('password', 50, __('20文字以下を入力してください。'))
      ->alphaNumeric('password', 'alphaNumeric', __('半角英数字のみ入力可能です。'))
      ->add('password', [
          'inAlpha' => [
              'rule' => ['custom', '/[a-zA-Z]+/'],
              'message' => __('半角英字を1文字以上利用してください。'),
          ],
          'inNumber' => [
              'rule' => ['custom', '/[0-9]+/'],
              'message' => __('半角数字を1文字以上利用してください。'),
          ],
      ])
      ->requirePresence('password', 'create')
      ->notEmpty('password');

  $validator
      ->notEmpty('password_conf')
      ->sameAs('password_conf', 'password', __('異なるパスワードが入力されています。'))
      ->requirePresence('password', 'create')
      ->notEmpty('password');

最初からこの記述を基本にすれば良いと思っていたけどやっぱり bake を基本にした方が効率が良い場面が多いので、
必要な場合のみ利用するようにしました。

regex メソッドの中を見ると解るけど rule としては custom という落とし穴に注意。

[CakePHP3] function _setPassword() が思ったタイミングで動いてくれない

$entity = $this->Samples->get($id);
$entity->password = '12345678';
$this->save($entity);

$entity である

  class Sample extends Entity



  protected function _setPassword($password)

があれば、実行されるのかと思ったいたけどダメでした。。

  $entity->set('password',  '12345678');

でもダメ。。

で、色々試すとここで実行されました。

  $this->Samples->patchEntity($entity, ['password' => '12345678'])

何故この場合だけにしているかは謎ですが、とにかく値を更新する時は必ずこれを利用するようにした方が、
バグが減りそうというメモでした。

2018年11月2日金曜日

[CakePHP3] 文字列型のDBカラムに配列を保存する方法

こんな便利な機能があったとは。。
もっと早く気づきたかった。。

CakePHP3 には文字列カラムに配列をJSON形式などに変換して保存し、取り出し時にまた配列に戻してくれる方法がありました。

_initializeSchema( Cake\Database\Schema\TableSchema $schema )
https://api.cakephp.org/3.6/class-Cake.ORM.Table.html#__initializeSchema

使い方

1.保存したいテーブルの Model にメソッドを追加します。

protected function _initializeSchema(\Cake\Database\Schema\TableSchema $schema)
{
    $schema->setColumnType('categories', 'json');
    return $schema;
}

ここでは カラム名:categories にMultiCheckboxなどで配列で得られる値を JSON形式 で保存する事を想定してみました。

使い方は以上です。

簡単ですね。
今まで知らずに自作してた。。

2018年6月11日月曜日

CakePHP3 + PhpStorm + Compass の設定

最新の CakePHP3 では

 composer install

すると

 src/Sass
 config.rb

が作成される様子なので、このに準ずる事とする。

無ければ以下で作成する

composer.json があるプロジェクトディレクトリ内に移動して初期設定のコマンドを実行。

$ compass create --sass-dir "src/Sass" --css-dir "webroot/css" --javascripts-dir "webroot/js" --images-dir "webroot/img"

src/Sass/screen.scss
 src/Sass/print.scss
 src/Sass/ie.scss
 webroot/css/ie.css
 webroot/css/print.css
 webroot/css/screen.css

が作成されるのが嫌って人は --bare オプションを追加して。

$ compass create --bare --sass-dir "src/Sass" --css-dir "webroot/css" --javascripts-dir "webroot/js" --images-dir "webroot/img"

 src/Sass
 config.rb

が作成されるはず。(他は元からあるはず。)

config.rb の中身はテキストなので確認する。

後でやっぱり

 src/Sass/screen.scss
 src/Sass/print.scss
 src/Sass/ie.scss
 webroot/css/ie.css
 webroot/css/print.css
 webroot/css/screen.css

を作成したいって人は。

$ compass init

を実行すればOK。

--bare の場合はまだ作成されないけど、コンパイルが実行されると

.sass-cache

が作成されるはずなのでこれは .gitignore に追加してバージョン管理されないように。


ここかまでが無ければの人の下ごしらえ。
で、ここから phpstorm の設定方法。

1.phpstorm の設定ウィンドウを開く

 File > Settings (Ctrl + Alt + s)


2.Compass の設定画面に移動する。

 Languages & Frameworks > Sytlesheets > Compass

 この設定はコンパイルの自動化だけなら必要無し。
 恐らく何か便利機能のため。
 1つしかできないので管理用画面と会員用画面みたいな複数で config.rb を分けたい時は多分どうしようもない。。


3.Enable Compass support にチェックを入れる。

自動的に Compass executable file: と Config Path: は設定されるはず。

 Compass executable file: compass コマンドのパス
 Config Path: config.rb のパス


4.File Watchers の設定画面に移動する。

 Tools > File Watchers


5.設定を追加する。

 右に緑の十字アイコンの Add(追加)ボタン をクリックして Compass SCSS を選択。


6.以下の内容を設定

デフォルトである程度適切に設定されてるはずだが、 Arguments: だけ変更。

 File type: SCSS Style Sheet
 Scope: Project Files
 Program: compass or compass.bat のパス
 更新 Arguments: compile
 Output paths to refresh: 空
 Working directory: config.rb を置いているディレクトリパス

※ 動作確認したい場合は Show console: Always に変更して確認でもOK。


以上!

2018年5月20日日曜日

Cakephp3 でPOST値をGET値で上書き

POST値を更新するなんて場面そう無いだろうけど、一応メモ。

以前は
$this->request->data = $this->request->params;

みたいな書き方でいけてたはずだけど、今はNGみたい。

マニュアルにも無いし、そんな場面作るなよって意味だろうけど以下ならできた。

$params = $this->getRequest()->getQueryParams();
$request = $this->getRequest()->withParsedBody($params);
$this->setRequest($request);


2018/11/06 追記

https://book.cakephp.org/3.0/ja/core-libraries/form.html
このページに「値はリクエストメソッドが GET の時にのみ定義されるべきで、 さもないと正しくないまたは保存されていない直前の POST データを上書きしてしまいます。」
と記載があるので、

  $this->request->withData('name', 'John Doe');
  
を利用するのが良いのかもしれません。。
今は get*** と修正されていっているので、

  $this->getRequest()->withData('name', 'John Doe');

かな。
Symfonyのコンポーネントだけ使ってメールアドレスが正しいかをチェックする

既存システムの改修を行う際にメールアドレスのチェックを強化したいけど、
フレームワークを使ってなかったので、composer で Symfony のバリデーションだけ入れて対応。

まずはコンポーネントを追加
$ composer require symfony/validator
※ 別要件で、 /vendor/autoload.php は読み込み済み。

後は、

http://symfony.com/doc/current/validation.html

を見ながら下の感じで。

use Symfony\Component\Validator\Validation;
use Symfony\Component\Validator\Constraints\Email;


$validator = Validation::createValidator();
$violations = $validator->validate($email, array(
    new Email(array(
        'message' => 'The email "{{ value }}" is not a valid email.',
        'strict' => true,
        'checkHost' => true,
    )),
));

// エラー有無のチェック
if (0 !== count($violations)) {
 // 有 の場合
 foreach ($violations as $violation) {
    echo $violation->getMessage();
 }
} else {
 // 無 の場合
}

利用可能なオプション

  • strict
  • message
  • checkMX
  • checkHost
  • payload
今回は strict、message、checkHost だけ利用。

2018年1月15日月曜日

Cakephp3 CallbackStream を利用したCSVダウンロード

CakePHP3 にいつからか(3.0からあったっけ?) CallbackStream ってのがあったのでサンプル作成してみました。

https://book.cakephp.org/3.0/en/controllers/request-response.html

use Cake\Http\CallbackStream;

public function foo()
{
    $items = $this->items->find()->limit(10);

    $this->response->type(['csv' => 'text/csv']);
    $this->response = $this->response->withType('csv');
    $this->response = $this->response->withDownload('foo.txt');

    $stream = new CallbackStream(function () use ($items) {
        foreach ($items as $item) {
            echo $item->id . ',' . $item->name . "\n";
        }
    });
    $this->response = $this->response->withBody($stream);
    return $this->response;
}

メモリー利用方法までは確認できていませんが、そこが問題無ければフレームワークに用意されているのであれば積極的に利用した方がよさそうですね。
ちなみにヘッダー出力は $this->response->withHeader() を利用しなさいって事になってるようです。

Followers