xref: /plugin/pureldap/classes/Client.php (revision 6d90d5c87387cafcb884bda8c1b3c7ab80656146)
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