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