1<?php 2 3use dokuwiki\Extension\AuthPlugin; 4use dokuwiki\plugin\pureldap\classes\ADClient; 5use dokuwiki\plugin\pureldap\classes\Client; 6 7/** 8 * DokuWiki Plugin pureldap (Auth Component) 9 * 10 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 11 * @author Andreas Gohr <andi@splitbrain.org> 12 */ 13class auth_plugin_pureldap extends AuthPlugin 14{ 15 /** @var Client */ 16 public $client; 17 18 /** 19 * Constructor. 20 */ 21 public function __construct() 22 { 23 global $conf; 24 parent::__construct(); // for compatibility 25 26 // prepare the base client 27 $this->loadConfig(); 28 $this->conf['admin_password'] = conf_decodeString($this->conf['admin_password']); 29 $this->conf['defaultgroup'] = $conf['defaultgroup']; 30 31 $this->client = new ADClient($this->conf); // FIXME decide class on config 32 33 // set capabilities 34 $this->cando['getUsers'] = true; 35 $this->cando['getGroups'] = true; 36 $this->cando['logout'] = !$this->client->getConf('sso'); 37 if ($this->client->getConf('encryption') !== 'none') { 38 // with encryption passwords can be changed 39 // for resetting passwords a privileged user is needed 40 $this->cando['modPass'] = true; 41 } 42 43 44 $this->success = true; 45 } 46 47 /** @inheritDoc */ 48 public function checkPass($user, $pass) 49 { 50 global $INPUT; 51 52 // when SSO is enabled, the login is autotriggered and we simply trust the environment 53 if ( 54 $this->client->getConf('sso') && 55 $INPUT->server->str('REMOTE_USER') !== '' && 56 $INPUT->server->str('REMOTE_USER') == $user 57 ) { 58 return true; 59 } 60 61 // try to bind with the user credentials, client will stay authenticated as user 62 $this->client = new ADClient($this->conf); // FIXME decide class on config 63 try { 64 $this->client->authenticate($user, $pass); 65 return true; 66 } catch (\Exception $e) { 67 $this->parseErrorCodesToMessages($e); 68 return false; 69 } 70 } 71 72 /** @inheritDoc */ 73 public function getUserData($user, $requireGroups = true) 74 { 75 $info = $this->client->getCachedUser($user, $requireGroups); 76 return $info ?: false; 77 } 78 79 /** 80 * @inheritDoc 81 */ 82 public function retrieveUsers($start = 0, $limit = 0, $filter = null) 83 { 84 return array_slice( 85 $this->client->getFilteredUsers( 86 $filter, 87 Client::FILTER_CONTAINS 88 ), 89 $start, 90 $limit 91 ); 92 } 93 94 /** @inheritDoc */ 95 public function retrieveGroups($start = 0, $limit = 0) 96 { 97 return array_slice($this->client->getCachedGroups(), $start, $limit); 98 } 99 100 /** @inheritDoc */ 101 public function isCaseSensitive() 102 { 103 return false; 104 } 105 106 /** @inheritDoc */ 107 public function cleanUser($user) 108 { 109 return $this->client->cleanUser($user); 110 } 111 112 /** @inheritDoc */ 113 public function cleanGroup($group) 114 { 115 return $group; 116 } 117 118 /** @inheritDoc */ 119 public function useSessionCache($user) 120 { 121 return true; 122 } 123 124 /** 125 * Support password changing 126 * @inheritDoc 127 */ 128 public function modifyUser($user, $changes) 129 { 130 if (empty($changes['pass'])) { 131 $this->client->error('Only password changes are supported', __FILE__, __LINE__); 132 return false; 133 } 134 135 global $INPUT; 136 return $this->client->setPassword($user, $changes['pass'], $INPUT->str('oldpass', null, true)); 137 } 138 139 /** 140 * Parse error codes from LDAP exceptions and output them as user-friendly messages. 141 * 142 * This is currently tailored for Active Directory bind errors. 143 * 144 * @param Exception $e 145 * @return void 146 */ 147 public function parseErrorCodesToMessages(\Exception $e) 148 { 149 // See https://ldapwiki.com/wiki/Wiki.jsp?page=Common%20Active%20Directory%20Bind%20Errors 150 $bind_errors = [ 151 '52f' => 'ERROR_ACCOUNT_RESTRICTION', 152 '530' => 'ERROR_INVALID_LOGON_HOURS', 153 '531' => 'ERROR_INVALID_WORKSTATION', 154 '532' => 'ERROR_PASSWORD_EXPIRED', 155 '533' => 'ERROR_ACCOUNT_DISABLED', 156 '701' => 'ERROR_ACCOUNT_EXPIRED', 157 '773' => 'ERROR_PASSWORD_MUST_CHANGE', 158 ]; 159 160 if ( 161 $e instanceof \FreeDSx\Ldap\Exception\BindException && 162 $e->getCode() === 49 && 163 preg_match('/ data ([0-9a-f]{3})/', $e->getMessage(), $matches) 164 ) { 165 $code = $matches[1]; 166 if (isset($bind_errors[$code])) { 167 $message = $this->getLang($bind_errors[$code]) ?: $bind_errors[$code]; 168 169 // on password expired or must change, add reset hint 170 if ($this->canDo('modPass') && ($code == 532 || $code == 773)) { 171 $link = '<a href="' . wl('start', ['do' => 'resendpwd']) . '" class="pureldap-reset-link">' . 172 $this->getLang('pass_reset') . '</a>'; 173 $message .= ' ' . $link; 174 } 175 176 msg($message, -1); 177 } 178 } 179 } 180} 181