<?xml version="1.0" encoding="UTF-8"?>
<!-- Reviewed: no -->
<!-- EN-Revision: 15215 -->
<sect1 id="zend.ldap.using">

    <title>導入</title>

    <note>
        <title>最小限の機能</title>
        <para>
            現在このクラスは、
            <link linkend="zend.auth.adapter.ldap"><classname>Zend_Auth_Adapter_Ldap</classname></link>
            認証アダプタが必要とする機能のみを満たすように作られています。
            ディレクトリ内のエントリを検索したり、
            エントリの作成や修正、名前の変更を行ったりといった機能は現時点ではサポートしていません。
            これらの機能は将来実装される予定です。
        </para>
    </note>

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

    <sect2 id="zend.ldap.using.theory-of-operation">

        <title>動作原理</title>

        <para>
            このコンポーネントは、現在 <classname>Zend_Ldap</classname> と
            <classname>Zend_Ldap_Exception</classname> のふたつのクラスで構成されています。
            <classname>Zend_Ldap</classname> クラスは、単一の LDAP サーバへのバインドを表します。
            バインド用のパラメータは、明示的に指定するか、あるいはオプションの配列形式で指定します。
        </para>

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

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

            <programlisting role="php"><![CDATA[
$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";
]]></programlisting>

        </para>

        <para>
            Microsoft AD を使う場合の例はこのようになります。

            <programlisting role="php"><![CDATA[
$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";
]]></programlisting>

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

        <sect3 id="zend.ldap.using.theory-of-operation.username-canonicalization-automatic">

            <title>バインド時の、ユーザ名の自動正規化</title>

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

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

            <para>
                次の例は、DN でないユーザ名 '<code>abaker</code>'
                を <code>bind()</code> で使用する方法を示すものです。

                <programlisting role="php"><![CDATA[
$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";
]]></programlisting>

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

        </sect3>

        <sect3 id="zend.ldap.using.theory-of-operation.options">

            <title>Zend_Ldap のオプション</title>

            <para>
                <classname>Zend_Ldap</classname> コンポーネント用のオプションの配列は、
                コンストラクタあるいは <code>setOptions()</code> メソッドで指定することができます。
                次のようなオプションが使用可能です。

                <table id="zend.ldap.using.theory-of-operation.options.table">
                  <title>Zend_Ldap のオプション</title>
                  <tgroup cols="2">
                    <thead>
                      <row>
                        <entry>名前</entry>
                        <entry>説明</entry>
                      </row>
                    </thead>
                    <tbody>
                      <row>
                        <entry>host</entry>
                        <entry>
                            LDAP サーバのデフォルトのホスト名。<code>connect()</code>
                            で指定しなかった場合に使用します (<code>bind()</code>
                            の際にユーザ名を正規化するときにも使用します)。
                        </entry>
                      </row>
                      <row>
                        <entry>port</entry>
                        <entry>
                            LDAP サーバのデフォルトのポート。<code>connect()</code>
                            で指定しなかった場合に使用します。
                        </entry>
                      </row>
                      <row>
                        <entry>useStartTls</entry>
                        <entry>
                            LDAP クライアント側に TLS (SSLv2) での暗号化トランスポートを要求するか否か。
                            実運用環境では <code>true</code> を指定することを強く推奨します。
                            これにより、パスワードが平文で送られてしまうことを防ぎます。
                            デフォルト値は <code>false</code> です。というのも、
                            この機能を使用するにはサーバ側に別途証明書のインストールが必要となることが多いからです。
                            <code>useSsl</code> オプションと <code>useStartTls</code> オプションは共存できません。
                            <code>useStartTls</code> オプションのほうが <code>useSsl</code>
                            より推奨されていますが、中にはこの新しい仕組みをサポートしていないサーバもあります。
                        </entry>
                      </row>
                      <row>
                        <entry>useSsl</entry>
                        <entry>
                            LDAP クライアント側に SSL での暗号化トランスポートを要求するか否か。
                            <code>useSsl</code> オプションと <code>useStartTls</code> オプションは共存できません。
                        </entry>
                      </row>
                      <row>
                        <entry>username</entry>
                        <entry>
                            デフォルトの認証ユーザ名。サーバによっては、DN 形式を要求するものもあります。
                        </entry>
                      </row>
                      <row>
                        <entry>password</entry>
                        <entry>
                            デフォルトの認証パスワード (上のユーザ名との組み合わせでのみ使用します)。
                        </entry>
                      </row>
                      <row>
                        <entry>bindRequiresDn</entry>
                        <entry>
                            <code>true</code> を指定すると、ユーザ名が DN 形式でない場合に
                            <classname>Zend_Ldap</classname> はバインド時に使用してアカウントの DN を取得します。
                            デフォルト値は <code>false</code> です。
                        </entry>
                      </row>
                      <row>
                        <entry>baseDn</entry>
                        <entry>
                            (アカウントなどの) 検索に使用するデフォルトのベース DN。
                            このオプションは、アカウントに関する大半の操作で必須となります。
                            そのアカウントが存在する DN を指す必要があります。
                        </entry>
                      </row>
                      <row>
                        <entry>accountCanonicalForm</entry>
                        <entry>
                            アカウント名の正規化方式を表す整数値。以下の
                            <emphasis>アカウント名の正規化</emphasis> のセクションを参照ください。
                        </entry>
                      </row>
                      <row>
                        <entry>accountDomainName</entry>
                        <entry>
                            対象となる LDAP サーバの FQDN ドメイン
                            (例 example.com)。
                        </entry>
                      </row>
                      <row>
                        <entry>accountDomainNameShort</entry>
                        <entry>
                            対象となる LDAP サーバの '短い' ドメイン。
                            これは、Windows ネットワークの NetBIOS ドメイン名として用いられますが、
                            AD 以外のサーバで用いられることもあります。
                        </entry>
                      </row>
                      <row>
                        <entry>accountFilterFormat</entry>
                        <entry>
                            アカウントを検索する際に使用する LDAP 検索フィルタ。
                            この文字列は
                            <ulink url="http://php.net/printf"><code>printf()</code></ulink>
                            形式のものとなり、ユーザ名を表す '<code>%s</code>'
                            をひとつ含む必要があります。デフォルト値は
                            '<code>(&amp;(objectClass=user)(sAMAccountName=%s))</code>' です。
                            ただし、<code>bindRequiresDn</code> が <code>true</code>
                            の場合のデフォルト値は
                            '<code>(&amp;(objectClass=posixAccount)(uid=%s))</code>'
                            となります。独自のスキーマを使用している場合は、
                            それにあわせてこのオプションを変更しなければなりません。
                        </entry>
                      </row>
                      <row>
                        <entry>allowEmptyPassword</entry>
                        <entry>
                            LDAP サーバによっては、匿名バインドの際のパスワードに空文字を設定できるものもあります。
                            この挙動は、ほとんどの場合において好ましくないものです。
                            そのため、空のパスワードは明示的に無効にしています。
                            この値を <code>true</code> にすると、
                            バインド時に空文字列のパスワードを使用できるようになります。
                        </entry>
                      </row>
                      <row>
                        <entry>optReferrals</entry>
                        <entry>
                            <code>true</code> に設定すると、
                            参照先を追跡するよう LDAP クライアントに指示します。
                            デフォルト値は <code>false</code> です。
                        </entry>
                      </row>
                    </tbody>
                  </tgroup>
                </table>

            </para>

        </sect3>

        <sect3 id="zend.ldap.using.theory-of-operation.account-name-canonicalization">

            <title>アカウント名の正規化</title>

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

                <table id="zend.ldap.using.theory-of-operation.account-name-canonicalization.table">
                  <title>accountCanonicalForm</title>
                  <tgroup cols="3">
                    <thead>
                      <row>
                        <entry>名前</entry>
                        <entry>値</entry>
                        <entry>例</entry>
                      </row>
                    </thead>
                    <tbody>
                      <row>
                        <entry><code>ACCTNAME_FORM_DN</code></entry>
                        <entry>1</entry>
                        <entry>CN=Alice Baker,CN=Users,DC=example,DC=com</entry>
                      </row>
                      <row>
                        <entry><code>ACCTNAME_FORM_USERNAME</code></entry>
                        <entry>2</entry>
                        <entry>abaker</entry>
                      </row>
                      <row>
                        <entry><code>ACCTNAME_FORM_BACKSLASH</code></entry>
                        <entry>3</entry>
                        <entry>EXAMPLE\abaker</entry>
                      </row>
                      <row>
                        <entry><code>ACCTNAME_FORM_PRINCIPAL</code></entry>
                        <entry>4</entry>
                        <entry>abaker@example.com</entry>
                      </row>
                    </tbody>
                  </tgroup>
                </table>

            </para>

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

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

        </sect3>

        <sect3 id="zend.ldap.using.theory-of-operation.multi-domain-failover">

            <title>複数ドメインの認証とフェイルオーバー</title>

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

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

                <programlisting role="php"><![CDATA[
$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;
        }
    }
}
]]></programlisting>

                何らかの理由でバインドに失敗すると、その次のオプションでのバインドを試みます。
            </para>

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

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

            <para>
                上のコードは、
                <link linkend="zend.auth.adapter.ldap"><classname>Zend_Auth_Adapter_Ldap</classname></link>
                の中で使用するコードと非常によく似ています。実際のところ、
                複数ドメインとファイルオーバー機能をもつ LDAP 基本印象を行うのなら、
                この認証アダプタを使用する (あるいはコードをコピーする) ことをおすすめします。
            </para>

        </sect3>

    </sect2>

</sect1>
<!--
vim:se ts=4 sw=4 et:
-->
