第29章 Zend_Ldap

目次

29.1. 導入
29.1.1. 動作原理
29.1.1.1. バインド時のユーザ名自動正規化
29.1.1.2. アカウント名の正規化
29.1.1.3. 複数ドメインの認証とフェイルオーバー
29.2. API概要
29.2.1. 設定/オプション
29.2.2. APIリファレンス
29.2.2.1. Zend_Ldap
29.2.2.1.1. Zend_Ldap_Collection
29.2.2.2. Zend_Ldap_Attribute
29.2.2.3. Zend_Ldap_Dn
29.2.2.4. Zend_Ldap_Filter
29.2.2.5. Zend_Ldap_Node
29.2.2.6. Zend_Ldap_Node_RootDse
29.2.2.6.1. OpenLDAP
29.2.2.6.2. ActiveDirectory
29.2.2.6.3. eDirectory
29.2.2.7. Zend_Ldap_Node_Schema
29.2.2.7.1. OpenLDAP
29.2.2.7.2. ActiveDirectory
29.2.2.8. Zend_Ldif_Encoder
29.3. 利用シナリオ
29.3.1. 認証シナリオ
29.3.1.1. OpenLDAP
29.3.1.2. ActiveDirectory
29.3.2. 基本的なCRUD操作
29.3.2.1. LDAPからデータを取得
29.3.2.2. LDAPにデータを追加
29.3.2.3. LDAPからデータを削除
29.3.2.4. LDAPを更新
29.3.3. 拡張された操作
29.3.3.1. LDAPで項目をコピーまたは移動
29.4. ツール
29.4.1. DN 文字列の作成及び変更
29.4.2. 検索フィルタを作成するためにフィルタAPIを使う
29.4.3. 属性APIを使用するLDAP項目の変更
29.5. Zend_Ldap_Nodeを使用してLDAPツリーへのオブジェクト指向アクセス
29.5.1. CRUD基本操作
29.5.1.1. LDAPからデータを取得
29.5.1.1.1. DNでノードを取得
29.5.1.1.2. ノードのサブツリーを検索
29.5.1.2. LDAPに新規ノードを追加
29.5.1.3. LDAPからノードを削除
29.5.1.4. LDAP上のノードを更新
29.5.2. 派生的な操作
29.5.2.1. LDAPでノードをコピーまたは移動
29.5.3. ツリーの走査
29.6. LDAPサーバから情報を取得
29.6.1. ルートDSE
29.6.2. 参照するスキーマ
29.6.2.1. OpenLDAP
29.6.2.2. ActiveDirectory
29.7. LDIFへ、または、からのLDAPデータシリアライズ
29.7.1. LDIFへのLDAP項目シリアライズ
29.7.2. LDIF文字列をLDAP項目に非シリアライズ化

29.1. 導入

Zend_LdapLDAP操作を行うクラスです。 バインドだけが可能で、 LDAP ディレクトリ内のエントリの検索や変更には対応していません。

29.1.1. 動作原理

現在このコンポーネントは、OpenLDAPまたはActiveDirectory(AD)サーバのような 単一のLDAPサーバへのバインディングを概念的に表現して、 LDAPサーバに対する活動を実行できる 主要なZend_Ldapクラスから成ります。 バインディングのパラメータは、明示的に、または、オプション配列の形で提供されるかもしれません。 Zend_Ldap_Nodeは、 単一のLDAPノードのためにオブジェクト指向インタフェースを提供します。 そして、LDAPベースのドメイン・モデルのために、 アクティブ・レコードのようなインターフェースの基盤を作ることに使うことができます。

属性の設定や取得 (日付値、パスワード、ブール値など)のような LDAP項目上での活動の実行や (Zend_Ldap_Attribute)、 LDAPフィルタ文字列の作成や修正 (Zend_Ldap_Filter)、 LDAP識別名 (DN)の操作 (Zend_Ldap_Dn) をおこなうためのいくつかのヘルパー・クラスをコンポーネントで提供します。

その上、 OpenLDAPとActiveDirectoyサーバのZend_Ldap_Node_Schemaのために ブラウズするLDAPスキーマ、 そして OpenLDAPサーバやActiveDirectoryサーバ、 Novell eDirectoryサーバのためのサーバ情報取得 (Zend_Ldap_Node_RootDse)をコンポーネントで抽象します。

Zend_Ldap クラスの使用法は LDAP サーバの形式によって異なり、 以下のいずれかのパターンとなります。

OpenLDAP を使用している場合は、以下の例のようになります (AD を使って いない 場合は bindRequiresDn オプションが重要となることに注意しましょう):

$options = array(
    'host'              => 's0.foo.net',
    'username'          => 'CN=user1,DC=foo,DC=net',
    'password'          => 'pass1',
    'bindRequiresDn'    => true,
    'accountDomainName' => 'foo.net',
    'baseDn'            => 'OU=Sales,DC=foo,DC=net',
);
$ldap = new Zend_Ldap($options);
$acctname = $ldap->getCanonicalAccountName('abaker',
                                           Zend_Ldap::ACCTNAME_FORM_DN);
echo "$acctname\n";

Microsoft AD を使う場合の簡単な例です:

$options = array(
    'host'                   => 'dc1.w.net',
    'useStartTls'            => true,
    'username'               => 'user1@w.net',
    'password'               => 'pass1',
    'accountDomainName'      => 'w.net',
    'accountDomainNameShort' => 'W',
    'baseDn'                 => 'CN=Users,DC=w,DC=net',
);
$ldap = new Zend_Ldap($options);
$acctname = $ldap->getCanonicalAccountName('bcarter',
                                           Zend_Ldap::ACCTNAME_FORM_DN);
echo "$acctname\n";

ここでは、getCanonicalAccountName() メソッドで、 アカウントの DN を取得していることに注意しましょう。 これはただ単に、このクラスに現在存在するコードの例をできるだけ多く見せたいからです。

29.1.1.1. バインド時のユーザ名自動正規化

bindRequiresDNTRUEで、 かつ DN 形式のユーザ名がオプションで設定されていない場合、 bind() を DN でないユーザ名でコールするとバインドに失敗します。 しかし、DN 形式のユーザ名がオプションで設定されていれば、 Zend_Ldapはまずそのユーザ名でバインドを行い、 bind()で指定したユーザ名に対応するアカウントの DN を取得した上で 改めてその DN でバインドしなおします。

この振る舞いはZend_Auth_Adapter_Ldap にとっては重要です。 これは、ユーザが指定したユーザ名を直接 bind() に渡します。

次の例では、DN でないユーザ名 'abaker' を bind() で使用する方法を示します:

$options = array(
        'host'              => 's0.foo.net',
        'username'          => 'CN=user1,DC=foo,DC=net',
        'password'          => 'pass1',
        'bindRequiresDn'    => true,
        'accountDomainName' => 'foo.net',
        'baseDn'            => 'OU=Sales,DC=foo,DC=net',
);
$ldap = new Zend_Ldap($options);
$ldap->bind('abaker', 'moonbike55');
$acctname = $ldap->getCanonicalAccountName('abaker',
                                           Zend_Ldap::ACCTNAME_FORM_DN);
echo "$acctname\n";

この例において bind() をコールすると、 ユーザ名 'abaker' が DN 形式でないことと bindRequiresDnTRUE であることから、まず 'CN=user1,DC=foo,DC=net' と 'pass1' を用いてバインドします。それから 'abaker' の DN を取得し、 いったんバインドを解除したうえであらためて 'CN=Alice Baker,OU=Sales,DC=foo,DC=net' でバインドしなおします。

29.1.1.2. アカウント名の正規化

accountDomainNameおよび accountDomainNameShortオプションは、 次のふたつの目的で使用します。 (1) 複数ドメインによる認証 (どちらか一方が使えないときの代替機能) を実現する。 (2) ユーザ名を正規化する。 特に、名前の正規化の際には accountCanonicalFormオプションで指定した形式を使用します。 このオプションの値は、次のいずれかとなります:

表 29.1. accountCanonicalFormのオプション

名前
ACCTNAME_FORM_DN 1 CN=Alice Baker,CN=Users,DC=example,DC=com
ACCTNAME_FORM_USERNAME 2 abaker
ACCTNAME_FORM_BACKSLASH 3 EXAMPLE\abaker
ACCTNAME_FORM_PRINCIPAL 4 abaker@example.com

デフォルトの正規化は、アカウントのドメイン名のオプションが どのように設定されているかによって変わります。 accountDomainNameShort が指定されている場合は、デフォルトの accountCanonicalForm の値は ACCTNAME_FORM_BACKSLASH となります。 それ以外の場合は、もし accountDomainName が設定されていればデフォルトは ACCTNAME_FORM_PRINCIPAL となります。

アカウント名の正規化をすることで、bind() に何が渡されたのかにかかわらずアカウントの識別に用いる文字列が一貫性のあるものになります。 たとえば、ユーザがアカウント名として abaker@example.com あるいは単に abaker だけを指定したとしても、accountCanonicalForm が 3 に設定されていれば正規化後の名前は EXAMPLE\abaker となります。

29.1.1.3. 複数ドメインの認証とフェイルオーバー

Zend_Ldap コンポーネント自身は、 複数サーバでの認証を試みません。 しかし、Zend_Ldap はこのような場合に対応するようにも設計されています。 サーバのオプションを指定した配列の配列を順にたどり、 個々のサーバへのバインドを試みるのです。上で説明したように、 bind() は自動的に名前を正規化します。したがって、ユーザが abaker@foo.net を指定したか、あるいは W\bcartercdavis と指定したかにはかかわらず、 bind() メソッドが成功するかどうかは バインド時に認証情報が正しく指定されたかどうかによって決まります。

次の例では、複数ドメインでの認証と フェイルオーバー機能を実装するために必要な技術を説明します:

$acctname = 'W\\user2';
$password = 'pass2';

$multiOptions = array(
    'server1' => array(
        'host'                   => 's0.foo.net',
        'username'               => 'CN=user1,DC=foo,DC=net',
        'password'               => 'pass1',
        'bindRequiresDn'         => true,
        'accountDomainName'      => 'foo.net',
        'accountDomainNameShort' => 'FOO',
        'accountCanonicalForm'   => 4, // ACCT_FORM_PRINCIPAL
        'baseDn'                 => 'OU=Sales,DC=foo,DC=net',
    ),
    'server2' => array(
        'host'                   => 'dc1.w.net',
        'useSsl'                 => true,
        'username'               => 'user1@w.net',
        'password'               => 'pass1',
        'accountDomainName'      => 'w.net',
        'accountDomainNameShort' => 'W',
        'accountCanonicalForm'   => 4, // ACCT_FORM_PRINCIPAL
        'baseDn'                 => 'CN=Users,DC=w,DC=net',
    ),
);

$ldap = new Zend_Ldap();

foreach ($multiOptions as $name => $options) {

    echo "Trying to bind using server options for '$name'\n";

    $ldap->setOptions($options);
    try {
        $ldap->bind($acctname, $password);
        $acctname = $ldap->getCanonicalAccountName($acctname);
        echo "SUCCESS: authenticated $acctname\n";
        return;
    } catch (Zend_Ldap_Exception $zle) {
        echo '  ' . $zle->getMessage() . "\n";
        if ($zle->getCode() === Zend_Ldap_Exception::LDAP_X_DOMAIN_MISMATCH) {
            continue;
        }
    }
}

何らかの理由でバインドに失敗すると、その次のセットのサーバオプションでバインドを試みます。

getCanonicalAccountName() をコールすると、 正規化したアカウント名を取得することができます。 これを使用して、アプリケーションから関連データを取得できるようになります。 accountCanonicalForm = 4 をすべてのサーバのオプションに設定することで、 どのサーバを使用する場合にも一貫した正規化が行えるようになっています。

ドメイン部つきのアカウント名 (単なる abaker ではなく abaker@foo.netFOO\abaker など) を指定した場合は、そのドメインが設定済みのオプションのどれとも一致しなければ 特別な例外 LDAP_X_DOMAIN_MISMATCH が発生します。 この例外は、そのアカウントがサーバに見つからないことを表します。 この場合はバインドは行われず、 サーバとの余計な通信は発生しません。 この例では continue という指示は無意味であることに注意しましょう。 しかし、実際には、エラー処理やデバッグなどのために LDAP_NO_SUCH_OBJECTLDAP_INVALID_CREDENTIALS だけではなく LDAP_X_DOMAIN_MISMATCH もチェックすることになるでしょう。

上のコードは、Zend_Auth_Adapter_Ldap の中で使用するコードと非常によく似ています。実際のところ、 複数ドメインとフェイルオーバー機能をもつ LDAP 基本認証を行うのなら、 この認証アダプタを使用する (あるいはコードをコピーする) ことをおすすめします。