|
|
@@ -0,0 +1,328 @@
|
|
|
+<?xml version="1.0" encoding="UTF-8"?>
|
|
|
+<!-- EN-Revision: 16617 -->
|
|
|
+<!-- Reviewed: no -->
|
|
|
+<sect1 id="zend.ldap.introduction">
|
|
|
+ <title>Introduction</title>
|
|
|
+
|
|
|
+ <para>
|
|
|
+ <classname>Zend_Ldap</classname> is a class for performing <acronym>LDAP</acronym>
|
|
|
+ operations including but not limited to binding, searching and modifying entries
|
|
|
+ in an <acronym>LDAP</acronym> directory.
|
|
|
+ </para>
|
|
|
+
|
|
|
+ <sect2 id="zend.ldap.introduction.theory-of-operations">
|
|
|
+ <title>Theory of operation</title>
|
|
|
+
|
|
|
+ <para>
|
|
|
+ This component currently consists of the main <classname>Zend_Ldap</classname> class,
|
|
|
+ that conceptually represents a binding to a single <acronym>LDAP</acronym> server
|
|
|
+ and allows for executing operations against a <acronym>LDAP</acronym> server such
|
|
|
+ as OpenLDAP or ActiveDirectory (AD) servers. The parameters for binding may be
|
|
|
+ provided explicitly or in the form of an options array.
|
|
|
+ <classname>Zend_Ldap_Node</classname> provides an object-oriented interface
|
|
|
+ for single <acronym>LDAP</acronym> nodes and can be used to form a basis for an
|
|
|
+ active-record-like interface for a <acronym>LDAP</acronym>-based domain model.
|
|
|
+ </para>
|
|
|
+
|
|
|
+ <para>
|
|
|
+ The component provides several helper classes to perform operations on
|
|
|
+ <acronym>LDAP</acronym> entries (<classname>Zend_Ldap_Attribute</classname>) such as
|
|
|
+ setting and retrieving attributes (date values, passwords, boolean values, ...), to
|
|
|
+ create and modify <acronym>LDAP</acronym> filter strings
|
|
|
+ (<classname>Zend_Ldap_Filter</classname>) and to manipulate <acronym>LDAP</acronym>
|
|
|
+ distinguished names (DN) (<classname>Zend_Ldap_Dn</classname>).
|
|
|
+ </para>
|
|
|
+
|
|
|
+ <para>
|
|
|
+ Additionally the component abstracts <acronym>LDAP</acronym> schema browsing
|
|
|
+ for OpenLDAP and ActiveDirectoy servers <classname>Zend_Ldap_Node_Schema</classname>
|
|
|
+ and server information retrieval for OpenLDAP-, ActiveDirectory- and Novell
|
|
|
+ eDirectory servers (<classname>Zend_Ldap_Node_RootDse</classname>).
|
|
|
+ </para>
|
|
|
+
|
|
|
+ <para>
|
|
|
+ Using the <classname>Zend_Ldap</classname> class depends on the type of
|
|
|
+ <acronym>LDAP</acronym> server and is best summarized with some simple examples.
|
|
|
+ </para>
|
|
|
+
|
|
|
+ <para>
|
|
|
+ If you are using OpenLDAP, a simple example looks like the following
|
|
|
+ (note that the <emphasis>bindRequiresDn</emphasis> option is important if you are
|
|
|
+ <emphasis>not</emphasis> using AD):
|
|
|
+ </para>
|
|
|
+
|
|
|
+ <programlisting language="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>
|
|
|
+ If you are using Microsoft AD a simple example is:
|
|
|
+ </para>
|
|
|
+
|
|
|
+ <programlisting language="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>
|
|
|
+
|
|
|
+ <para>
|
|
|
+ Note that we use the <methodname>getCanonicalAccountName()</methodname> method
|
|
|
+ to retrieve the account DN here only because that is what exercises the
|
|
|
+ most of what little code is currently present in this class.
|
|
|
+ </para>
|
|
|
+
|
|
|
+ <sect3 id="zend.ldap.introduction.theory-of-operations.automatic-username-canonicalization">
|
|
|
+ <title>Automatic Username Canonicalization When Binding</title>
|
|
|
+
|
|
|
+ <para>
|
|
|
+ If <methodname>bind()</methodname> is called with a non-DN username but
|
|
|
+ <emphasis>bindRequiresDN</emphasis> is <constant>TRUE</constant> and no username in
|
|
|
+ DN form was supplied as an option, the bind will fail. However, if a username in DN
|
|
|
+ form is supplied in the options array, <classname>Zend_Ldap</classname> will
|
|
|
+ first bind with that username, retrieve the account DN for the username
|
|
|
+ supplied to <methodname>bind()</methodname> and then re-bind with that DN.
|
|
|
+ </para>
|
|
|
+
|
|
|
+ <para>
|
|
|
+ This behavior is critical to <link
|
|
|
+ linkend="zend.auth.adapter.ldap"><classname>Zend_Auth_Adapter_Ldap</classname></link>,
|
|
|
+ which passes the username supplied by the user directly to
|
|
|
+ <methodname>bind()</methodname>.
|
|
|
+ </para>
|
|
|
+
|
|
|
+ <para>
|
|
|
+ The following example illustrates how the non-DN username
|
|
|
+ '<emphasis>abaker</emphasis>' can be used with <methodname>bind()</methodname>:
|
|
|
+ </para>
|
|
|
+
|
|
|
+ <programlisting language="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>
|
|
|
+
|
|
|
+ <para>
|
|
|
+ The <methodname>bind()</methodname> call in this example sees that
|
|
|
+ the username '<emphasis>abaker</emphasis>' is not in DN form, finds
|
|
|
+ <emphasis>bindRequiresDn</emphasis> is <constant>TRUE</constant>, uses
|
|
|
+ '<command>CN=user1,DC=foo,DC=net</command>' and '<emphasis>pass1</emphasis>' to
|
|
|
+ bind, retrieves the DN for '<emphasis>abaker</emphasis>', unbinds and then rebinds
|
|
|
+ with the newly discovered
|
|
|
+ '<command>CN=Alice Baker,OU=Sales,DC=foo,DC=net</command>'.
|
|
|
+ </para>
|
|
|
+ </sect3>
|
|
|
+
|
|
|
+ <sect3 id="zend.ldap.introduction.theory-of-operations.account-name-canonicalization">
|
|
|
+ <title>Account Name Canonicalization</title>
|
|
|
+
|
|
|
+ <para>
|
|
|
+ The <emphasis>accountDomainName</emphasis> and
|
|
|
+ <emphasis>accountDomainNameShort</emphasis>
|
|
|
+ options are used for two purposes: (1) they facilitate multi-domain
|
|
|
+ authentication and failover capability, and (2) they are also used to
|
|
|
+ canonicalize usernames. Specifically, names are canonicalized to the
|
|
|
+ form specified by the <emphasis>accountCanonicalForm</emphasis> option.
|
|
|
+ This option may one of the following values:
|
|
|
+ </para>
|
|
|
+
|
|
|
+ <table id="zend.ldap.using.theory-of-operation.account-name-canonicalization.table">
|
|
|
+ <title>Options for accountCanonicalForm</title>
|
|
|
+ <tgroup cols="3">
|
|
|
+ <thead>
|
|
|
+ <row>
|
|
|
+ <entry>Name</entry>
|
|
|
+ <entry>Value</entry>
|
|
|
+ <entry>Example</entry>
|
|
|
+ </row>
|
|
|
+ </thead>
|
|
|
+ <tbody>
|
|
|
+ <row>
|
|
|
+ <entry>
|
|
|
+ <constant>ACCTNAME_FORM_DN</constant>
|
|
|
+ </entry>
|
|
|
+ <entry>1</entry>
|
|
|
+ <entry>CN=Alice Baker,CN=Users,DC=example,DC=com</entry>
|
|
|
+ </row>
|
|
|
+ <row>
|
|
|
+ <entry>
|
|
|
+ <constant>ACCTNAME_FORM_USERNAME</constant>
|
|
|
+ </entry>
|
|
|
+ <entry>2</entry>
|
|
|
+ <entry>abaker</entry>
|
|
|
+ </row>
|
|
|
+ <row>
|
|
|
+ <entry>
|
|
|
+ <constant>ACCTNAME_FORM_BACKSLASH</constant>
|
|
|
+ </entry>
|
|
|
+ <entry>3</entry>
|
|
|
+ <entry>EXAMPLE\abaker</entry>
|
|
|
+ </row>
|
|
|
+ <row>
|
|
|
+ <entry>
|
|
|
+ <constant>ACCTNAME_FORM_PRINCIPAL</constant>
|
|
|
+ </entry>
|
|
|
+ <entry>4</entry>
|
|
|
+ <entry><filename>abaker@example.com</filename></entry>
|
|
|
+ </row>
|
|
|
+ </tbody>
|
|
|
+ </tgroup>
|
|
|
+ </table>
|
|
|
+
|
|
|
+ <para>
|
|
|
+ The default canonicalization depends on what account domain name options
|
|
|
+ were supplied. If <emphasis>accountDomainNameShort</emphasis> was supplied, the
|
|
|
+ default <emphasis>accountCanonicalForm</emphasis> value is
|
|
|
+ <constant>ACCTNAME_FORM_BACKSLASH</constant>. Otherwise, if
|
|
|
+ <emphasis>accountDomainName</emphasis> was supplied, the
|
|
|
+ default is <constant>ACCTNAME_FORM_PRINCIPAL</constant>.
|
|
|
+ </para>
|
|
|
+
|
|
|
+ <para>
|
|
|
+ Account name canonicalization ensures that the string used to identify
|
|
|
+ an account is consistent regardless of what was supplied to
|
|
|
+ <methodname>bind()</methodname>. For example, if the user supplies an account
|
|
|
+ name of <filename>abaker@example.com</filename> or just
|
|
|
+ <emphasis>abaker</emphasis> and the <emphasis>accountCanonicalForm</emphasis>
|
|
|
+ is set to 3, the resulting canonicalized name would be
|
|
|
+ <emphasis>EXAMPLE\abaker</emphasis>.
|
|
|
+ </para>
|
|
|
+ </sect3>
|
|
|
+
|
|
|
+ <sect3 id="zend.ldap.introduction.theory-of-operations.multi-domain-failover">
|
|
|
+ <title>Multi-domain Authentication and Failover</title>
|
|
|
+
|
|
|
+ <para>
|
|
|
+ The <classname>Zend_Ldap</classname> component by itself makes no attempt
|
|
|
+ to authenticate with multiple servers. However, <classname>Zend_Ldap</classname>
|
|
|
+ is specifically designed to handle this scenario gracefully. The
|
|
|
+ required technique is to simply iterate over an array of arrays of serve
|
|
|
+ options and attempt to bind with each server. As described above
|
|
|
+ <methodname>bind()</methodname> will automatically canonicalize each name, so
|
|
|
+ it does not matter if the user passes <filename>abaker@foo.net</filename> or
|
|
|
+ <emphasis>W\bcarter</emphasis> or <emphasis>cdavis</emphasis> - the
|
|
|
+ <methodname>bind()</methodname> method will only succeed if the credentials were
|
|
|
+ successfully used in the bind.
|
|
|
+ </para>
|
|
|
+
|
|
|
+ <para>
|
|
|
+ Consider the following example that illustrates the technique required to
|
|
|
+ implement multi-domain authentication and failover:
|
|
|
+ </para>
|
|
|
+
|
|
|
+ <programlisting language="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>
|
|
|
+ If the bind fails for any reason, the next set of server options is tried.
|
|
|
+ </para>
|
|
|
+
|
|
|
+ <para>
|
|
|
+ The <methodname>getCanonicalAccountName()</methodname> call gets the canonical
|
|
|
+ account name that the application would presumably use to associate data with such
|
|
|
+ as preferences. The <emphasis>accountCanonicalForm = 4</emphasis> in all server
|
|
|
+ options ensures that the canonical form is consistent regardless of which
|
|
|
+ server was ultimately used.
|
|
|
+ </para>
|
|
|
+
|
|
|
+ <para>
|
|
|
+ The special <constant>LDAP_X_DOMAIN_MISMATCH</constant> exception occurs when an
|
|
|
+ account name with a domain component was supplied (e.g.,
|
|
|
+ <filename>abaker@foo.net</filename> or <emphasis>FOO\abaker</emphasis> and not just
|
|
|
+ <emphasis>abaker</emphasis>) but the domain component did not match either domain
|
|
|
+ in the currently selected server options. This exception indicates
|
|
|
+ that the server is not an authority for the account. In this
|
|
|
+ case, the bind will not be performed, thereby eliminating unnecessary
|
|
|
+ communication with the server. Note that the <emphasis>continue</emphasis>
|
|
|
+ instruction has no effect in this example, but in practice for error handling and
|
|
|
+ debugging purposes, you will probably want to check for
|
|
|
+ <constant>LDAP_X_DOMAIN_MISMATCH</constant> as well as
|
|
|
+ <constant>LDAP_NO_SUCH_OBJECT</constant> and
|
|
|
+ <constant>LDAP_INVALID_CREDENTIALS</constant>.
|
|
|
+ </para>
|
|
|
+
|
|
|
+ <para>
|
|
|
+ The above code is very similar to code used within <link
|
|
|
+ linkend="zend.auth.adapter.ldap"><classname>Zend_Auth_Adapter_Ldap</classname></link>.
|
|
|
+ In fact, we recommend that you simply use that authentication adapter for
|
|
|
+ multi-domain + failover <acronym>LDAP</acronym> based authentication
|
|
|
+ (or copy the code).
|
|
|
+ </para>
|
|
|
+ </sect3>
|
|
|
+ </sect2>
|
|
|
+</sect1>
|