|
| 1 | +# Introduction |
| 2 | + |
| 3 | +zend-ldap lets you perform LDAP operations, including, but not limited to, |
| 4 | +binding, searching and modifying entries in an LDAP directory. |
| 5 | + |
| 6 | +## Theory of operation |
| 7 | + |
| 8 | +This component currently consists of the main `Zend\Ldap\Ldap` class, which |
| 9 | +conceptually represents a binding to a single LDAP server and allows for |
| 10 | +executing operations against a LDAP server such as OpenLDAP or ActiveDirectory |
| 11 | +(AD) servers. The parameters for binding may be provided explicitly or in the |
| 12 | +form of an options array. `Zend\Ldap\Node` provides an object-oriented interface |
| 13 | +for single LDAP nodes and can be used to form a basis for an active-record-like |
| 14 | +interface for a LDAP-based domain model. |
| 15 | + |
| 16 | +The component provides several helper classes to perform operations on LDAP |
| 17 | +entries (`Zend\Ldap\Attribute`) such as setting and retrieving attributes (date |
| 18 | +values, passwords, boolean values, ...), to create and modify LDAP filter |
| 19 | +strings (`Zend\Ldap\Filter`) and to manipulate LDAP distinguished names (DN) |
| 20 | +(`Zend\Ldap\Dn`). |
| 21 | + |
| 22 | +Additionally the component abstracts LDAP schema browsing for OpenLDAP and |
| 23 | +ActiveDirectory servers `Zend\Ldap\Node\Schema` and server information retrieval |
| 24 | +for OpenLDAP-, ActiveDirectory- and Novell eDirectory servers |
| 25 | +(`Zend\Ldap\Node\RootDse`). |
| 26 | + |
| 27 | +Usage of zend-ldap depends on the type of LDAP server, and is best summarized with |
| 28 | +some examples. |
| 29 | + |
| 30 | +If you are using OpenLDAP, consider the following example (note that the |
| 31 | +`bindRequiresDn` option is important if you are **not** using AD): |
| 32 | + |
| 33 | +```php |
| 34 | +use Zend\Ldap\Ldap; |
| 35 | + |
| 36 | +$options = [ |
| 37 | + 'host' => 's0.foo.net', |
| 38 | + 'username' => 'CN=user1,DC=foo,DC=net', |
| 39 | + 'password' => 'pass1', |
| 40 | + 'bindRequiresDn' => true, |
| 41 | + 'accountDomainName' => 'foo.net', |
| 42 | + 'baseDn' => 'OU=Sales,DC=foo,DC=net', |
| 43 | +]; |
| 44 | + |
| 45 | +$ldap = new Ldap($options); |
| 46 | +$acctname = $ldap->getCanonicalAccountName('abaker', Ldap::ACCTNAME_FORM_DN); |
| 47 | +echo "$acctname\n"; |
| 48 | +``` |
| 49 | + |
| 50 | +If you are using Microsoft AD: |
| 51 | + |
| 52 | +```php |
| 53 | +use Zend\Ldap\Ldap; |
| 54 | + |
| 55 | +$options = [ |
| 56 | + 'host' => 'dc1.w.net', |
| 57 | + 'useStartTls' => true, |
| 58 | + 'username' => ' [email protected]', |
| 59 | + 'password' => 'pass1', |
| 60 | + 'accountDomainName' => 'w.net', |
| 61 | + 'accountDomainNameShort' => 'W', |
| 62 | + 'baseDn' => 'CN=Users,DC=w,DC=net', |
| 63 | +]; |
| 64 | + |
| 65 | +$ldap = new Ldap($options); |
| 66 | +$acctname = $ldap->getCanonicalAccountName('bcarter', Ldap::ACCTNAME_FORM_DN); |
| 67 | +echo "$acctname\n"; |
| 68 | +``` |
| 69 | + |
| 70 | +Note that we use the `getCanonicalAccountName()` method to retrieve the account |
| 71 | +DN here only because that is what exercises the most of what little code is |
| 72 | +currently present in this class. |
| 73 | + |
| 74 | +### Automatic Username Canonicalization When Binding |
| 75 | + |
| 76 | +If `bind()` is called with a non-DN username but `bindRequiresDN` is `true` |
| 77 | +and no username in DN form was supplied as an option, the bind will fail. |
| 78 | +However, if a username in DN form is supplied in the options array, |
| 79 | +`Zend\Ldap\Ldap` will first bind with that username, retrieve the account DN for |
| 80 | +the username supplied to `bind()` and then re-bind with that DN. |
| 81 | + |
| 82 | +This behavior is critical to [Zend\\Authentication\\Adapter\\Ldap](http://zendframework.github.io/zend-authentication/adapter/ldap/), |
| 83 | +which passes the username supplied by the user directly to `bind()`. |
| 84 | + |
| 85 | +The following example illustrates how the non-DN username 'abaker' can be used |
| 86 | +with `bind()`: |
| 87 | + |
| 88 | +```php |
| 89 | +use Zend\Ldap\Ldap; |
| 90 | + |
| 91 | +$options = [ |
| 92 | + 'host' => 's0.foo.net', |
| 93 | + 'username' => 'CN=user1,DC=foo,DC=net', |
| 94 | + 'password' => 'pass1', |
| 95 | + 'bindRequiresDn' => true, |
| 96 | + 'accountDomainName' => 'foo.net', |
| 97 | + 'baseDn' => 'OU=Sales,DC=foo,DC=net', |
| 98 | +]; |
| 99 | + |
| 100 | +$ldap = new Ldap($options); |
| 101 | +$ldap->bind('abaker', 'moonbike55'); |
| 102 | +$acctname = $ldap->getCanonicalAccountName('abaker', Ldap::ACCTNAME_FORM_DN); |
| 103 | +echo "$acctname\n"; |
| 104 | +``` |
| 105 | + |
| 106 | +The `bind()` call in this example sees that the username 'abaker' is not in DN |
| 107 | +form, finds `bindRequiresDn` is `TRUE`, uses `CN=user1,DC=foo,DC=net` and |
| 108 | +`pass1` to bind, retrieves the DN for 'abaker', unbinds and then rebinds with |
| 109 | +the newly discovered `CN=Alice Baker,OU=Sales,DC=foo,DC=net`. |
| 110 | + |
| 111 | +### Account Name Canonicalization |
| 112 | + |
| 113 | +The `accountDomainName` and `accountDomainNameShort` options are used for two |
| 114 | +purposes: (1) they facilitate multi-domain authentication and failover |
| 115 | +capability, and (2) they are also used to canonicalize usernames. Specifically, |
| 116 | +names are canonicalized to the form specified by the `accountCanonicalForm` |
| 117 | +option. This option may one of the following values: |
| 118 | + |
| 119 | +The default canonicalization depends on what account domain name options were |
| 120 | +supplied. If `accountDomainNameShort` was supplied, the default |
| 121 | +`accountCanonicalForm` value is `ACCTNAME_FORM_BACKSLASH`. Otherwise, if |
| 122 | +`accountDomainName` was supplied, the default is `ACCTNAME_FORM_PRINCIPAL`. |
| 123 | + |
| 124 | +Account name canonicalization ensures that the string used to identify an |
| 125 | +account is consistent regardless of what was supplied to `bind()`. For example, |
| 126 | +if the user supplies an account name of `[email protected]` or just `abaker` |
| 127 | +and the `accountCanonicalForm` is set to 3, the resulting canonicalized name |
| 128 | +would be `EXAMPLE\\abaker`. |
| 129 | + |
| 130 | +### Multi-domain Authentication and Failover |
| 131 | + |
| 132 | +The `Zend\Ldap\Ldap` component by itself makes no attempt to authenticate with |
| 133 | +multiple servers. However, `Zend\Ldap\Ldap` is specifically designed to handle |
| 134 | +this scenario gracefully. The required technique is to simply iterate over an |
| 135 | +array of arrays of serve options and attempt to bind with each server. As |
| 136 | +described above `bind()` will automatically canonicalize each name, so it does |
| 137 | +not matter if the user passes `[email protected]` or `Wbcarter` or `cdavis`; the |
| 138 | +`bind()` method will only succeed if the credentials were successfully used in |
| 139 | +the bind. |
| 140 | + |
| 141 | +Consider the following example that illustrates the technique required to |
| 142 | +implement multi-domain authentication and failover: |
| 143 | + |
| 144 | +```php |
| 145 | +use Zend\Ldap\Exception\LdapException; |
| 146 | +use Zend\Ldap\Ldap; |
| 147 | + |
| 148 | +$acctname = 'W\\user2'; |
| 149 | +$password = 'pass2'; |
| 150 | + |
| 151 | +$multiOptions = [ |
| 152 | + 'server1' => [ |
| 153 | + 'host' => 's0.foo.net', |
| 154 | + 'username' => 'CN=user1,DC=foo,DC=net', |
| 155 | + 'password' => 'pass1', |
| 156 | + 'bindRequiresDn' => true, |
| 157 | + 'accountDomainName' => 'foo.net', |
| 158 | + 'accountDomainNameShort' => 'FOO', |
| 159 | + 'accountCanonicalForm' => 4, // ACCT_FORM_PRINCIPAL |
| 160 | + 'baseDn' => 'OU=Sales,DC=foo,DC=net', |
| 161 | + ], |
| 162 | + 'server2' => [ |
| 163 | + 'host' => 'dc1.w.net', |
| 164 | + 'useSsl' => true, |
| 165 | + 'username' => ' [email protected]', |
| 166 | + 'password' => 'pass1', |
| 167 | + 'accountDomainName' => 'w.net', |
| 168 | + 'accountDomainNameShort' => 'W', |
| 169 | + 'accountCanonicalForm' => 4, // ACCT_FORM_PRINCIPAL |
| 170 | + 'baseDn' => 'CN=Users,DC=w,DC=net', |
| 171 | + ], |
| 172 | +]; |
| 173 | + |
| 174 | +$ldap = new Ldap(); |
| 175 | + |
| 176 | +foreach ($multiOptions as $name => $options) { |
| 177 | + echo "Trying to bind using server options for '$name'\n"; |
| 178 | + |
| 179 | + $ldap->setOptions($options); |
| 180 | + try { |
| 181 | + $ldap->bind($acctname, $password); |
| 182 | + $acctname = $ldap->getCanonicalAccountName($acctname); |
| 183 | + echo "SUCCESS: authenticated $acctname\n"; |
| 184 | + return; |
| 185 | + } catch (LdapException $zle) { |
| 186 | + echo ' ' . $zle->getMessage() . "\n"; |
| 187 | + if ($zle->getCode() === LdapException::LDAP_X_DOMAIN_MISMATCH) { |
| 188 | + continue; |
| 189 | + } |
| 190 | + } |
| 191 | +} |
| 192 | +``` |
| 193 | + |
| 194 | +If the bind fails for any reason, the next set of server options is tried. |
| 195 | + |
| 196 | +The `getCanonicalAccountName()` call gets the canonical account name that the |
| 197 | +application would presumably use to associate data with such as preferences. The |
| 198 | +`accountCanonicalForm = 4` in all server options ensures that the canonical form |
| 199 | +is consistent regardless of which server was ultimately used. |
| 200 | + |
| 201 | +The special `LDAP_X_DOMAIN_MISMATCH` exception occurs when an account name with |
| 202 | +a domain component was supplied (e.g., `[email protected]` or `FOO\\abaker` and not |
| 203 | +just `abaker`) but the domain component did not match either domain in the |
| 204 | +currently selected server options. This exception indicates that the server is |
| 205 | +not an authority for the account. In this case, the bind will not be performed, |
| 206 | +thereby eliminating unnecessary communication with the server. Note that the |
| 207 | +`continue` instruction has no effect in this example, but in practice for error |
| 208 | +handling and debugging purposes, you will probably want to check for |
| 209 | +`LDAP_X_DOMAIN_MISMATCH` as well as `LDAP_NO_SUCH_OBJECT` and |
| 210 | +`LDAP_INVALID_CREDENTIALS`. |
| 211 | + |
| 212 | +The above code is very similar to code used within |
| 213 | +[Zend\\Authentication\\Adapter\\Ldap](http://zendframework.github.io/zend-authentication/adapter/ldap/). |
| 214 | +In fact,we recommend that you use that authentication adapter for multi-domain + |
| 215 | +failover LDAP based authentication (or copy the code). |
0 commit comments