1<?php 2 3use dokuwiki\Form\Form; 4use dokuwiki\plugin\twofactor\Manager; 5use dokuwiki\plugin\twofactor\MenuItem; 6use dokuwiki\plugin\twofactor\Provider; 7 8/** 9 * DokuWiki Plugin twofactor (Action Component) 10 * 11 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 12 */ 13class action_plugin_twofactor_profile extends \dokuwiki\Extension\ActionPlugin 14{ 15 /** @var Manager */ 16 protected $manager; 17 18 /** 19 * Constructor 20 */ 21 public function __construct() 22 { 23 $this->manager = Manager::getInstance(); 24 } 25 26 /** @inheritDoc */ 27 public function register(Doku_Event_Handler $controller) 28 { 29 if (!$this->manager->isReady()) return; 30 31 // Adds our twofactor profile to the user menu. 32 $controller->register_hook('MENU_ITEMS_ASSEMBLY', 'AFTER', $this, 'handleUserMenuAssembly'); 33 34 $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'handlePreProcess'); 35 $controller->register_hook('TPL_ACT_UNKNOWN', 'BEFORE', $this, 'handleUnknownAction'); 36 } 37 38 /** 39 * Add 2fa Menu Item 40 * 41 * @param Doku_Event $event 42 */ 43 public function handleUserMenuAssembly(Doku_Event $event) 44 { 45 global $INPUT; 46 // If this is not the user menu, then get out. 47 if ($event->data['view'] != 'user') return; 48 if (!$INPUT->server->has('REMOTE_USER')) return; 49 50 // Create the new menu item 51 $menuitem = new MenuItem($this->getLang('btn_twofactor_profile')); 52 53 // Find index of existing Profile menu item. 54 for ($index = 0; $index > count($event->data['items']); $index++) { 55 if ($event->data['items'][$index]->getType() === 'profile') { 56 break; 57 } 58 } 59 array_splice($event->data['items'], $index + 1, 0, [$menuitem]); 60 } 61 62 /** 63 * Check permissions to call the 2fa profile 64 * 65 * @param Doku_Event $event 66 */ 67 public function handlePreProcess(Doku_Event $event) 68 { 69 if ($event->data != 'twofactor_profile') return; 70 71 // We will be handling this action's permissions here. 72 $event->preventDefault(); 73 $event->stopPropagation(); 74 75 // If not logged into the main auth plugin then send there. 76 global $INPUT; 77 global $ID; 78 79 if (!$INPUT->server->has('REMOTE_USER')) { 80 $event->result = false; 81 send_redirect(wl($ID, array('do' => 'login'), true, '&')); 82 return; 83 } 84 85 $this->handleProfile(); 86 87 /** FIXME 88 * 89 * // If not logged into twofactor then send there. 90 * if (!$this->get_clearance()) { 91 * $event->result = false; 92 * send_redirect(wl($ID, array('do' => 'twofactor_login'), true, '&')); 93 * return; 94 * } 95 * // Otherwise handle the action. 96 * $event->result = $this->_process_changes($event, $param); 97 */ 98 99 } 100 101 /** 102 * @param Doku_Event $event 103 */ 104 public function handleUnknownAction(Doku_Event $event) 105 { 106 if ($event->data != 'twofactor_profile') return; 107 108 $event->preventDefault(); 109 $event->stopPropagation(); 110 $this->printProfile(); 111 } 112 113 /** 114 * Handle POSTs for provider forms 115 */ 116 protected function handleProfile() 117 { 118 global $INPUT; 119 if (!$INPUT->has('provider')) return; 120 $user = $INPUT->server->str('REMOTE_USER'); 121 122 $class = 'helper_plugin_' . $INPUT->str('provider'); 123 /** @var Provider $provider */ 124 $provider = new $class($user); 125 126 if (!checkSecurityToken()) return; 127 128 if (!$provider->isConfigured()) { 129 $provider->handleProfileForm(); 130 } elseif ($INPUT->has('2fa_delete')) { 131 $provider->reset(); 132 $this->manager->getUserDefaultProvider(); // resets the default to the next available 133 } elseif ($INPUT->has('2fa_default')) { 134 $this->manager->setUserDefaultProvider($provider); 135 } 136 } 137 138 /** 139 * Handles the profile form rendering. Displays user manageable settings. 140 */ 141 protected function printProfile() 142 { 143 global $lang; 144 145 echo $this->locale_xhtml('profile'); 146 147 // default provider selection 148 $userproviders = $this->manager->getUserProviders(); 149 $default = $this->manager->getUserDefaultProvider(); 150 if (count($userproviders)) { 151 $form = new Form(['method' => 'POST']); 152 $form->addFieldsetOpen('Default Provider'); 153 foreach ($userproviders as $provider) { 154 $form->addRadioButton('provider', $provider->getLabel()) 155 ->val($provider->getProviderID()) 156 ->attr('checked', $provider->getProviderID() === $default->getProviderID()); 157 } 158 $form->addButton('2fa_default', $lang['btn_save'])->attr('submit'); 159 $form->addFieldsetClose(); 160 echo $form->toHTML(); 161 } 162 163 // iterate over all providers 164 $providers = $this->manager->getAllProviders(); 165 foreach ($providers as $provider) { 166 $form = new dokuwiki\Form\Form(['method' => 'POST']); 167 $form->setHiddenField('do', 'twofactor_profile'); 168 $form->setHiddenField('provider', $provider->getProviderID()); 169 $form->addFieldsetOpen($provider->getLabel()); 170 $provider->renderProfileForm($form); 171 if (!$provider->isConfigured()) { 172 $form->addButton('2fa_submit', $lang['btn_save'])->attr('submit'); 173 } else { 174 $form->addButton('2fa_delete', $lang['btn_delete'])->attr('submit'); 175 } 176 $form->addFieldsetClose(); 177 echo $form->toHTML(); 178 } 179 180 /* FIXME 181 182 183 $optinout = $this->getConf("optinout"); 184 $optinvalue = $optinout == 'mandatory' ? 'in' : ($this->attribute ? $this->attribute->get("twofactor", 185 "state") : ''); 186 $available = count($this->tokenMods) + count($this->otpMods) > 0; 187 // If the user is being redirected here because of mandatory two factor, then display a message saying so. 188 if (!$available && $optinout == 'mandatory') { 189 msg($this->getLang('mandatory'), -1); 190 } elseif ($this->attribute->get("twofactor", "state") == '' && $optinout == 'optout') { 191 msg($this->getLang('optout_notice'), 2); 192 } elseif ($this->attribute->get("twofactor", 193 "state") == 'in' && count($this->tokenMods) == 0 && count($this->otpMods) == 0) { 194 msg($this->getLang('not_configured_notice'), 2); 195 } 196 global $USERINFO, $lang, $conf; 197 $form = new Doku_Form(array('id' => 'twofactor_setup')); 198 // Add the checkbox to opt in and out, only if optinout is not mandatory. 199 $items = array(); 200 if ($optinout != 'mandatory') { 201 if (!$this->attribute || !$optinvalue) { // If there is no personal setting for optin, the default is based on the wiki default. 202 $optinvalue = $this->getConf("optinout") == 'optout'; 203 } 204 $items[] = form_makeCheckboxField('optinout', '1', $this->getLang('twofactor_optin'), '', 'block', 205 $optinvalue == 'in' ? array('checked' => 'checked') : array()); 206 } 207 // Add the notification checkbox if appropriate. 208 if ($this->getConf('loginnotice') == 'user' && $optinvalue == 'in' && count($this->otpMods) > 0) { 209 $loginnotice = $this->attribute ? $this->attribute->get("twofactor", "loginnotice") : false; 210 $items[] = form_makeCheckboxField('loginnotice', '1', $this->getLang('twofactor_notify'), '', 'block', 211 $loginnotice === true ? array('checked' => 'checked') : array()); 212 } 213 // Select a notification provider. 214 if ($optinvalue == 'in') { 215 // If there is more than one choice, have the user select the default. 216 if (count($this->otpMods) > 1) { 217 $defaultMod = $this->attribute->exists("twofactor", "defaultmod") ? $this->attribute->get("twofactor", 218 "defaultmod") : null; 219 $modList = array_merge(array($this->getLang('useallotp')), array_keys($this->otpMods)); 220 $items[] = form_makeListboxField('default_module', $modList, $defaultMod, 221 $this->getLang('defaultmodule'), '', 'block'); 222 } 223 } 224 if (count($items) > 0) { 225 $form->startFieldset($this->getLang('settings')); 226 foreach ($items as $item) { 227 $form->addElement($item); 228 } 229 $form->endFieldset(); 230 } 231 // TODO: Make this AJAX so that the user does not have to keep clicking 232 // submit them Update Profile! 233 // Loop through all modules and render the profile components. 234 if ($optinvalue == 'in') { 235 $parts = array(); 236 foreach ($this->modules as $mod) { 237 if ($mod->getConf("enable") == 1) { 238 $this->log('twofactor_profile_form: processing ' . get_class($mod) . '::renderProfileForm()', 239 self::LOGGING_DEBUG); 240 $items = $mod->renderProfileForm(); 241 if (count($items) > 0) { 242 $form->startFieldset($mod->getLang('name')); 243 foreach ($items as $item) { 244 $form->addElement($item); 245 } 246 $form->endFieldset(); 247 } 248 } 249 } 250 } 251 if ($conf['profileconfirm']) { 252 $form->addElement('<br />'); 253 $form->startFieldset($this->getLang('verify_password')); 254 $form->addElement(form_makePasswordField('oldpass', $lang['oldpass'], '', 'block', 255 array('size' => '50', 'required' => 'required'))); 256 $form->endFieldset(); 257 } 258 $form->addElement('<br />'); 259 $form->addElement(form_makeButton('submit', '', $lang['btn_save'])); 260 $form->addElement('<a href="' . wl($ID, array('do' => 'show'), true, 261 '&') . '">' . $this->getLang('btn_return') . '</a>'); 262 $form->addHidden('do', 'twofactor_profile'); 263 $form->addHidden('save', '1'); 264 echo '<div class="centeralign">' . NL . $form->getForm() . '</div>' . NL; 265 266 */ 267 } 268} 269 270