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