179f39653SAndreas Gohr<?php 21078ec26SAndreas Gohr 3208fe81aSAndreas Gohruse dokuwiki\Extension\AuthPlugin; 41078ec26SAndreas Gohruse dokuwiki\plugin\pureldap\classes\ADClient; 51078ec26SAndreas Gohruse dokuwiki\plugin\pureldap\classes\Client; 61078ec26SAndreas Gohr 779f39653SAndreas Gohr/** 879f39653SAndreas Gohr * DokuWiki Plugin pureldap (Auth Component) 979f39653SAndreas Gohr * 1079f39653SAndreas Gohr * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 1179f39653SAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 1279f39653SAndreas Gohr */ 13208fe81aSAndreas Gohrclass auth_plugin_pureldap extends AuthPlugin 1479f39653SAndreas Gohr{ 151078ec26SAndreas Gohr /** @var Client */ 160f498d06SAndreas Gohr public $client; 1779f39653SAndreas Gohr 1879f39653SAndreas Gohr /** 1979f39653SAndreas Gohr * Constructor. 2079f39653SAndreas Gohr */ 2179f39653SAndreas Gohr public function __construct() 2279f39653SAndreas Gohr { 231078ec26SAndreas Gohr global $conf; 2479f39653SAndreas Gohr parent::__construct(); // for compatibility 2579f39653SAndreas Gohr 261078ec26SAndreas Gohr // prepare the base client 271078ec26SAndreas Gohr $this->loadConfig(); 281078ec26SAndreas Gohr $this->conf['admin_password'] = conf_decodeString($this->conf['admin_password']); 291078ec26SAndreas Gohr $this->conf['defaultgroup'] = $conf['defaultgroup']; 301078ec26SAndreas Gohr 311078ec26SAndreas Gohr $this->client = new ADClient($this->conf); // FIXME decide class on config 3222654fdeSAndreas Gohr 3322654fdeSAndreas Gohr // set capabilities 3422654fdeSAndreas Gohr $this->cando['getUsers'] = true; 3522654fdeSAndreas Gohr $this->cando['getGroups'] = true; 3622654fdeSAndreas Gohr $this->cando['logout'] = !$this->client->getConf('sso'); 3708ace392SAndreas Gohr if ($this->client->getConf('encryption') !== 'none') { 3808ace392SAndreas Gohr // with encryption passwords can be changed 3908ace392SAndreas Gohr // for resetting passwords a privileged user is needed 4008ace392SAndreas Gohr $this->cando['modPass'] = true; 4108ace392SAndreas Gohr } 4208ace392SAndreas Gohr 4322654fdeSAndreas Gohr 4479f39653SAndreas Gohr $this->success = true; 4579f39653SAndreas Gohr } 4679f39653SAndreas Gohr 471078ec26SAndreas Gohr /** @inheritDoc */ 4879f39653SAndreas Gohr public function checkPass($user, $pass) 4979f39653SAndreas Gohr { 50bf69b89cSAndreas Gohr global $INPUT; 51bf69b89cSAndreas Gohr 52bf69b89cSAndreas Gohr // when SSO is enabled, the login is autotriggered and we simply trust the environment 53bf69b89cSAndreas Gohr if ( 5422654fdeSAndreas Gohr $this->client->getConf('sso') && 55bf69b89cSAndreas Gohr $INPUT->server->str('REMOTE_USER') !== '' && 56bf69b89cSAndreas Gohr $INPUT->server->str('REMOTE_USER') == $user 57bf69b89cSAndreas Gohr ) { 58bf69b89cSAndreas Gohr return true; 59bf69b89cSAndreas Gohr } 60bf69b89cSAndreas Gohr 6108ace392SAndreas Gohr // try to bind with the user credentials, client will stay authenticated as user 6208ace392SAndreas Gohr $this->client = new ADClient($this->conf); // FIXME decide class on config 63*fb75804eSAndreas Gohr try { 64*fb75804eSAndreas Gohr $this->client->authenticate($user, $pass); 65*fb75804eSAndreas Gohr return true; 66*fb75804eSAndreas Gohr } catch (\Exception $e) { 67*fb75804eSAndreas Gohr $this->parseErrorCodesToMessages($e); 68*fb75804eSAndreas Gohr return false; 69*fb75804eSAndreas Gohr } 7079f39653SAndreas Gohr } 7179f39653SAndreas Gohr 721078ec26SAndreas Gohr /** @inheritDoc */ 7379f39653SAndreas Gohr public function getUserData($user, $requireGroups = true) 7479f39653SAndreas Gohr { 755a3b9122SAndreas Gohr $info = $this->client->getCachedUser($user, $requireGroups); 761078ec26SAndreas Gohr return $info ?: false; 7779f39653SAndreas Gohr } 7879f39653SAndreas Gohr 7949b4734aSAndreas Gohr /** 8049b4734aSAndreas Gohr * @inheritDoc 8149b4734aSAndreas Gohr */ 82b21740b4SAndreas Gohr public function retrieveUsers($start = 0, $limit = 0, $filter = null) 83b21740b4SAndreas Gohr { 8485916a2dSAndreas Gohr return array_slice( 8585916a2dSAndreas Gohr $this->client->getFilteredUsers( 8685916a2dSAndreas Gohr $filter, 8749b4734aSAndreas Gohr Client::FILTER_CONTAINS 8885916a2dSAndreas Gohr ), 8985916a2dSAndreas Gohr $start, 90208fe81aSAndreas Gohr $limit 91208fe81aSAndreas Gohr ); 92b21740b4SAndreas Gohr } 9379f39653SAndreas Gohr 94b21740b4SAndreas Gohr /** @inheritDoc */ 95b21740b4SAndreas Gohr public function retrieveGroups($start = 0, $limit = 0) 96b21740b4SAndreas Gohr { 97b21740b4SAndreas Gohr return array_slice($this->client->getCachedGroups(), $start, $limit); 98b21740b4SAndreas Gohr } 9979f39653SAndreas Gohr 1006d90d5c8SAndreas Gohr /** @inheritDoc */ 10179f39653SAndreas Gohr public function isCaseSensitive() 10279f39653SAndreas Gohr { 1036d90d5c8SAndreas Gohr return false; 10479f39653SAndreas Gohr } 10579f39653SAndreas Gohr 1065da7f46bSAndreas Gohr /** @inheritDoc */ 10779f39653SAndreas Gohr public function cleanUser($user) 10879f39653SAndreas Gohr { 109a1128cc0SAndreas Gohr return $this->client->cleanUser($user); 11079f39653SAndreas Gohr } 11179f39653SAndreas Gohr 1125da7f46bSAndreas Gohr /** @inheritDoc */ 11379f39653SAndreas Gohr public function cleanGroup($group) 11479f39653SAndreas Gohr { 11579f39653SAndreas Gohr return $group; 11679f39653SAndreas Gohr } 11779f39653SAndreas Gohr 1186d90d5c8SAndreas Gohr /** @inheritDoc */ 1191078ec26SAndreas Gohr public function useSessionCache($user) 1201078ec26SAndreas Gohr { 1216d90d5c8SAndreas Gohr return true; 1221078ec26SAndreas Gohr } 12308ace392SAndreas Gohr 12408ace392SAndreas Gohr /** 12508ace392SAndreas Gohr * Support password changing 12608ace392SAndreas Gohr * @inheritDoc 12708ace392SAndreas Gohr */ 12808ace392SAndreas Gohr public function modifyUser($user, $changes) 12908ace392SAndreas Gohr { 13008ace392SAndreas Gohr if (empty($changes['pass'])) { 13108ace392SAndreas Gohr $this->client->error('Only password changes are supported', __FILE__, __LINE__); 13208ace392SAndreas Gohr return false; 13308ace392SAndreas Gohr } 13408ace392SAndreas Gohr 13508ace392SAndreas Gohr global $INPUT; 13608ace392SAndreas Gohr return $this->client->setPassword($user, $changes['pass'], $INPUT->str('oldpass', null, true)); 13708ace392SAndreas Gohr } 138*fb75804eSAndreas Gohr 139*fb75804eSAndreas Gohr /** 140*fb75804eSAndreas Gohr * Parse error codes from LDAP exceptions and output them as user-friendly messages. 141*fb75804eSAndreas Gohr * 142*fb75804eSAndreas Gohr * This is currently tailored for Active Directory bind errors. 143*fb75804eSAndreas Gohr * 144*fb75804eSAndreas Gohr * @param Exception $e 145*fb75804eSAndreas Gohr * @return void 146*fb75804eSAndreas Gohr */ 147*fb75804eSAndreas Gohr public function parseErrorCodesToMessages(\Exception $e) 148*fb75804eSAndreas Gohr { 149*fb75804eSAndreas Gohr // See https://ldapwiki.com/wiki/Wiki.jsp?page=Common%20Active%20Directory%20Bind%20Errors 150*fb75804eSAndreas Gohr $bind_errors = [ 151*fb75804eSAndreas Gohr '52f' => 'ERROR_ACCOUNT_RESTRICTION', 152*fb75804eSAndreas Gohr '530' => 'ERROR_INVALID_LOGON_HOURS', 153*fb75804eSAndreas Gohr '531' => 'ERROR_INVALID_WORKSTATION', 154*fb75804eSAndreas Gohr '532' => 'ERROR_PASSWORD_EXPIRED', 155*fb75804eSAndreas Gohr '533' => 'ERROR_ACCOUNT_DISABLED', 156*fb75804eSAndreas Gohr '701' => 'ERROR_ACCOUNT_EXPIRED', 157*fb75804eSAndreas Gohr '773' => 'ERROR_PASSWORD_MUST_CHANGE', 158*fb75804eSAndreas Gohr ]; 159*fb75804eSAndreas Gohr 160*fb75804eSAndreas Gohr if ( 161*fb75804eSAndreas Gohr $e instanceof \FreeDSx\Ldap\Exception\BindException && 162*fb75804eSAndreas Gohr $e->getCode() === 49 && 163*fb75804eSAndreas Gohr preg_match('/ data ([0-9a-f]{3})/', $e->getMessage(), $matches) 164*fb75804eSAndreas Gohr ) { 165*fb75804eSAndreas Gohr $code = $matches[1]; 166*fb75804eSAndreas Gohr if (isset($bind_errors[$code])) { 167*fb75804eSAndreas Gohr $message = $this->getLang($bind_errors[$code]) ?: $bind_errors[$code]; 168*fb75804eSAndreas Gohr 169*fb75804eSAndreas Gohr // on password expired or must change, add reset hint 170*fb75804eSAndreas Gohr if ($this->canDo('modPass') && ($code == 532 || $code == 773)) { 171*fb75804eSAndreas Gohr $link = '<a href="' . wl('start', ['do' => 'resendpwd']) . '" class="pureldap-reset-link">' . 172*fb75804eSAndreas Gohr $this->getLang('pass_reset') . '</a>'; 173*fb75804eSAndreas Gohr $message .= ' ' . $link; 174*fb75804eSAndreas Gohr } 175*fb75804eSAndreas Gohr 176*fb75804eSAndreas Gohr msg($message, -1); 177*fb75804eSAndreas Gohr } 178*fb75804eSAndreas Gohr } 179*fb75804eSAndreas Gohr } 180b21740b4SAndreas Gohr} 181