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