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