自動化

CakePHPのACLを効率的に管理する

CakePHP Advent Calendar 2011の3日目です。

@kara_dが担当させていただくことになりました。

今回はACLについて書こうと思います

ACLは、僕にとって結構CakePHPを選ぶポイントの1つで、気に入っています。
Play Frameworkにもあればいいのにって思ったりします。

ACLってなんだっけ?って方は、
ACL を制御するシンプルなアプリケーション
このチュートリアルで遊んでみてください。

そうそう、今回の記事はバージョン1.3系の話です。2.0系のACLは使った事がないですが、きっとよいでしょう。

ところで、ACLを結構使っていると言うと、俺も使ってるぜってあまりならないことが多いです。
多くの場合、

  • なんか複雑そうだったのでオレオレACLを使っている
  • そもそもプロジェクトでそういう複雑なパーミッション管理が必要でない

という感じではないでしょうか?

しかしCakePHP標準のACLは非常に便利な上に、使うことで生じるメリットがあります。

  • デフォルトで組み込まれているためメンテされており、安心
  • 既に多くの使用実績があり、なかなか厳格
  • それほど多くはないがノウハウがWeb上にある

ということで、積極的な利用をおすすめしたいです。

ACLの管理をコードでする理由

ACLと一口に言っても、その管理の方法は様々なスタイルがあり、よく使われているものにACLプラグインというのがあります。

Alaxos – CakePHP – Plugin ACL

これは、Webの画面からACLやAROのビルドやパーミッション設定が出来るというものです。非常に便利で、特にパーミッション設定などは、一覧で何が出来てなにが出来ないかを把握するのに役立ちます。
ただし、僕はこのプラグインは限定的に利用するにとどめています。

ACLプラグインは、ACL周りの設定には使用せず、現状のACLがきちんとパーミッション設定されているかの確認のみに使用します。

何故でしょうか?

実は最初の頃ACLの管理にはこのプラグインを使っていました。
しかし、CakePHPを使ったプロジェクトが大きくなるにつれて、複数人数で開発を行うようになり、ACL設定の漏れやマージミスが発生したのです。

そこで、至った結論が、「ACLはコードで管理する」というものでした。

ACLのパーミッションの管理方法について

CakePHPには、ACLの設定をコードで行う事が出来るAPIが用意されています。

例えば、Pagesコントローラーのindexについてアクセス許可をしたい場合、

$this->Acl->allow($group, "controllers/Pages/index");

という書き方で対象のグループに許可を与える事が出来ます。だいたい、僕が使うグループは、管理者グループ、一般ユーザーグループ、特殊ユーザーグループ(プレミア会員など)みたいな感じでユーザーを設定しておいて、それぞれに許可を出します。

アクセスを禁止する場合は、

$this->Acl->deny($group, "controllers/Pages/index");

denyメソッドを使います。

もちろん、これはコントローラー側でACLコンポーネントを使用するという前提の話です。
こういった感じで、各アクションのパーミッション設定をしていきます。
CakePHPでは、プラグインのアクションにもパーミッションが設定できるようになっており、

$this->Acl->allow($group, "controllers/MyCategory/Categories/index");

みたいな書き方でMyCategoryプラグインのCategoriesコントローラーのindexアクションの設定が行えます。

また、

$this->Acl->allow($group, "controllers/Pages");

という書き方をすると、該当コントローラーのパーミッションを一度に設定することも出来ます。

これらを使って、コードとしてコントローラーに書いておくことで、

  • コードとして書かれているため、Gitなどで共有できる
  • 間違いがあった場合、お互いに修正できる
  • 新しい環境での構築が楽

上記のメリットがあります。

効率的な管理

ただし、プロジェクトが進むと、アクションとグループの数が増えて、ACLのコードが膨大になったりします。
1000行超えとかあります。

さて、こういうアクション毎に設定していった大量のACLを整理するにはどうしたらよいでしょうか?
考え方のベースとしては、ACLの一括設定を有効に使っていくというのが考えられます。

スタイルとしては2つで、許可を行うものの一覧を表すホワイトリスト形式と、許可しないものの一覧を列挙するブラックリスト方式です。

ホワイトリスト形式は、こんな感じで、

$this->Acl->deny($group, "controllers/Pages");
$this->Acl->allow($group, "controllers/Pages/about");
$this->Acl->allow($group, "controllers/Pages/test");
$this->Acl->allow($group, "controllers/Pages/contact");

一旦全てを許可しない状態に設定したあと、許可するものを指定していく方式です。
許可するものを視覚化できるので、一般ユーザーなどの管理に使えます。書かなかったアクションは許可されないからです。

ブラックリスト形式は、逆に、

$this->Acl->allow($group, "controllers/Pages");
$this->Acl->deny($group, "controllers/Pages/about");
$this->Acl->deny($group, "controllers/Pages/test");
$this->Acl->deny($group, "controllers/Pages/contact");

こういう風に、全てを許可した上で、禁止するアクションのみを指定していきます。
この方式は管理者ユーザーのような、大多数の部分にはアクセスできるけれど、一般ユーザーの登録みたいなアクセスされるとなんらかの不整合が起きたりするところへ禁止する形に向いています。
ブラックリスト方式の注意点は、新しくアクションが追加されたことを知らずに、ACLを再構築してしまうと、暗黙のうちに許可されてしまう点です。

これらを駆使して、ACLのパーミッションコードを書いていくと、結構縮まります。

ということで、良いACLライフを!!

明日は@shin1x1さんです。よろしくお願いします。

Read More

Play Frameworkのジョブ確認にCapsロックのLEDを使う!!

12月1日より始まったPlay! framework Advent Calendar 2011 jp #play_ja

第1回目は、PlayFrameworkの東京勉強会でも主催された @ikeike443 さんでした。

Play! framework 概要 Tipsもあるよ! – 複雑系スパゲティソース(はてな版)

僕は2日目を担当させていただきます。

今回のテーマは、
「Play Frameworkのジョブ確認にCapsロックのLEDを使う!!」
です。

いきなり、ニッチすぎる。。

ジョブっていつ実行されてるかわかんないですよね。ログとかターミナル見るのも面倒だし。手元で確認できたらいいよね、みたいな。ただ、今回の場合はローカルで実行しているPlayのみ対応です。サーバーで動いているジョブでも対応させようとしたら、なんらかのAPIを用意してやる感じになるでしょう。

@ikeike443 さんのブログにもありましたが、Play Frameworkのジョブは、play.jobs.Jobというクラスを継承して作ります。
ジョブには、@OnApplicationStartというアプリケーション起動時になにかを行うタイプのジョブと、Cronみたいに指定したタイミングで動作させるタイプのものがあります。

Cronみたいに使う際は、@On(“*/30 * * ? * * *”)みたいな感じで使います。この例では、30秒に1回起動する形になります。このあたりはCronとかと似ているので理解しやすいと思います。

Cronの指定をソースコード内に書く事で、Gitとかでバッチ処理のコードも管理することが出来るため、アプリケーションの一元管理をしやすくなります。PHPのときは、Cron用のファイルを用意したりしてました。
Play Frameworkのジョブを使う際には注意しなければいけない点として、DEVモードで起動している場合は一度どこかのページにアクセスする必要があるということがあります。

さて、本編に入る前にPlayと僕的なハナシをしようと思います。

Play Frameworkを使い始めたのは、play-1.0.3.2からです。ただ、その頃はちょっと触っただけでやめてしまったので、本格的に触り出したのは、1.1からです。自分の仕事ではずっとデザイントレンドのリサーチをライフワークとしており、これはCakePHPベースで作ってきたのですが、Play Frameworkでリニューアルしはじめたところから始まりました。

本編に戻ります。

僕はメイン機にMacBook Airを使っているんですが、US配列のキーボードを使っており、おまけにCapsロックキーをCtrlキーにしてしまっていて、Capsロックの出番は皆無です。というか使った事がありません。

しかしながら、Capsロックには、LEDがついており、MacBook Airのキーボードでも唯一といっていいコンピューターからのフィードバックがあるエリアなのです。

ここを有効活用したいと思い、今回はジョブを実行したら点滅させるということを目指します。ググると、いました!! 先人が。

Official Google Mac Blog: Manipulating keyboard LEDs through software

このソフトウェアは、Cで書かれていて、コンパイルをして使用します。
コンパイルにはXCodeが必要です。

karad @ device-f205b8 : ~
$ gcc -Wall -o keyboard_leds keyboard_leds.c \
>         -framework IOKit -framework CoreFoundation

こんな感じでコンパイルを終えたらテストをしてみます。
まずは、実行権限を与えて、

karad @ device-f205b8 : ~
$ chmod 707 ./keyboard_leds

-rwx---rwx    1 karad  staff           15296 11 21 10:41 keyboard_leds
-rw-r--r--@   1 karad  staff           11541 11 21 10:41 keyboard_leds.c

-hオプションをつけて、使い方を確認します。

karad @ device-f205b8 : ~
$ keyboard_leds -h

keyboard_leds (version 0.1)
Copyright (c) 2007,2008 Amit Singh. All Rights Reserved.
Manipulate keyboard LEDs

Usage: keyboard_leds [OPTIONS...], where OPTIONS is one of the following

  -c[1|0], --capslock[=1|=0] get or set (on=1, off=0) caps lock LED
  -h,      --help            print this help message and exit
  -n[1|0], --numlock[=1|=0]  get or set (on=1, off=0) num lock LED

Cオプションを使うことでLEDを点灯させたり出来るみたいですね。
ということで実験。

karad @ device-f205b8 : ~
$ keyboard_leds -c1

off
on

すると、、、

P2

おお、ついた(笑

karad @ device-f205b8 : ~
$ keyboard_leds -c0

on
off

とやると、、、

P1

消えた(笑

ただし、このままではつけるか消えるしかないので、点滅させてみましょう。
こちらはシェルスクリプトでやります。Play Framework側からアクセスしやすくする意味もあります。

#!/bin/bash
$1/keyboard_leds -c1
sleep 0.1
$1/keyboard_leds -c0
sleep 0.1
$1/keyboard_leds -c1
sleep 0.1
$1/keyboard_leds -c0
sleep 0.1
$1/keyboard_leds -c1
sleep 0.1
$1/keyboard_leds -c0
sleep 0.1

exit;

なんともベタな書き方ですが、今回はこんな感じでいきます。
このシェルスクリプトは引数をひとつとります。引数はkeyboard_leds自体へのパスです。
実行させると、ピカピカと点滅を3回繰り返します。

さて、ようやくPlay Framework側から使ってみます。
まずは、app/libというパッケージを用意して、その中にFlashLightというクラスを作ります。
このクラスを様々なコントローラーやジョブから使おうというわけです。

package lib;

public class FlashLight {
    public static void on(String appPath) {
        Runtime r = Runtime.getRuntime();
        try {
            Process p = r.exec(appPath + "/keyboard_leds.sh " + appPath);
            Thread.sleep(1000);
            System.out.println("LED is ok.");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

FlashLight.onというスタティックメソッドを用意しました。
onメソッドの引数としてkeyboard_ledsへのパスをとる形にしています。

これをテストしてみましょう。
テストコードはこんな感じですが、こういうのはどうテストするのかよくわかりません。とりあえず光らせるだけやってみる感じで。。

import org.junit.*;
import play.test.*;
import lib.*;

public class FlashLightTest extends UnitTest {

    @Before
    public void setUp() {
        //
    }

    @Test
    public void testFlashLightOnOff () {
        try {
            FlashLight.on("/Users/hogehoge");
            Thread.sleep(2000);
            FlashLight.on("/Users/hogehoge");
            Thread.sleep(2000);
            FlashLight.on("/Users/hogehoge");
            Thread.sleep(2000);
            FlashLight.on("/Users/hogehoge");
            Thread.sleep(2000);
        } catch (Exception e) {
            e.printStackTrace();
            fail(e.getMessage() + " is expected");
            return;
        }
    }
}

4回光るはずですが、光らない事があります。
いやな予感がしますね。ただ、今回はネタなので、気にせず進めます。

続いて、何かのページを表示させたらCapsロックのライトを光らしてみましょう。
適当なコントローラにこんな感じで、表示時に呼び出すように書きます。

public static void index() {
    // 表示データの取得
    entities = Model.getEntries().orderBy("modified DESC");
    FlashLight.on("/Users/hogehoge");
    renderTemplate("Sites/index.html", entities);
}

すると、当たり前ですが、光ります。ということで、続いてジョブに組み込んでみます。
組み込むというか差し込むだけですが。

@On("*/30 * * ? * * *")
public class MyJob extends Job {

    @Override
    public void doJob() {
        boolean result = false;
        System.out.println("My job start.");
        FlashLight.on("/Users/hogehoge");
        result = captureOverseas();
    }
}

ということで、無事ジョブが実行されるとCapsロックキーが点滅しますが、点滅しないこともあります。これは想定済みではあるのですが、どうも気分が悪いです。おそらく独立したプロセスで呼んでいるライブラリが、同じ事をしようとしておかしなことになっているような気がします。そこで、たぶんですが、もうちょっとちゃんと監視してごにょごにょするとよくなりそうです。

今回はこれで終わりにします。
ではでは。

Play! framework Advent Calendar 2011 jp3日目は @kitora_naoki さんです。

よろしくお願いします!!

Read More

[Play!][CakePHP]Selenium周りを整備する

Seleniumに関しては、前々からちょこちょこと意識していたものの、本格的に使っていこうと思い出したのはPlayFrameworkを使い始めてから。

Seleniumが完全に統合されており、かっこよすぎる

というのも、PlayFrameworkではデフォルトでSeleniumによるテスト環境が整備されており、ちょちょいのちょいで始める事が出来る。この偉大な環境に僕は心酔し、Seleniumの世界へのめりこむこととなった。僕は元々フロント周りに強い人であったので、フロント周りのテストにも強い人として更なるパワーアップに期待したいところである。

ということで、PlayFrameworkに関して言えば、インストール時に既にSelenium周りが整備されているので言う事はない。

問題はもう1つの溺愛フレームワークであるCakePHPのほうだ。
CakePHPは、現実案件が1.3系で進んでいることもあり、1.3ベースでの整備を考えていきたい。

ググった限りではSeleniumを使ったSimpleTestの話題はあまり存在せず、PHPUnitベースのものが普及しているようだった。
CakePHPは2.0からPHPUnitベースのテストとなるので、このあたりでPlayFrameworkのようなエレガントなユニットからWebテストまでをオートテスティングできるような統合がされて欲しいと思っている(っていうかもうされている?よく追えてない)

さて、CakePHPでSeleniumという場合、

http://cakebaker.42dh.com/downloads/

にて、Selenium Helperとかそういうものが配布されており、これに関する言及もいろいろなところでされているが、いかんせんネタ自体が2006年や2007年のものが多くて不安になる。

CakePHPに限らずSeleniumをPHP環境で使う場合は、Selenium IDEを使ったHTMLベースのテストをすると、何の障害もなく進む。

そうではないPHPUnitを使った場合でも、まずSelenium RCサーバーをセットアップして起動を行い(Selenium RCのセットアップはググるといろいろ出てくるので割愛)、

$ java -jar ~/selenium/selenium-server-standalone-2.11.0.jar

コマンド上からPHPUnitを起動する(PHPUnitのセットアップはググるといろいろ出てくるので割愛)ことで、実現ができる。

$ phpunit --verbose /var/www/htdocs/hoge/app/tests/selenium/myapp.php

上記のPHPコードは、こんな感じだ。

<?php
require_once 'PHPUnit/Extensions/SeleniumTestCase.php';
class WebTest extends PHPUnit_Extensions_SeleniumTestCase {
    protected function setUp() {
        $this->setBrowser('*firefox');
        $this->setBrowserUrl('http://localhost:8808/');
    }

    public function testText() {
        $this->open('http://localhost:8808/users/');
        $this->assertElementContainsText('id=content', 'Hello');
    }
}
?>

このコードで行っていることは、通常のSeleniumのテストのコマンドライン版である下記

$ java -jar ~/selenium/selenium-server-standalone-2.11.0.jar -htmlSuite "*firefox" "http://localhost:8808/" "/var/www/htdocs/selenium_sample/app/tests/selenium/myapp.html" "/Applications/MAMP/htdocs/selenium_sample/app/tests/selenium/myapp_result.html"

の引数などをPHPから扱えるインターフェースにして、テストスイートを書く必要がなく、簡潔なコードでアサーションを書けるということである。

これをCakePHPに組み込む場合、まずテストケースをCakePHPのテスト関係が入っているフォルダに入れるというのが考えられる。続けて行う事としては、他のテストが実行される段階で同時に上記コマンドを実行するようにしてテストを走らせるか、もしくはクラスを使ってテストケース内で実行することにするかということになる。

個人的には、コマンドラインから走らせて結果がわかるというだけでも十分いいんだけどね。
PlayFrameworkのエレガントさを見てしまうと、CakePHPでもああいう感じで一気貫通で行いたいとか思ってしまったりなのだ。

※OSXでSelenium RCを動かしていると、Seleniumを終了させた後でサーバーであるJettyが終わらずに残ってしまうことがあり、ここはちょっと困ったなあという感じ。

Read More