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 88 $user = $this->qualifiedUser($this->config['admin_username']); 89 $ok = $this->authenticate($user, $this->config['admin_password']); 90 if(!$ok) { 91 $this->debug('Administrative bind failed. Probably wrong user/password.', __FILE__, __LINE__); 92 } 93 return $ok; 94 } 95 96 /** 97 * Authenticates a given user. This client will remain authenticated 98 * 99 * @param string $user 100 * @param string $pass 101 * @return bool was the authentication successful? 102 * @noinspection PhpRedundantCatchClauseInspection 103 */ 104 public function authenticate($user, $pass) 105 { 106 $user = $this->qualifiedUser($user); 107 108 if ($this->config['encryption'] === 'tls') { 109 try { 110 $this->ldap->startTls(); 111 } catch (OperationException $e) { 112 $this->fatal($e); 113 } 114 } 115 116 try { 117 $this->ldap->bind($user, $pass); 118 } catch (BindException $e) { 119 return false; 120 } catch (ConnectionException $e) { 121 $this->fatal($e); 122 return false; 123 } catch (OperationException $e) { 124 $this->fatal($e); 125 return false; 126 } 127 128 $this->isAuthenticated = true; 129 return true; 130 } 131 132 /** 133 * Get info for a single user, use cache if available 134 * 135 * @param string $username 136 * @param bool $fetchgroups Are groups needed? 137 * @return array|null 138 */ 139 public function getCachedUser($username, $fetchgroups = true) 140 { 141 global $conf; 142 143 // memory cache first 144 if (isset($this->userCache[$username])) { 145 if (!$fetchgroups || is_array($this->userCache[$username]['grps'])) { 146 return $this->userCache[$username]; 147 } 148 } 149 150 // disk cache second 151 $cachename = getCacheName($username, '.pureldap-user'); 152 $cachetime = @filemtime($cachename); 153 if ($cachetime && (time() - $cachetime) < $conf['auth_security_timeout']) { 154 $this->userCache[$username] = json_decode(file_get_contents($cachename), true); 155 if (!$fetchgroups || is_array($this->userCache[$username]['grps'])) { 156 return $this->userCache[$username]; 157 } 158 } 159 160 // fetch fresh data 161 $info = $this->getUser($username, $fetchgroups); 162 163 // store in cache 164 if ($info !== null) { 165 $this->userCache[$username] = $info; 166 file_put_contents($cachename, json_encode($info)); 167 } 168 169 return $info; 170 } 171 172 /** 173 * Fetch a single user 174 * 175 * @param string $username 176 * @param bool $fetchgroups Shall groups be fetched, too? 177 * @return null|array 178 */ 179 abstract public function getUser($username, $fetchgroups = true); 180 181 /** 182 * Return a list of all available groups, use cache if available 183 * 184 * @return string[] 185 */ 186 public function getCachedGroups() 187 { 188 if (empty($this->groupCache)) { 189 $this->groupCache = $this->getGroups(); 190 } 191 192 return $this->groupCache; 193 } 194 195 /** 196 * Return a list of all available groups 197 * 198 * Optionally filter the list 199 * 200 * @param null|string $match Filter for this, null for all groups 201 * @param string $filtermethod How to match the groups 202 * @return string[] 203 */ 204 abstract public function getGroups($match = null, $filtermethod = 'equal'); 205 206 /** 207 * Construst the fully qualified name to identify a user 208 * 209 * @param string $username 210 * @return string 211 */ 212 abstract public function qualifiedUser($username); 213 214 /** 215 * Simplify the username if possible 216 * 217 * @param string $username 218 * @return string 219 */ 220 abstract public function simpleUser($username); 221 222 /** 223 * Helper method to get the first value of the given attribute 224 * 225 * The given attribute may be null, an empty string is returned then 226 * 227 * @param Attribute|null $attribute 228 * @return string 229 */ 230 protected function attr2str($attribute) 231 { 232 if ($attribute !== null) { 233 return $attribute->firstValue(); 234 } 235 return ''; 236 } 237 238 /** 239 * Handle fatal exceptions 240 * 241 * @param \Exception $e 242 */ 243 protected function fatal(\Exception $e) 244 { 245 if (defined('DOKU_UNITTEST')) { 246 throw new \RuntimeException('', 0, $e); 247 } 248 msg('[pureldap] ' . hsc($e->getMessage()) . ' at ' . $e->getFile() . ':' . $e->getLine(), -1); 249 } 250 251 /** 252 * Handle debug output 253 * 254 * @param string $msg 255 * @param string $file 256 * @param int $line 257 */ 258 protected function debug($msg, $file, $line) 259 { 260 msg('[pureldap] ' . hsc($msg) . ' at ' . $file . ':' . $line, 0); 261 } 262} 263