1<?php 2 3namespace dokuwiki\plugin\pureldap\classes; 4 5use dokuwiki\Utf8\PhpString; 6use FreeDSx\Ldap\Entry\Attribute; 7use FreeDSx\Ldap\Exception\BindException; 8use FreeDSx\Ldap\Exception\ConnectionException; 9use FreeDSx\Ldap\Exception\OperationException; 10use FreeDSx\Ldap\LdapClient; 11 12require_once __DIR__ . '/../vendor/autoload.php'; 13 14abstract class Client 15{ 16 /** @var array the configuration */ 17 protected $config; 18 19 /** @var LdapClient */ 20 protected $ldap; 21 22 /** @var bool is this client authenticated already? */ 23 protected $isAuthenticated = false; 24 25 /** @var array cached user info */ 26 protected $userCache = []; 27 28 /** @var array cached group list */ 29 protected $groupCache = []; 30 31 /** 32 * Client constructor. 33 * @param array $config 34 */ 35 public function __construct($config) 36 { 37 $this->config = $this->prepareConfig($config); 38 $this->ldap = new LdapClient($this->config); 39 } 40 41 /** 42 * Setup sane config defaults 43 * 44 * @param array $config 45 * @return array 46 */ 47 protected function prepareConfig($config) 48 { 49 $defaults = [ 50 'defaultgroup' => 'user', // we expect this to be passed from global conf 51 'domain' => '', 52 'port' => '', 53 'encryption' => false, 54 'admin_username' => '', 55 'admin_password' => '', 56 'page_size' => 1000, 57 'use_ssl' => false, 58 'validate' => 'strict', 59 ]; 60 61 $config = array_merge($defaults, $config); 62 63 // default port depends on SSL setting 64 if (!$config['port']) { 65 $config['port'] = ($config['encryption'] === 'ssl') ? 636 : 389; 66 } 67 68 // set ssl parameters 69 $config['use_ssl'] = ($config['encryption'] === 'ssl'); 70 if ($config['validate'] === 'none') { 71 $config['ssl_validate_cert'] = false; 72 } elseif ($config['validate'] === 'self') { 73 $config['ssl_allow_self_signed'] = true; 74 } 75 76 $config['domain'] = PhpString::strtolower($config['domain']); 77 78 return $config; 79 } 80 81 /** 82 * Authenticate as admin 83 */ 84 public function autoAuth() 85 { 86 if ($this->isAuthenticated) return true; 87 $ok = $this->authenticate($this->config['admin_username'], $this->config['admin_password']); 88 if(!$ok) { 89 $this->debug('Administrative bind failed. Probably wrong user/password.', __FILE__, __LINE__); 90 } 91 return $ok; 92 } 93 94 /** 95 * Authenticates a given user. This client will remain authenticated 96 * 97 * @param string $user 98 * @param string $pass 99 * @return bool was the authentication successful? 100 * @noinspection PhpRedundantCatchClauseInspection 101 */ 102 public function authenticate($user, $pass) 103 { 104 $user = $this->qualifiedUser($user); 105 106 if ($this->config['encryption'] === 'tls') { 107 try { 108 $this->ldap->startTls(); 109 } catch (OperationException $e) { 110 $this->fatal($e); 111 } 112 } 113 114 try { 115 $this->ldap->bind($user, $pass); 116 } catch (BindException $e) { 117 return false; 118 } catch (ConnectionException $e) { 119 $this->fatal($e); 120 return false; 121 } catch (OperationException $e) { 122 $this->fatal($e); 123 return false; 124 } 125 126 $this->isAuthenticated = true; 127 return true; 128 } 129 130 /** 131 * Get info for a single user, use cache if available 132 * 133 * @param string $username 134 * @param bool $fetchgroups Are groups needed? 135 * @return array|null 136 */ 137 public function getCachedUser($username, $fetchgroups = true) 138 { 139 global $conf; 140 141 // memory cache first 142 if (isset($this->userCache[$username])) { 143 if (!$fetchgroups || is_array($this->userCache[$username]['grps'])) { 144 return $this->userCache[$username]; 145 } 146 } 147 148 // disk cache second 149 $cachename = getCacheName($username, '.pureldap-user'); 150 $cachetime = @filemtime($cachename); 151 if ($cachetime && (time() - $cachetime) < $conf['auth_security_timeout']) { 152 $this->userCache[$username] = json_decode(file_get_contents($cachename), true); 153 if (!$fetchgroups || is_array($this->userCache[$username]['grps'])) { 154 return $this->userCache[$username]; 155 } 156 } 157 158 // fetch fresh data 159 $info = $this->getUser($username, $fetchgroups); 160 161 // store in cache 162 if ($info !== null) { 163 $this->userCache[$username] = $info; 164 file_put_contents($cachename, json_encode($info)); 165 } 166 167 return $info; 168 } 169 170 /** 171 * Fetch a single user 172 * 173 * @param string $username 174 * @param bool $fetchgroups Shall groups be fetched, too? 175 * @return null|array 176 */ 177 abstract public function getUser($username, $fetchgroups = true); 178 179 /** 180 * Return a list of all available groups, use cache if available 181 * 182 * @return string[] 183 */ 184 public function getCachedGroups() 185 { 186 if (empty($this->groupCache)) { 187 $this->groupCache = $this->getGroups(); 188 } 189 190 return $this->groupCache; 191 } 192 193 /** 194 * Return a list of all available groups 195 * 196 * Optionally filter the list 197 * 198 * @param null|string $match Filter for this, null for all groups 199 * @param string $filtermethod How to match the groups 200 * @return string[] 201 */ 202 abstract public function getGroups($match = null, $filtermethod = 'equal'); 203 204 /** 205 * Construst the fully qualified name to identify a user 206 * 207 * @param string $username 208 * @return string 209 */ 210 abstract public function qualifiedUser($username); 211 212 /** 213 * Simplify the username if possible 214 * 215 * @param string $username 216 * @return string 217 */ 218 abstract public function simpleUser($username); 219 220 /** 221 * Helper method to get the first value of the given attribute 222 * 223 * The given attribute may be null, an empty string is returned then 224 * 225 * @param Attribute|null $attribute 226 * @return string 227 */ 228 protected function attr2str($attribute) 229 { 230 if ($attribute !== null) { 231 return $attribute->firstValue(); 232 } 233 return ''; 234 } 235 236 /** 237 * Handle fatal exceptions 238 * 239 * @param \Exception $e 240 */ 241 protected function fatal(\Exception $e) 242 { 243 if (defined('DOKU_UNITTEST')) { 244 throw new \RuntimeException('', 0, $e); 245 } 246 msg('[pureldap] ' . hsc($e->getMessage()) . ' at ' . $e->getFile() . ':' . $e->getLine(), -1); 247 } 248 249 /** 250 * Handle debug output 251 * 252 * @param string $msg 253 * @param string $file 254 * @param int $line 255 */ 256 protected function debug($msg, $file, $line) 257 { 258 msg('[pureldap] ' . hsc($msg) . ' at ' . $file . ':' . $line, 0); 259 } 260} 261