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