1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18:
19: class Horde_Auth_Ldap extends Horde_Auth_Base
20: {
21: 22: 23: 24: 25: 26:
27: protected $_capabilities = array(
28: 'add' => true,
29: 'update' => true,
30: 'resetpassword' => true,
31: 'remove' => true,
32: 'list' => true,
33: 'authenticate' => true,
34: );
35:
36: 37: 38: 39: 40:
41: protected $_ldap;
42:
43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59:
60: public function __construct(array $params = array())
61: {
62: foreach (array('basedn', 'ldap', 'uid') as $val) {
63: if (!isset($params[$val])) {
64: throw new InvalidArgumentException(__CLASS__ . ': Missing ' . $val . ' parameter.');
65: }
66: }
67:
68: if (!empty($this->_params['ad'])) {
69: $this->_capabilities['resetpassword'] = false;
70: }
71:
72: $this->_ldap = $params['ldap'];
73: unset($params['ldap']);
74:
75: parent::__construct($params);
76: }
77:
78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89:
90: protected function _lookupShadow($dn)
91: {
92:
93: $lookupshadow = array(
94: 'shadowlastchange' => false,
95: 'shadowmin' => false,
96: 'shadowmax' => false,
97: 'shadowwarning' => false
98: );
99:
100: 101: 102: 103: 104: 105:
106: try {
107: $result = $this->_ldap->search(null, '(objectClass=*)', array(
108: 'attributes' => array(
109: 'pwdlastset',
110: 'shadowmax',
111: 'shadowmin',
112: 'shadowlastchange',
113: 'shadowwarning',
114: 'passwordexpirationtime'
115: ),
116: 'scope' => 'base'
117: ));
118: } catch (Horde_Ldap_Exception $e) {
119: return $lookupshadow;
120: }
121:
122: if (!$result) {
123: return $lookupshadow;
124: }
125:
126: $info = reset($result);
127:
128:
129: if (!empty($this->_params['ad'])) {
130: if (isset($info['pwdlastset'][0])) {
131: 132:
133: $lookupshadow['shadowlastchange'] = floor((($info['pwdlastset'][0] / 10000000) - 11644406783) / 86400) - 1;
134:
135: 136:
137: $lookupshadow['shadowwarning'] = $this->_params['warnage'];
138: $lookupshadow['shadowmin'] = $this->_params['minage'];
139: $lookupshadow['shadowmax'] = $this->_params['maxage'];
140: }
141: } elseif (isset($info['passwordexpirationtime'][0])) {
142: 143: 144: 145: 146: 147:
148: $ldaptimepattern = "/([0-9]{4})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})Z/";
149: if (preg_match($ldaptimepattern, $info['passwordexpirationtime'][0], $regs)) {
150: 151: 152:
153: $lookupshadow['shadowlastchange'] = floor(mktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]) / 86400) - $this->_params['maxage'];
154:
155: 156:
157: $lookupshadow['shadowwarning'] = $this->_params['warnage'];
158: $lookupshadow['shadowmin'] = $this->_params['minage'];
159: $lookupshadow['shadowmax'] = $this->_params['maxage'];
160: } elseif ($this->_logger) {
161: $this->_logger->log('Wrong time format: ' . $info['passwordexpirationtime'][0], 'ERR');
162: }
163: } else {
164: if (isset($info['shadowmax'][0])) {
165: $lookupshadow['shadowmax'] = $info['shadowmax'][0];
166: }
167: if (isset($info['shadowmin'][0])) {
168: $lookupshadow['shadowmin'] = $info['shadowmin'][0];
169: }
170: if (isset($info['shadowlastchange'][0])) {
171: $lookupshadow['shadowlastchange'] = $info['shadowlastchange'][0];
172: }
173: if (isset($info['shadowwarning'][0])) {
174: $lookupshadow['shadowwarning'] = $info['shadowwarning'][0];
175: }
176: }
177:
178: return $lookupshadow;
179: }
180:
181: 182: 183: 184: 185: 186: 187: 188:
189: protected function _authenticate($userId, $credentials)
190: {
191:
192: $this->_ldap->bind();
193: try {
194: $dn = $this->_ldap->findUserDN($userId);
195: } catch (Horde_Exception_NotFound $e) {
196: throw new Horde_Auth_Exception('', Horde_Auth::REASON_BADLOGIN);
197: } catch (Horde_Exception_Ldap $e) {
198: throw new Horde_Auth_Exception($e->getMessage(), Horde_Auth::REASON_MESSAGE);
199: }
200:
201:
202: try {
203: $this->_ldap->bind($dn, $credentials['password']);
204: } catch (Horde_Ldap_Exception $e) {
205: if (Horde_Ldap::errorName($e->getCode() == 'LDAP_INVALID_CREDENTIALS')) {
206: throw new Horde_Auth_Exception('', Horde_Auth::REASON_BADLOGIN);
207: }
208: throw new Horde_Auth_Exception($e->getMessage(), Horde_Auth::REASON_MESSAGE);
209: }
210:
211: if ($this->_params['password_expiration'] == 'yes') {
212: $shadow = $this->_lookupShadow($dn);
213: if ($shadow['shadowmax'] && $shadow['shadowlastchange'] &&
214: $shadow['shadowwarning']) {
215: $today = floor(time() / 86400);
216: $toexpire = $shadow['shadowlastchange'] +
217: $shadow['shadowmax'] - $today;
218:
219: $warnday = $shadow['shadowlastchange'] + $shadow['shadowmax'] - $shadow['shadowwarning'];
220: if ($today >= $warnday) {
221: $this->setCredential('expire', $toexpire);
222: }
223:
224: if ($toexpire == 0) {
225: $this->setCredential('change', true);
226: } elseif ($toexpire < 0) {
227: throw new Horde_Auth_Exception('', Horde_Auth::REASON_EXPIRED);
228: }
229: }
230: }
231: }
232:
233: 234: 235: 236: 237: 238: 239: 240:
241: public function addUser($userId, $credentials)
242: {
243: if (!empty($this->_params['ad'])) {
244: throw new Horde_Auth_Exception(__CLASS__ . ': Adding users is not supported for Active Directory.');
245: }
246:
247: if (isset($credentials['ldap'])) {
248: $entry = $credentials['ldap'];
249: $dn = $entry['dn'];
250:
251:
252: unset($entry['dn']);
253: } else {
254:
255: $dn = $this->_params['uid'] . '=' . $userId . ','
256: . $this->_params['basedn'];
257: $entry['cn'] = $userId;
258: $entry['sn'] = $userId;
259: $entry[$this->_params['uid']] = $userId;
260: $entry['objectclass'] = array_merge(
261: array('top'),
262: $this->_params['newuser_objectclass']);
263: $entry['userPassword'] = Horde_Auth::getCryptedPassword(
264: $credentials['password'], '',
265: $this->_params['encryption'],
266: 'true');
267:
268: if ($this->_params['password_expiration'] == 'yes') {
269: $entry['shadowMin'] = $this->_params['minage'];
270: $entry['shadowMax'] = $this->_params['maxage'];
271: $entry['shadowWarning'] = $this->_params['warnage'];
272: $entry['shadowLastChange'] = floor(time() / 86400);
273: }
274: }
275:
276: try {
277: $this->_ldap->add(Horde_Ldap_Entry::createFresh($dn, $entry));
278: } catch (Horde_Ldap_Exception $e) {
279: throw new Horde_Auth_Exception(sprintf(__CLASS__ . ': Unable to add user "%s". This is what the server said: ', $userId) . $e->getMessage());
280: }
281: }
282:
283: 284: 285: 286: 287: 288: 289: 290:
291: public function removeUser($userId, $dn = null)
292: {
293: if (!empty($this->_params['ad'])) {
294: throw new Horde_Auth_Exception(__CLASS__ . ': Removing users is not supported for Active Directory');
295: }
296:
297: if (is_null($dn)) {
298:
299: try {
300: $dn = $this->_ldap->findUserDN($userId);
301: } catch (Horde_Exception_Ldap $e) {
302: throw new Horde_Auth_Exception($e);
303: }
304: }
305:
306: try {
307: $this->_ldap->delete($dn);
308: } catch (Horde_Ldap_Exception $e) {
309: throw new Horde_Auth_Exception(sprintf(__CLASS__ . ': Unable to remove user "%s"', $userId));
310: }
311: }
312:
313: 314: 315: 316: 317: 318: 319: 320: 321: 322: 323: 324: 325:
326: public function updateUser($oldID, $newID, $credentials, $olddn = null,
327: $newdn = null)
328: {
329: if (!empty($this->_params['ad'])) {
330: throw new Horde_Auth_Exception(__CLASS__ . ': Updating users is not supported for Active Directory.');
331: }
332:
333: if (is_null($olddn)) {
334:
335: try {
336: $dn = $this->_ldap->findUserDN($oldID);
337: } catch (Horde_Exception_Ldap $e) {
338: throw new Horde_Auth_Exception($e);
339: }
340:
341: $olddn = $dn;
342: $newdn = preg_replace('/uid=.*?,/', 'uid=' . $newID . ',', $dn, 1);
343: $shadow = $this->_lookupShadow($dn);
344:
345: 346:
347: if ($shadow['shadowlastchange'] &&
348: $shadow['shadowmin'] &&
349: ($shadow['shadowlastchange'] + $shadow['shadowmin'] > (time() / 86400))) {
350: throw new Horde_Auth_Exception('Minimum password age has not yet expired');
351: }
352:
353:
354: if ($shadow['shadowlastchange']) {
355: $entry['shadowlastchange'] = floor(time() / 86400);
356: }
357:
358:
359: $entry['userpassword'] = Horde_Auth::getCryptedPassword(
360: $credentials['password'], '',
361: $this->_params['encryption'],
362: 'true');
363: } else {
364: $entry = $credentials;
365: unset($entry['dn']);
366: }
367:
368: try {
369: if ($oldID != $newID) {
370: $this->_ldap->move($olddn, $newdn);
371: $this->_ldap->modify($newdn, $entry);
372: } else {
373: $this->_ldap->modify($olddn, $entry);
374: }
375: } catch (Horde_Ldap_Exception $e) {
376: throw new Horde_Auth_Exception(sprintf(__CLASS__ . ': Unable to update user "%s"', $newID));
377: }
378: }
379:
380: 381: 382: 383: 384: 385: 386: 387: 388:
389: public function resetPassword($userId)
390: {
391: if (!empty($this->_params['ad'])) {
392: throw new Horde_Auth_Exception(__CLASS__ . ': Updating users is not supported for Active Directory.');
393: }
394:
395:
396: try {
397: $dn = $this->_ldap->findUserDN($userId);
398: } catch (Horde_Exception_Ldap $e) {
399: throw new Horde_Auth_Exception($e);
400: }
401:
402:
403: $password = Horde_Auth::genRandomPassword();
404:
405:
406: $entry = array(
407: 'userpassword' => Horde_Auth::getCryptedPassword($password,
408: '',
409: $this->_params['encryption'],
410: 'true'));
411:
412:
413: $shadow = $this->_lookupShadow($dn);
414: if ($shadow['shadowlastchange']) {
415: $entry['shadowlastchange'] = floor(time() / 86400);
416: }
417:
418:
419: try {
420: $this->_ldap->modify($dn, $entry);
421: } catch (Horde_Ldap_Exception $e) {
422: throw new Horde_Auth_Exception($e);
423: }
424:
425: return $password;
426: }
427:
428: 429: 430: 431: 432: 433:
434: public function listUsers($sort = false)
435: {
436: $params = array(
437: 'attributes' => array($this->_params['uid']),
438: 'scope' => $this->_params['scope'],
439: 'sizelimit' => isset($this->_params['sizelimit']) ? $this->_params['sizelimit'] : 0
440: );
441:
442: 443:
444: $userlist = array();
445: try {
446: $search = $this->_ldap->search(
447: $this->_params['basedn'],
448: Horde_Ldap_Filter::build(array('filter' => $this->_params['filter'])),
449: $params);
450: $uid = Horde_String::lower($this->_params['uid']);
451: foreach ($search as $val) {
452: $userlist[] = $val->getValue($uid, 'single');
453: }
454: } catch (Horde_Ldap_Exception $e) {}
455: return $this->_sort($userlist, $sort);
456: }
457:
458: 459: 460: 461: 462: 463: 464: 465: 466:
467: public function exists($userId)
468: {
469: $params = array(
470: 'scope' => $this->_params['scope']
471: );
472:
473: try {
474: $uidfilter = Horde_Ldap_Filter::create($this->_params['uid'], 'equals', $userId);
475: $classfilter = Horde_Ldap_Filter::build(array('filter' => $this->_params['filter']));
476:
477: $search = $this->_ldap->search(
478: $this->_params['basedn'],
479: Horde_Ldap_Filter::combine('and', array($uidfilter, $classfilter)),
480: $params);
481: if ($search->count() < 1) {
482: return false;
483: }
484: if ($search->count() > 1 && $this->_logger) {
485: $this->_logger->log('Multiple LDAP entries with user identifier ' . $userId, 'WARN');
486: }
487: return true;
488: } catch (Horde_Ldap_Exception $e) {
489: if ($this->_logger) {
490: $this->_logger->log('Error searching LDAP user: ' . $e->getMessage(), 'ERR');
491: }
492: return false;
493: }
494: }
495: }
496: