xref: /plugin/twofactor/admin.php (revision f62d0e33ba552d56db13c4063ea3f5f1e75c81a9)
1<?php
2
3use dokuwiki\Form\Form;
4use dokuwiki\plugin\twofactor\Manager;
5
6/**
7 *  Twofactor Manager
8 *
9 *  Dokuwiki Admin Plugin
10 *  Special thanks to the useradmin extension as a starting point for this class
11 *
12 * @author  Mike Wilmes <mwilmes@avc.edu>
13 */
14class admin_plugin_twofactor extends DokuWiki_Admin_Plugin
15{
16    protected $userList = array();     // list of users with attributes
17    protected $filter = array();   // user selection filter(s)
18    protected $start = 0;          // index of first user to be displayed
19    protected $last = 0;           // index of the last user to be displayed
20    protected $pagesize = 20;      // number of users to list on one page
21    protected $disabled = '';      // if disabled set to explanatory string
22    protected $lastdisabled = false; // set to true if last user is unknown and last button is hence buggy
23
24    /** @var helper_plugin_attribute */
25    protected $attribute;
26
27    /**
28     * Constructor
29     */
30    public function __construct()
31    {
32        if (!(Manager::getInstance())->isReady()) return;
33        $this->attribute = plugin_load('helper', 'attribute');
34        $this->userList = $this->attribute->enumerateUsers('twofactor');
35    }
36
37    /** @inheritdoc */
38    public function handle()
39    {
40        global $INPUT, $INFO;
41        if (!$INFO['isadmin']) return false;
42        if ($this->disabled) {
43            // If disabled, don't process anything.
44            return true;
45        }
46
47        // extract the command and any specific parameters
48        // submit button name is of the form - fn[cmd][param(s)]
49        $fn = $INPUT->param('fn');
50
51        if (is_array($fn)) {
52            $cmd = key($fn);
53            $param = is_array($fn[$cmd]) ? key($fn[$cmd]) : null;
54        } else {
55            $cmd = $fn;
56            $param = null;
57        }
58
59        if ($cmd != "search") {
60            $this->start = $INPUT->int('start', 0);
61            $this->filter = $this->_retrieveFilter();
62        }
63
64        switch ($cmd) {
65            case "reset"  :
66                $this->_resetUser();
67                break;
68            case "search" :
69                $this->_setFilter($param);
70                $this->start = 0;
71                break;
72        }
73
74        $this->_user_total = count($this->userList) > 0 ? $this->_getUserCount($this->filter) : -1;
75
76        // page handling
77        switch ($cmd) {
78            case 'start' :
79                $this->start = 0;
80                break;
81            case 'prev'  :
82                $this->start -= $this->pagesize;
83                break;
84            case 'next'  :
85                $this->start += $this->pagesize;
86                break;
87            case 'last'  :
88                $this->start = $this->_user_total;
89                break;
90        }
91        $this->_validatePagination();
92        return true;
93    }
94
95    /**
96     * Output appropriate html
97     *
98     * @return bool
99     */
100    public function html()
101    {
102        global $ID, $INFO;
103
104        $users = $this->getUsers($this->start, $this->pagesize, $this->filter);
105        $pagination = $this->getPagination();
106
107        echo $this->locale_xhtml('admin');
108
109        echo '<div id="user__manager">'; // FIXME do we reuse styles?
110        echo '<div class="level2">';
111
112        // FIXME check if isReady, display info if not
113
114        $form = new Form(['method' => 'POST']);
115        $form->setHiddenField('do', 'admin');
116        $form->setHiddenField('page', 'twofactor');
117        $form->setHiddenField('start', $this->start);
118
119        $form->addTagOpen('div')->addClass('table');
120        $form->addTagOpen('table')->addClass('inline');
121        $form = $this->addTableHead($form);
122
123        $form->addTagOpen('tbody');
124        foreach ($users as $user => $userinfo) {
125            $form = $this->addTableUser($form, $user, $userinfo);
126        }
127        $form->addTagClose('tbody');
128
129        $form->addTagClose('table');
130        $form->addTagClose('div');
131
132        echo $form->toHTML();
133
134        return true;
135    }
136
137    /**
138     * Add the table headers to the table in the given form
139     * @param Form $form
140     * @return Form
141     */
142    protected function addTableHead(Form $form)
143    {
144        $form->addTagOpen('thead');
145
146        // header
147        $form->addTagOpen('tr');
148        $form->addTagOpen('th');
149        $form->addHTML($this->getLang('user_id'));
150        $form->addTagClose('th');
151        $form->addTagOpen('th');
152        $form->addHTML($this->getLang('user_name'));
153        $form->addTagClose('th');
154        $form->addTagOpen('th');
155        $form->addHTML($this->getLang('user_mail'));
156        $form->addTagClose('th');
157        $form->addTagOpen('th');
158        $form->addHTML($this->getLang('action'));
159        $form->addTagClose('th');
160        $form->addTagClose('tr');
161
162        // filter
163        $form->addTagOpen('tr');
164        $form->addTagOpen('th');
165        $form->addTextInput('userid');
166        $form->addTagClose('th');
167        $form->addTagOpen('th');
168        $form->addTextInput('username');
169        $form->addTagClose('th');
170        $form->addTagOpen('th');
171        $form->addTextInput('usermail');
172        $form->addTagClose('th');
173        $form->addTagOpen('th');
174        $form->addButton('', $this->getLang('search'))->attr('type', 'submit');
175        $form->addTagClose('th');
176        $form->addTagClose('tr');
177
178        $form->addTagClose('thead');
179        return $form;
180    }
181
182    /**
183     * Add
184     *
185     * @param Form $form
186     * @param $user
187     * @param $userinfo
188     * @return Form
189     */
190    protected function addTableUser(Form $form, $user, $userinfo)
191    {
192        $form->addTagOpen('tr');
193        $form->addTagOpen('td');
194        $form->addHTML(hsc($user));
195        $form->addTagClose('td');
196        $form->addTagOpen('td');
197        $form->addHTML(hsc($userinfo['name']));
198        $form->addTagClose('td');
199        $form->addTagOpen('td');
200        $form->addHTML(hsc($userinfo['mail']));
201        $form->addTagClose('td');
202        $form->addTagOpen('td');
203        $form->addButton('reset[' . $user . ']', $this->getLang('reset'))->attr('type', 'submit');
204        $form->addTagClose('td');
205        $form->addTagClose('tr');
206        return $form;
207    }
208
209    /**
210     * @return int current start value for pageination
211     */
212    public function getStart()
213    {
214        return $this->start;
215    }
216
217    /**
218     * @return int number of users per page
219     */
220    public function getPagesize()
221    {
222        return $this->pagesize;
223    }
224
225    /**
226     * @param boolean $lastdisabled
227     */
228    public function setLastdisabled($lastdisabled)
229    {
230        $this->lastdisabled = $lastdisabled;
231    }
232
233    /**
234     * Prints a inputfield
235     *
236     * @param string $id
237     * @param string $name
238     * @param string $label
239     * @param string $value
240     * @param bool $cando whether auth backend is capable to do this action
241     * @param int $indent
242     */
243    protected function _htmlInputField($id, $name, $label, $value, $cando, $indent = 0)
244    {
245        $class = $cando ? '' : ' class="disabled"';
246        echo str_pad('', $indent);
247
248        if ($name == 'userpass' || $name == 'userpass2') {
249            $fieldtype = 'password';
250            $autocomp = 'autocomplete="off"';
251        } elseif ($name == 'usermail') {
252            $fieldtype = 'email';
253            $autocomp = '';
254        } else {
255            $fieldtype = 'text';
256            $autocomp = '';
257        }
258        $value = hsc($value);
259
260        echo "<tr $class>";
261        echo "<td><label for=\"$id\" >$label: </label></td>";
262        echo "<td>";
263        if ($cando) {
264            echo "<input type=\"$fieldtype\" id=\"$id\" name=\"$name\" value=\"$value\" class=\"edit\" $autocomp />";
265        } else {
266            echo "<input type=\"hidden\" name=\"$name\" value=\"$value\" />";
267            echo "<input type=\"$fieldtype\" id=\"$id\" name=\"$name\" value=\"$value\" class=\"edit disabled\" disabled=\"disabled\" />";
268        }
269        echo "</td>";
270        echo "</tr>";
271    }
272
273    /**
274     * Returns htmlescaped filter value
275     *
276     * @param string $key name of search field
277     * @return string html escaped value
278     */
279    protected function _htmlFilter($key)
280    {
281        if (empty($this->filter)) return '';
282        return (isset($this->filter[$key]) ? hsc($this->filter[$key]) : '');
283    }
284
285    /**
286     * Print hidden inputs with the current filter values
287     *
288     * @param int $indent
289     */
290    protected function _htmlFilterSettings($indent = 0)
291    {
292
293        ptln("<input type=\"hidden\" name=\"start\" value=\"" . $this->start . "\" />", $indent);
294
295        foreach ($this->filter as $key => $filter) {
296            ptln("<input type=\"hidden\" name=\"filter[" . $key . "]\" value=\"" . hsc($filter) . "\" />", $indent);
297        }
298    }
299
300    /**
301     * Reset user (a user has been selected to remove two factor authentication)
302     *
303     * @param string $param id of the user
304     * @return bool whether succesful
305     */
306    protected function _resetUser()
307    {
308        global $INPUT;
309        if (!checkSecurityToken()) return false;
310
311        $selected = $INPUT->arr('delete');
312        if (empty($selected)) return false;
313        $selected = array_keys($selected);
314
315        if (in_array($_SERVER['REMOTE_USER'], $selected)) {
316            msg($this->lang['reset_not_self'], -1);
317            return false;
318        }
319
320        $count = 0;
321        foreach ($selected as $user) {
322            // All users here have a attribute namespace file. Purge them.
323            $purged = $this->attribute->purge('twofactor', $user);
324            foreach ($this->modules as $mod) {
325                $purged |= $this->attribute->purge($mod->moduleName, $user);
326            }
327            $count += $purged ? 1 : 0;
328        }
329
330        if ($count == count($selected)) {
331            $text = str_replace('%d', $count, $this->lang['reset_ok']);
332            msg("$text.", 1);
333        } else {
334            $part1 = str_replace('%d', $count, $this->lang['reset_ok']);
335            $part2 = str_replace('%d', (count($selected) - $count), $this->lang['reset_fail']);
336            // Output results.
337            msg("$part1, $part2", -1);
338        }
339
340        // Now refresh the user list.
341        $this->_getUsers();
342
343        return true;
344    }
345
346    protected function _retrieveFilteredUsers($filter = array())
347    {
348        global $auth;
349        $users = array();
350        $noUsers = is_null($auth) || !$auth->canDo('getUsers');
351        foreach ($this->userList as $user) {
352            if ($noUsers) {
353                $userdata = array('user' => $user, 'name' => $user, 'mail' => null);
354            } else {
355                $userdata = $auth->getUserData($user);
356                if (!is_array($userdata)) {
357                    $userdata = array('user' => $user, 'name' => null, 'mail' => null);
358                }
359            }
360            $include = true;
361            foreach ($filter as $key => $value) {
362                $include &= strstr($userdata[$key], $value);
363            }
364            if ($include) {
365                $users[$user] = $userdata;
366            }
367        }
368        return $users;
369    }
370
371    protected function _getUserCount($filter)
372    {
373        return count($this->_retrieveFilteredUsers($filter));
374    }
375
376    protected function getUsers($start, $pagesize, $filter)
377    {
378        $users = $this->_retrieveFilteredUsers($filter);
379        return $users;
380    }
381
382    /**
383     * Retrieve & clean user data from the form
384     *
385     * @param bool $clean whether the cleanUser method of the authentication backend is applied
386     * @return array (user, password, full name, email, array(groups))
387     */
388    protected function _retrieveUser($clean = true)
389    {
390        /** @var DokuWiki_Auth_Plugin $auth */
391        global $auth;
392        global $INPUT;
393
394        $user = array();
395        $user[] = $INPUT->str('userid');
396        $user[] = $INPUT->str('username');
397        $user[] = $INPUT->str('usermail');
398
399        return $user;
400    }
401
402    /**
403     * Set the filter with the current search terms or clear the filter
404     *
405     * @param string $op 'new' or 'clear'
406     */
407    protected function _setFilter($op)
408    {
409
410        $this->filter = array();
411
412        if ($op == 'new') {
413            list($user, $name, $mail) = $this->_retrieveUser();
414
415            if (!empty($user)) $this->filter['user'] = $user;
416            if (!empty($name)) $this->filter['name'] = $name;
417            if (!empty($mail)) $this->filter['mail'] = $mail;
418        }
419    }
420
421    /**
422     * Get the current search terms
423     *
424     * @return array
425     */
426    protected function _retrieveFilter()
427    {
428        global $INPUT;
429
430        $t_filter = $INPUT->arr('filter');
431
432        // messy, but this way we ensure we aren't getting any additional crap from malicious users
433        $filter = array();
434
435        if (isset($t_filter['user'])) $filter['user'] = $t_filter['user'];
436        if (isset($t_filter['name'])) $filter['name'] = $t_filter['name'];
437        if (isset($t_filter['mail'])) $filter['mail'] = $t_filter['mail'];
438
439        return $filter;
440    }
441
442    /**
443     * Validate and improve the pagination values
444     */
445    protected function _validatePagination()
446    {
447
448        if ($this->start >= $this->_user_total) {
449            $this->start = $this->_user_total - $this->pagesize;
450        }
451        if ($this->start < 0) $this->start = 0;
452
453        $this->last = min($this->_user_total, $this->start + $this->pagesize);
454    }
455
456    /**
457     * Return an array of strings to enable/disable pagination buttons
458     *
459     * @return array with enable/disable attributes
460     */
461    protected function getPagination()
462    {
463
464        $disabled = 'disabled="disabled"';
465
466        $buttons = array();
467        $buttons['start'] = $buttons['prev'] = ($this->start == 0) ? $disabled : '';
468
469        if ($this->_user_total == -1) {
470            $buttons['last'] = $disabled;
471            $buttons['next'] = '';
472        } else {
473            $buttons['last'] = $buttons['next'] = (($this->start + $this->pagesize) >= $this->_user_total) ? $disabled : '';
474        }
475
476        if ($this->lastdisabled) {
477            $buttons['last'] = $disabled;
478        }
479
480        return $buttons;
481    }
482
483}
484