1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324
|
<?xml version="1.0" encoding="UTF-8"?>
<!-- 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>
|