CakePHPのLDAPサポートプラグイン(Yaldap2)


(2014/07/19) CakePHPの標準では、データベースへのアクセスレイヤー(データソース)としては LDAP がサポートされていません。このため LDAP アクセス部分は独自にロジックを書く必要がありますが、そうすると CakePHP が提供しているモデルによる抽象化の恩恵が受けられません。

サードパーティーのプラグインを探してみたところIdbrokerという実装を見つけましたが、Cake の最新版ではそのままでは動かなかったので、これを fork してとりあえず動くものを作りました。

例示におけるプラットフォームは VMware vSphere 上の VM に入れた CentOS 6.6(64bit) です。NetBeasn で CLI のステップ実行ができるように、インストール時のパッケージグループとしては Software Development Workstation を選択しました。 CakePHP のインストールに関してはCakePHP のインストールを参照してください。

目次

Go previousメニューに戻る

LDAP 環境

LDAP 環境がない方のために、後述のサンプルアプリを動かすための必要最小限の LDAP 環境作成の手順を示します。すでに LDAP 環境がある方はスキップしてください。

サンプルデータでは、LDAP の管理者パスワードや Root DN 等は後述の database.php に合わせています。実環境ではUnixPower on Networking - LDAPの概要などを参照の上、適切なパスワードを設定してください。

# yum install openldap-servers openldap-clients
# cp /usr/share/openldap-servers/DB_CONFIG.example /var/lib/ldap/DB_CONFIG
# wget http://net-newbie.com/cakephp/Yaldap2/openldap-example.tar.gz -P /tmp
# cd /etc/openldap
# tar xzf /tmp/openldap-example.tar.gz
# slapadd < ./example.com.ldif(若干の警告が出ます)
# chown -R ldap.ldap .
# chown -R ldap.ldap /var/lib/ldap
# service slapd start

LDAP サーバ単体の動作確認

hotta@HOST:$ ldapsearch -x -LLL -b dc=example,dc=com '(uid=user1)' dn
dn: uid=user1,ou=Users,dc=example,dc=com

結果が表示されれば成功です。LDAP にどういうデータが登録されているかは、/etc/openldap/example.com.ldif を参照してください。

YaLdap2 プラグインの導入

CakePHP のインストール手順により、ホームディレクトリ直下の cake ディレクトリに CakePHP 環境があるものと仮定します。

hotta@HOST:~$ cd cake/app/Plugin
hotta@HOST:~/cake/app/Plugin$ git clone https://github.com/hotta/Yaldap2

プラグインを読み込む設定

app/Config/bootstrap.php の末尾に以下を追加します。

hotta@HOST:~/cake/app/Plugin$ cd ../Config
hotta@HOST:~/cake/app/Config$ vi bootstrap.php
CakePlugin::load('Yaldap2');

DB/LDAP データソース設定

プラグインの中にあるサンプル設定ファイルを database.php として使用します。

hotta@HOST:~/cake/app/Config$ cp ../Plugin/Yaldap2/Config/database.php.sample database.php

これで前述の LDAP テスト環境であればそのまま動くはずです。ご自分の既存の LDAP 環境でテストする場合は database.php を修正してください。設定を間違えると見つけるのが難しいので、最初のうちはデバッガのお世話になることになります。

実行結果(使用法表示)

コンソールアプリを起動する場合は、コマンドとして 'インスタンス/app/Console/cake' を指定します。'インスタンス/app/Console' にパスが通っていない場合は相対パスで指定します。cake コマンドを引数なしで実行した場合は、以下のようにヘルプメッセージを表示します。

hotta@HOST:~/cake$ ./app/Console/cake

Welcome to CakePHP v2.5.2 Console
---------------------------------------------------------------
App : app
Path: /home/hotta/cake/app/
---------------------------------------------------------------
Current Paths:

 -app: app
 -working: /home/hotta/cake/app
 -root: /home/hotta/cake
 -core: /home/hotta/cake/lib

Changing Paths:

Your working path should be the same as your application path. To change your path use the '-app' param.
Example: -app relative/path/to/myapp or -app /absolute/path/to/myapp

Available Shells:

[Yaldap2] sample_app

[CORE] acl, api, bake, command_list, console, i18n, schema, server, test, testsuite, upgrade

To run an app or core command, type cake shell_name [args]
To run a plugin command, type cake Plugin.shell_name [args]
To get help on a specific command, type cake shell_name --help

ヘルプメッセージの中にある "[Yaldap2] sample_app" は、当該 Yaldap2 プラグインに付属する動作確認用のサンプルプログラムです。実体は ~/cake/app/Plugin/Yaldap2/Console/Command/SampleAppShell.php です。それでは早速実行してみます。

hotta@HOST:~/cake$ ./app/Console/cake Yaldap2.sample_app
cake SampleApp [arg]
   arg is one of the list below:
   args ............ Show argument list
   ldap_read ....... Model->read()
   ldap_findby ..... Model->findByXXX()
   ldap_findfirst .. Model->find('first')
   ldap_findall .... Model->find('all')
   ldap_query ...... Model->query(sql)
   ldap_auth ....... Model->find('first', password)
   ad_read ....... Model->read()
   ad_findby ..... Model->findByXXX()
   ad_findfirst .. Model->find('first')
   ad_findall .... Model->find('all')
   ad_query ...... Model->query(sql)
   ad_auth ....... Model->find('first', password)

事実上の引数は第二パラメータ以降となります。それぞれが各 API を使った簡単なテストとなっています。以下それぞれ、ソースと実行結果を2,3ご紹介します。

モデル定義

プラグインの中でサンプルアプリで使うモデルの定義をしています。

hotta@HOST:~/cake$ cat app/Plugin/Yaldap2/Model/Yaldap2_User.php
<?php
/*
 * app/Plugin/Yaldap2/Model/Yaldap2_User.php
 */
class   Yaldap2_User extends AppModel {
    public  $name        = 'Yaldap2_User';
    public  $useDbConfig = 'ldap';   //  cf. app/Config/database.php
    public  $primaryKey  = 'uid';
    public  $useTable    = 'ou=Users';
}

モデル使用宣言

アプリケーションでモデルの使用を宣言します。

hotta@HOST:~/cake$ head -7 app/Plugin/Yaldap2/Console/Command/SampleAppShell.php
<?php
//  sample/app/Console/Command/SampleAppShell.php
Class   SampleAppShell  extends AppShell    {

    public  $uses = array('Yaldap2.Yaldap2_User',
                          'Yaldap2.Yaldap2_Aduser');
    const   USERID = 'user1';

実行結果(ldap_read)

hotta@HOST:~/cake$ sed -n 28,32p app/Plugin/Yaldap2/Console/Command/SampleAppShell.php
    public  function    ldap_read() {
        $this->out('$this->Yaldap2_User->read()');
        $this->out(print_r($this->Yaldap2_User->read(
            array('displayName', 'homeDirectory'), self::USERID), true));
    }
hotta@HOST:~/cake$ ./app/Console/cake Yaldap2.sample_app ldap_read
$this->Yaldap2_User->read()
Array
(
    [Yaldap2_User] => Array
        (
            [displayname] => user1 user1-name
            [homedirectory] => /home/user1
            [dn] => uid=user1,ou=Users,dc=example,dc=com
        )

    [0] => Array
        (
            [count] => 1
        )
)

1. search | cond: (uid=user1) | targetDn: ou=Users,dc=example,dc=com | fields: displayName, homeDirectory | order:

実行結果(ldap_findby)

m-hotta@HOST:~/cake$ sed -n 34,37p app/Plugin/Yaldap2/Console/Command/SampleAppShell.php
    public  function    ldap_findby() {
        $this->out('$this->Yaldap2_User->findBy()');
        $this->out(print_r($this->Yaldap2_User->findByUid(self::USERID),true));
    }
m-hotta@HOST:~/cake$ ./app/Console/cake Yaldap2.sample_app ldap_findby
$this->Yaldap2_User->findBy()
Array
(
    [Yaldap2_User] => Array
        (
            [objectclass] => Array
                (
                    [0] => posixAccount
                    [1] => inetOrgPerson
                )

            [sn] => user1
            [cn] => user1-name
            [displayname] => user1 user1-name
            [uid] => user1
            [uidnumber] => 1000
            [gidnumber] => 1000
            [homedirectory] => /home/user1
            [loginshell] => /bin/bash
            [userpassword] => {SSHA}FI5FDD63K3DlANtNXUWL0Kizth/D2zTw
            [mail] => user1@example.com
            [structuralobjectclass] => inetOrgPerson
            [entryuuid] => f62d88aa-a255-1033-88f4-0100fe469ff0
            [creatorsname] => cn=Manager,dc=example,dc=com
            [createtimestamp] => 20140717232937Z
            [entrycsn] => 20140717232937.334250Z#000000#000#000000
            [modifiersname] => cn=Manager,dc=example,dc=com
            [modifytimestamp] => 20140717232937Z
            [entrydn] => uid=user1,ou=Users,dc=example,dc=com
            [subschemasubentry] => cn=Subschema
            [hassubordinates] => FALSE
            [dn] => uid=user1,ou=Users,dc=example,dc=com
        )

    [0] => Array
        (
            [count] => 1
        )

)

1. search | cond: (uid=user1) | targetDn: ou=Users,dc=example,dc=com | order:

Web 画面におけるログイン認証

次はWebアプリにLDAP認証機能を組み込んでみます。

コントローラー:宣言

以下の宣言により、Auth コンポーネントの authenticate() メソッドが Yaldap2 プラグインが提供しているものを使うようになります。

Class   UsersController    Extends     AppController
{
    public  $uses = ['User', 'Aduser', 'Group'];
    public  $name = 'Users';    //  コントローラ名(複数形)
    public  $components = [
        'Auth' => [
            'authenticate' => [
                'Yaldap2.Ldap' => [
                    'userModel' => 'User',
                    'GroupType' => 'posixgroup',
                    'targetDn'  => 'ou=Groups'
                ]
            ]
        ]
    ];

コントローラー:利用

ここは通常の認証処理と変わりません。

    //  ログイン(チェック)処理
    public  function    login()
    {
        if ($this->request->is('post')) {
            if ($this->Auth->login()) {
                return $this->redirect(array('action' => 'newpass'));
            } else {
                $this->Session->setFlash(
                    __('ユーザ名またはパスワードが違います'),
                    'default', array('class'=>'error-message'), 'auth');
            }
        }
    }   //  UsersController :: login()

モデル

User クラスの定義は以下のようになります。バリデーション等は省略します。

<?php
/*
 * app/Model/User.php   OpenLDAP アクセスクラス(Usersツリー操作)
 */

class   User    extends AppModel {
    public $name        = 'User';
    public $useDbConfig = 'ldap';
    public $primaryKey  = 'uid';
    public $useTable    = 'ou=Users';
    public $validate = [
        'new_password1' => [
            [
                'rule' => array('minlength',6),
                'message' => '6文字以上で入力して下さい。',
                'last'  =>  true
            ], ...

技術資料

CheatSheets

本データソースと LDAP 認証機能を作成するにあたり、CakePHP 本体を解析した資料を Google Drive 上に置いてあります。

Go back