11078ec26SAndreas Gohr<?php 21078ec26SAndreas Gohr 31078ec26SAndreas Gohrnamespace dokuwiki\plugin\pureldap\classes; 41078ec26SAndreas Gohr 580ac552fSAndreas Gohruse dokuwiki\Utf8\PhpString; 61078ec26SAndreas Gohruse FreeDSx\Ldap\Entry\Attribute; 71078ec26SAndreas Gohruse FreeDSx\Ldap\Exception\BindException; 81078ec26SAndreas Gohruse FreeDSx\Ldap\Exception\ConnectionException; 91078ec26SAndreas Gohruse FreeDSx\Ldap\Exception\OperationException; 101078ec26SAndreas Gohruse FreeDSx\Ldap\LdapClient; 111078ec26SAndreas Gohr 121078ec26SAndreas Gohrrequire_once __DIR__ . '/../vendor/autoload.php'; 131078ec26SAndreas Gohr 141078ec26SAndreas Gohrabstract class Client 151078ec26SAndreas Gohr{ 161078ec26SAndreas Gohr /** @var array the configuration */ 171078ec26SAndreas Gohr protected $config; 181078ec26SAndreas Gohr 191078ec26SAndreas Gohr /** @var LdapClient */ 201078ec26SAndreas Gohr protected $ldap; 211078ec26SAndreas Gohr 221078ec26SAndreas Gohr /** @var bool is this client authenticated already? */ 231078ec26SAndreas Gohr protected $isAuthenticated = false; 241078ec26SAndreas Gohr 251078ec26SAndreas Gohr /** @var array cached user info */ 261078ec26SAndreas Gohr protected $userCache = []; 271078ec26SAndreas Gohr 285a3b9122SAndreas Gohr /** @var array cached group list */ 295a3b9122SAndreas Gohr protected $groupCache = []; 305a3b9122SAndreas Gohr 311078ec26SAndreas Gohr /** 321078ec26SAndreas Gohr * Client constructor. 331078ec26SAndreas Gohr * @param array $config 341078ec26SAndreas Gohr */ 351078ec26SAndreas Gohr public function __construct($config) 361078ec26SAndreas Gohr { 371078ec26SAndreas Gohr $this->config = $this->prepareConfig($config); 381078ec26SAndreas Gohr $this->ldap = new LdapClient($this->config); 391078ec26SAndreas Gohr } 401078ec26SAndreas Gohr 411078ec26SAndreas Gohr /** 421078ec26SAndreas Gohr * Setup sane config defaults 431078ec26SAndreas Gohr * 441078ec26SAndreas Gohr * @param array $config 451078ec26SAndreas Gohr * @return array 461078ec26SAndreas Gohr */ 471078ec26SAndreas Gohr protected function prepareConfig($config) 481078ec26SAndreas Gohr { 491078ec26SAndreas Gohr $defaults = [ 501078ec26SAndreas Gohr 'defaultgroup' => 'user', // we expect this to be passed from global conf 5180ac552fSAndreas Gohr 'domain' => '', 521078ec26SAndreas Gohr 'port' => '', 53*6d90d5c8SAndreas Gohr 'encryption' => false, 541078ec26SAndreas Gohr 'admin_username' => '', 551078ec26SAndreas Gohr 'admin_password' => '', 565a3b9122SAndreas Gohr 'page_size' => 1000, 57*6d90d5c8SAndreas Gohr 'use_ssl' => false, 58*6d90d5c8SAndreas Gohr 'validate' => 'strict', 591078ec26SAndreas Gohr ]; 601078ec26SAndreas Gohr 611078ec26SAndreas Gohr $config = array_merge($defaults, $config); 621078ec26SAndreas Gohr 631078ec26SAndreas Gohr // default port depends on SSL setting 641078ec26SAndreas Gohr if (!$config['port']) { 65*6d90d5c8SAndreas Gohr $config['port'] = ($config['encryption'] === 'ssl') ? 636 : 389; 66*6d90d5c8SAndreas Gohr } 67*6d90d5c8SAndreas Gohr 68*6d90d5c8SAndreas Gohr // set ssl parameters 69*6d90d5c8SAndreas Gohr $config['use_ssl'] = ($config['encryption'] === 'ssl'); 70*6d90d5c8SAndreas Gohr if ($config['validate'] === 'none') { 71*6d90d5c8SAndreas Gohr $config['ssl_validate_cert'] = false; 72*6d90d5c8SAndreas Gohr } elseif ($config['validate'] === 'self') { 73*6d90d5c8SAndreas Gohr $config['ssl_allow_self_signed'] = true; 741078ec26SAndreas Gohr } 751078ec26SAndreas Gohr 7680ac552fSAndreas Gohr $config['domain'] = PhpString::strtolower($config['domain']); 7780ac552fSAndreas Gohr 781078ec26SAndreas Gohr return $config; 791078ec26SAndreas Gohr } 801078ec26SAndreas Gohr 811078ec26SAndreas Gohr /** 821078ec26SAndreas Gohr * Authenticate as admin 831078ec26SAndreas Gohr */ 841078ec26SAndreas Gohr public function autoAuth() 851078ec26SAndreas Gohr { 861078ec26SAndreas Gohr if ($this->isAuthenticated) return true; 871078ec26SAndreas Gohr return $this->authenticate($this->config['admin_username'], $this->config['admin_password']); 881078ec26SAndreas Gohr } 891078ec26SAndreas Gohr 901078ec26SAndreas Gohr /** 911078ec26SAndreas Gohr * Authenticates a given user. This client will remain authenticated 921078ec26SAndreas Gohr * 931078ec26SAndreas Gohr * @param string $user 941078ec26SAndreas Gohr * @param string $pass 951078ec26SAndreas Gohr * @return bool was the authentication successful? 965a3b9122SAndreas Gohr * @noinspection PhpRedundantCatchClauseInspection 971078ec26SAndreas Gohr */ 981078ec26SAndreas Gohr public function authenticate($user, $pass) 991078ec26SAndreas Gohr { 10080ac552fSAndreas Gohr $user = $this->qualifiedUser($user); 10180ac552fSAndreas Gohr 102*6d90d5c8SAndreas Gohr if ($this->config['encryption'] === 'tls') { 1031078ec26SAndreas Gohr try { 1041078ec26SAndreas Gohr $this->ldap->startTls(); 1051078ec26SAndreas Gohr } catch (OperationException $e) { 106da369b60SAndreas Gohr $this->fatal($e); 1071078ec26SAndreas Gohr } 1081078ec26SAndreas Gohr } 1091078ec26SAndreas Gohr 1101078ec26SAndreas Gohr try { 1111078ec26SAndreas Gohr $this->ldap->bind($user, $pass); 1121078ec26SAndreas Gohr } catch (BindException $e) { 1131078ec26SAndreas Gohr return false; 1141078ec26SAndreas Gohr } catch (ConnectionException $e) { 115da369b60SAndreas Gohr $this->fatal($e); 1161078ec26SAndreas Gohr return false; 1171078ec26SAndreas Gohr } catch (OperationException $e) { 118da369b60SAndreas Gohr $this->fatal($e); 1191078ec26SAndreas Gohr return false; 1201078ec26SAndreas Gohr } 1211078ec26SAndreas Gohr 1221078ec26SAndreas Gohr $this->isAuthenticated = true; 1231078ec26SAndreas Gohr return true; 1241078ec26SAndreas Gohr } 1251078ec26SAndreas Gohr 1261078ec26SAndreas Gohr /** 1271078ec26SAndreas Gohr * Get info for a single user, use cache if available 1281078ec26SAndreas Gohr * 1291078ec26SAndreas Gohr * @param string $username 1301078ec26SAndreas Gohr * @param bool $fetchgroups Are groups needed? 1311078ec26SAndreas Gohr * @return array|null 1321078ec26SAndreas Gohr */ 1331078ec26SAndreas Gohr public function getCachedUser($username, $fetchgroups = true) 1341078ec26SAndreas Gohr { 135*6d90d5c8SAndreas Gohr global $conf; 136*6d90d5c8SAndreas Gohr 137*6d90d5c8SAndreas Gohr // memory cache first 1381078ec26SAndreas Gohr if (isset($this->userCache[$username])) { 1391078ec26SAndreas Gohr if (!$fetchgroups || is_array($this->userCache[$username]['grps'])) { 1401078ec26SAndreas Gohr return $this->userCache[$username]; 1411078ec26SAndreas Gohr } 1421078ec26SAndreas Gohr } 1431078ec26SAndreas Gohr 144*6d90d5c8SAndreas Gohr // disk cache second 145*6d90d5c8SAndreas Gohr $cachename = getCacheName($username, '.pureldap-user'); 146*6d90d5c8SAndreas Gohr $cachetime = @filemtime($cachename); 147*6d90d5c8SAndreas Gohr if ($cachetime && (time() - $cachetime) < $conf['auth_security_timeout']) { 148*6d90d5c8SAndreas Gohr $this->userCache[$username] = json_decode(file_get_contents($cachename), true); 149*6d90d5c8SAndreas Gohr if (!$fetchgroups || is_array($this->userCache[$username]['grps'])) { 150*6d90d5c8SAndreas Gohr return $this->userCache[$username]; 151*6d90d5c8SAndreas Gohr } 152*6d90d5c8SAndreas Gohr } 153*6d90d5c8SAndreas Gohr 1541078ec26SAndreas Gohr // fetch fresh data 1551078ec26SAndreas Gohr $info = $this->getUser($username, $fetchgroups); 1561078ec26SAndreas Gohr 1571078ec26SAndreas Gohr // store in cache 1581078ec26SAndreas Gohr if ($info !== null) { 1591078ec26SAndreas Gohr $this->userCache[$username] = $info; 160*6d90d5c8SAndreas Gohr file_put_contents($cachename, json_encode($info)); 1611078ec26SAndreas Gohr } 1621078ec26SAndreas Gohr 1631078ec26SAndreas Gohr return $info; 1641078ec26SAndreas Gohr } 1651078ec26SAndreas Gohr 1661078ec26SAndreas Gohr /** 1671078ec26SAndreas Gohr * Fetch a single user 1681078ec26SAndreas Gohr * 1691078ec26SAndreas Gohr * @param string $username 1701078ec26SAndreas Gohr * @param bool $fetchgroups Shall groups be fetched, too? 1711078ec26SAndreas Gohr * @return null|array 1721078ec26SAndreas Gohr */ 1731078ec26SAndreas Gohr abstract public function getUser($username, $fetchgroups = true); 1741078ec26SAndreas Gohr 1751078ec26SAndreas Gohr /** 1765a3b9122SAndreas Gohr * Return a list of all available groups, use cache if available 1775a3b9122SAndreas Gohr * 1785a3b9122SAndreas Gohr * @return string[] 1795a3b9122SAndreas Gohr */ 1805a3b9122SAndreas Gohr public function getCachedGroups() 1815a3b9122SAndreas Gohr { 1825a3b9122SAndreas Gohr if (empty($this->groupCache)) { 1835a3b9122SAndreas Gohr $this->groupCache = $this->getGroups(); 1845a3b9122SAndreas Gohr } 1855a3b9122SAndreas Gohr 1865a3b9122SAndreas Gohr return $this->groupCache; 1875a3b9122SAndreas Gohr } 1885a3b9122SAndreas Gohr 1895a3b9122SAndreas Gohr /** 1905a3b9122SAndreas Gohr * Return a list of all available groups 1915a3b9122SAndreas Gohr * 192b21740b4SAndreas Gohr * Optionally filter the list 193b21740b4SAndreas Gohr * 194b21740b4SAndreas Gohr * @param null|string $match Filter for this, null for all groups 195b21740b4SAndreas Gohr * @param string $filtermethod How to match the groups 1965a3b9122SAndreas Gohr * @return string[] 1975a3b9122SAndreas Gohr */ 198b21740b4SAndreas Gohr abstract public function getGroups($match = null, $filtermethod = 'equal'); 1995a3b9122SAndreas Gohr 20080ac552fSAndreas Gohr /** 20180ac552fSAndreas Gohr * Construst the fully qualified name to identify a user 20280ac552fSAndreas Gohr * 20380ac552fSAndreas Gohr * @param string $username 20480ac552fSAndreas Gohr * @return string 20580ac552fSAndreas Gohr */ 20680ac552fSAndreas Gohr abstract public function qualifiedUser($username); 20780ac552fSAndreas Gohr 20880ac552fSAndreas Gohr /** 20980ac552fSAndreas Gohr * Simplify the username if possible 21080ac552fSAndreas Gohr * 21180ac552fSAndreas Gohr * @param string $username 21280ac552fSAndreas Gohr * @return string 21380ac552fSAndreas Gohr */ 21480ac552fSAndreas Gohr abstract public function simpleUser($username); 21580ac552fSAndreas Gohr 2165a3b9122SAndreas Gohr /** 2171078ec26SAndreas Gohr * Helper method to get the first value of the given attribute 2181078ec26SAndreas Gohr * 2191078ec26SAndreas Gohr * The given attribute may be null, an empty string is returned then 2201078ec26SAndreas Gohr * 2211078ec26SAndreas Gohr * @param Attribute|null $attribute 2221078ec26SAndreas Gohr * @return string 2231078ec26SAndreas Gohr */ 2245a3b9122SAndreas Gohr protected function attr2str($attribute) 2255a3b9122SAndreas Gohr { 2261078ec26SAndreas Gohr if ($attribute !== null) { 2271078ec26SAndreas Gohr return $attribute->firstValue(); 2281078ec26SAndreas Gohr } 2291078ec26SAndreas Gohr return ''; 2301078ec26SAndreas Gohr } 2311078ec26SAndreas Gohr 2321078ec26SAndreas Gohr /** 233da369b60SAndreas Gohr * Handle fatal exceptions 2341078ec26SAndreas Gohr * 2351078ec26SAndreas Gohr * @param \Exception $e 2361078ec26SAndreas Gohr */ 237da369b60SAndreas Gohr protected function fatal(\Exception $e) 2381078ec26SAndreas Gohr { 2391078ec26SAndreas Gohr if (defined('DOKU_UNITTEST')) { 2408595f73eSAndreas Gohr throw new \RuntimeException('', 0, $e); 2411078ec26SAndreas Gohr } 242da369b60SAndreas Gohr msg('[pureldap] ' . hsc($e->getMessage()) . ' at ' . $e->getFile() . ':' . $e->getLine(), -1); 243da369b60SAndreas Gohr } 2441078ec26SAndreas Gohr 245da369b60SAndreas Gohr /** 246da369b60SAndreas Gohr * Handle debug output 247da369b60SAndreas Gohr * 248da369b60SAndreas Gohr * @param string $msg 249da369b60SAndreas Gohr * @param string $file 250da369b60SAndreas Gohr * @param int $line 251da369b60SAndreas Gohr */ 252da369b60SAndreas Gohr protected function debug($msg, $file, $line) 253da369b60SAndreas Gohr { 25485916a2dSAndreas Gohr msg('[pureldap] ' . hsc($msg) . ' at ' . $file . ':' . $line, 0); 2551078ec26SAndreas Gohr } 2561078ec26SAndreas Gohr} 257