1fca58076SAndreas Gohr<?php 2fca58076SAndreas Gohr 35f8f561aSAndreas Gohruse dokuwiki\Extension\ActionPlugin; 4b6119621SAndreas Gohruse dokuwiki\Form\Form; 58b7620a8SAndreas Gohruse dokuwiki\plugin\twofactor\Manager; 68b7620a8SAndreas Gohruse dokuwiki\plugin\twofactor\MenuItem; 7a635cb20SAndreas Gohr 8fca58076SAndreas Gohr/** 9fca58076SAndreas Gohr * DokuWiki Plugin twofactor (Action Component) 10fca58076SAndreas Gohr * 1197647c7eSAndreas Gohr * This handles the 2fa profile screen where users can set their 2fa preferences and configure the 1297647c7eSAndreas Gohr * providers they want to use. 1397647c7eSAndreas Gohr * 14fca58076SAndreas Gohr * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 15fca58076SAndreas Gohr */ 165f8f561aSAndreas Gohrclass action_plugin_twofactor_profile extends ActionPlugin 17fca58076SAndreas Gohr{ 18fca58076SAndreas Gohr /** @inheritDoc */ 19fca58076SAndreas Gohr public function register(Doku_Event_Handler $controller) 20fca58076SAndreas Gohr { 21fca58076SAndreas Gohr // Adds our twofactor profile to the user menu. 22fca58076SAndreas Gohr $controller->register_hook('MENU_ITEMS_ASSEMBLY', 'AFTER', $this, 'handleUserMenuAssembly'); 23fca58076SAndreas Gohr 24fca58076SAndreas Gohr $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'handlePreProcess'); 25fca58076SAndreas Gohr $controller->register_hook('TPL_ACT_UNKNOWN', 'BEFORE', $this, 'handleUnknownAction'); 26fca58076SAndreas Gohr } 27fca58076SAndreas Gohr 28fca58076SAndreas Gohr /** 29fca58076SAndreas Gohr * Add 2fa Menu Item 30fca58076SAndreas Gohr * 31fca58076SAndreas Gohr * @param Doku_Event $event 32fca58076SAndreas Gohr */ 33fca58076SAndreas Gohr public function handleUserMenuAssembly(Doku_Event $event) 34fca58076SAndreas Gohr { 355f8f561aSAndreas Gohr if (!(Manager::getInstance())->isReady()) return; 365f8f561aSAndreas Gohr 37fca58076SAndreas Gohr global $INPUT; 38fca58076SAndreas Gohr // If this is not the user menu, then get out. 39fca58076SAndreas Gohr if ($event->data['view'] != 'user') return; 40fca58076SAndreas Gohr if (!$INPUT->server->has('REMOTE_USER')) return; 41fca58076SAndreas Gohr 42fca58076SAndreas Gohr // Create the new menu item 438b7620a8SAndreas Gohr $menuitem = new MenuItem($this->getLang('btn_twofactor_profile')); 44fca58076SAndreas Gohr 45fca58076SAndreas Gohr // Find index of existing Profile menu item. 46fca58076SAndreas Gohr for ($index = 0; $index > count($event->data['items']); $index++) { 47fca58076SAndreas Gohr if ($event->data['items'][$index]->getType() === 'profile') { 48fca58076SAndreas Gohr break; 49fca58076SAndreas Gohr } 50fca58076SAndreas Gohr } 51fca58076SAndreas Gohr array_splice($event->data['items'], $index + 1, 0, [$menuitem]); 52fca58076SAndreas Gohr } 53fca58076SAndreas Gohr 54fca58076SAndreas Gohr /** 55fca58076SAndreas Gohr * Check permissions to call the 2fa profile 56fca58076SAndreas Gohr * 57fca58076SAndreas Gohr * @param Doku_Event $event 58fca58076SAndreas Gohr */ 59fca58076SAndreas Gohr public function handlePreProcess(Doku_Event $event) 60fca58076SAndreas Gohr { 61fca58076SAndreas Gohr if ($event->data != 'twofactor_profile') return; 625f8f561aSAndreas Gohr if (!(Manager::getInstance())->isReady()) return; 63fca58076SAndreas Gohr 64fca58076SAndreas Gohr // We will be handling this action's permissions here. 65fca58076SAndreas Gohr $event->preventDefault(); 66fca58076SAndreas Gohr $event->stopPropagation(); 67fca58076SAndreas Gohr 68fca58076SAndreas Gohr // If not logged into the main auth plugin then send there. 69fca58076SAndreas Gohr global $INPUT; 70fca58076SAndreas Gohr global $ID; 71fca58076SAndreas Gohr 72fca58076SAndreas Gohr if (!$INPUT->server->has('REMOTE_USER')) { 73fca58076SAndreas Gohr $event->result = false; 74fca58076SAndreas Gohr send_redirect(wl($ID, array('do' => 'login'), true, '&')); 75fca58076SAndreas Gohr return; 76fca58076SAndreas Gohr } 77fca58076SAndreas Gohr 784b9cff8aSAndreas Gohr if (strtolower($INPUT->server->str('REQUEST_METHOD')) == 'post') { 79a635cb20SAndreas Gohr $this->handleProfile(); 8097647c7eSAndreas Gohr // we might have changed something important, make sure the whole workflow restarts 8197647c7eSAndreas Gohr send_redirect(wl($ID, ['do' => 'twofactor_profile'], true, '&')); 82fca58076SAndreas Gohr } 83fca58076SAndreas Gohr 844b9cff8aSAndreas Gohr } 854b9cff8aSAndreas Gohr 86fca58076SAndreas Gohr /** 8797647c7eSAndreas Gohr * Output the forms 8897647c7eSAndreas Gohr * 89fca58076SAndreas Gohr * @param Doku_Event $event 90fca58076SAndreas Gohr */ 91fca58076SAndreas Gohr public function handleUnknownAction(Doku_Event $event) 92fca58076SAndreas Gohr { 93fca58076SAndreas Gohr if ($event->data != 'twofactor_profile') return; 945f8f561aSAndreas Gohr if (!(Manager::getInstance())->isReady()) return; 95fca58076SAndreas Gohr $event->preventDefault(); 96fca58076SAndreas Gohr $event->stopPropagation(); 9797647c7eSAndreas Gohr 9897647c7eSAndreas Gohr echo '<div class="plugin_twofactor_profile">'; 9997647c7eSAndreas Gohr echo $this->locale_xhtml('profile'); 10097647c7eSAndreas Gohr if ($this->printOptOutForm()) return; 10197647c7eSAndreas Gohr $this->printDefaultProviderForm(); 10297647c7eSAndreas Gohr $this->printProviderForms(); 10397647c7eSAndreas Gohr echo '</div>'; 10497647c7eSAndreas Gohr 105fca58076SAndreas Gohr } 106fca58076SAndreas Gohr 107fca58076SAndreas Gohr /** 108a635cb20SAndreas Gohr * Handle POSTs for provider forms 109a635cb20SAndreas Gohr */ 110a635cb20SAndreas Gohr protected function handleProfile() 111a635cb20SAndreas Gohr { 112a635cb20SAndreas Gohr global $INPUT; 113a635cb20SAndreas Gohr if (!checkSecurityToken()) return; 1145f8f561aSAndreas Gohr $manager = Manager::getInstance(); 115a635cb20SAndreas Gohr 1164b9cff8aSAndreas Gohr if ($INPUT->has('2fa_optout') && $this->getConf('optinout') === 'optout') { 1175f8f561aSAndreas Gohr $manager->userOptOutState($INPUT->bool('optout')); 1184b9cff8aSAndreas Gohr return; 1194b9cff8aSAndreas Gohr } 1204b9cff8aSAndreas Gohr 1214b9cff8aSAndreas Gohr if (!$INPUT->has('provider')) return; 1225f8f561aSAndreas Gohr $providers = $manager->getAllProviders(); 1234b9cff8aSAndreas Gohr if (!isset($providers[$INPUT->str('provider')])) return; 1244b9cff8aSAndreas Gohr $provider = $providers[$INPUT->str('provider')]; 1254b9cff8aSAndreas Gohr 126a635cb20SAndreas Gohr if (!$provider->isConfigured()) { 127a635cb20SAndreas Gohr $provider->handleProfileForm(); 128a635cb20SAndreas Gohr } elseif ($INPUT->has('2fa_delete')) { 129a635cb20SAndreas Gohr $provider->reset(); 1305f8f561aSAndreas Gohr $manager->getUserDefaultProvider(); // resets the default to the next available 131b6119621SAndreas Gohr } elseif ($INPUT->has('2fa_default')) { 1325f8f561aSAndreas Gohr $manager->setUserDefaultProvider($provider); 133a635cb20SAndreas Gohr } 134a635cb20SAndreas Gohr } 135a635cb20SAndreas Gohr 136a635cb20SAndreas Gohr /** 13797647c7eSAndreas Gohr * Print the opt-out form (if available) 1384b9cff8aSAndreas Gohr * 13997647c7eSAndreas Gohr * @return bool true if the user currently opted out 140fca58076SAndreas Gohr */ 14197647c7eSAndreas Gohr protected function printOptOutForm() 142fca58076SAndreas Gohr { 1435f8f561aSAndreas Gohr $manager = Manager::getInstance(); 14497647c7eSAndreas Gohr $optedout = false; 14597647c7eSAndreas Gohr $setting = $this->getConf('optinout'); 146fca58076SAndreas Gohr 14797647c7eSAndreas Gohr echo '<section class="state">'; 14897647c7eSAndreas Gohr echo $this->locale_xhtml($setting); 149fca58076SAndreas Gohr 15097647c7eSAndreas Gohr // optout form 15197647c7eSAndreas Gohr if ($setting == 'optout') { 1524b9cff8aSAndreas Gohr $form = new Form(['method' => 'post']); 15397647c7eSAndreas Gohr $cb = $form->addCheckbox('optout', $this->getLang('optout')); 1545f8f561aSAndreas Gohr if ($manager->userOptOutState()) { 1554b9cff8aSAndreas Gohr $cb->attr('checked', 'checked'); 1564b9cff8aSAndreas Gohr } 15797647c7eSAndreas Gohr $form->addButton('2fa_optout', $this->getLang('btn_confirm')); 1584b9cff8aSAndreas Gohr echo $form->toHTML(); 1594b9cff8aSAndreas Gohr 1604b9cff8aSAndreas Gohr // when user opted out, don't show the rest of the form 1615f8f561aSAndreas Gohr if ($manager->userOptOutState()) { 16297647c7eSAndreas Gohr $optedout = true; 1634b9cff8aSAndreas Gohr } 1644b9cff8aSAndreas Gohr } 1654b9cff8aSAndreas Gohr 16697647c7eSAndreas Gohr echo '</section>'; 16797647c7eSAndreas Gohr return $optedout; 16897647c7eSAndreas Gohr } 16997647c7eSAndreas Gohr 17097647c7eSAndreas Gohr /** 17197647c7eSAndreas Gohr * Print the form where a user can select their default provider 17297647c7eSAndreas Gohr * 17397647c7eSAndreas Gohr * @return void 17497647c7eSAndreas Gohr */ 17597647c7eSAndreas Gohr protected function printDefaultProviderForm() 17697647c7eSAndreas Gohr { 17797647c7eSAndreas Gohr global $lang; 1785f8f561aSAndreas Gohr $manager = Manager::getInstance(); 17997647c7eSAndreas Gohr 1805f8f561aSAndreas Gohr $userproviders = $manager->getUserProviders(); 1815f8f561aSAndreas Gohr $default = $manager->getUserDefaultProvider(); 182b6119621SAndreas Gohr if (count($userproviders)) { 183b6119621SAndreas Gohr $form = new Form(['method' => 'POST']); 18497647c7eSAndreas Gohr $form->addFieldsetOpen($this->getLang('defaultprovider')); 185b6119621SAndreas Gohr foreach ($userproviders as $provider) { 186*f6b61423SAndreas Gohr $el = $form->addRadioButton('provider', $provider->getLabel())->val($provider->getProviderID()); 187*f6b61423SAndreas Gohr if ($provider->getProviderID() === $default->getProviderID()) { 188*f6b61423SAndreas Gohr $el->attr('checked', 'checked'); 189*f6b61423SAndreas Gohr } 190b6119621SAndreas Gohr } 191b6119621SAndreas Gohr $form->addButton('2fa_default', $lang['btn_save'])->attr('submit'); 192b6119621SAndreas Gohr $form->addFieldsetClose(); 193b6119621SAndreas Gohr echo $form->toHTML(); 194b6119621SAndreas Gohr } 19597647c7eSAndreas Gohr } 196b6119621SAndreas Gohr 19797647c7eSAndreas Gohr /** 19897647c7eSAndreas Gohr * Prints a form for each available provider to configure 19997647c7eSAndreas Gohr * 20097647c7eSAndreas Gohr * @return void 20197647c7eSAndreas Gohr */ 20297647c7eSAndreas Gohr protected function printProviderForms() 20397647c7eSAndreas Gohr { 20497647c7eSAndreas Gohr global $lang; 2055f8f561aSAndreas Gohr $manager = Manager::getInstance(); 20697647c7eSAndreas Gohr 20797647c7eSAndreas Gohr echo '<section class="providers">'; 20897647c7eSAndreas Gohr echo '<h2>' . $this->getLang('providers') . '</h2>'; 20997647c7eSAndreas Gohr 21097647c7eSAndreas Gohr echo '<div>'; 2115f8f561aSAndreas Gohr $providers = $manager->getAllProviders(); 212a635cb20SAndreas Gohr foreach ($providers as $provider) { 213a635cb20SAndreas Gohr $form = new dokuwiki\Form\Form(['method' => 'POST']); 214a635cb20SAndreas Gohr $form->setHiddenField('do', 'twofactor_profile'); 215a635cb20SAndreas Gohr $form->setHiddenField('provider', $provider->getProviderID()); 216a635cb20SAndreas Gohr $form->addFieldsetOpen($provider->getLabel()); 217a635cb20SAndreas Gohr $provider->renderProfileForm($form); 218a635cb20SAndreas Gohr if (!$provider->isConfigured()) { 21997647c7eSAndreas Gohr $form->addButton('2fa_submit', $lang['btn_save'])->attr('type', 'submit'); 220a635cb20SAndreas Gohr } else { 22197647c7eSAndreas Gohr $form->addButton('2fa_delete', $lang['btn_delete']) 22297647c7eSAndreas Gohr ->addClass('twofactor_delconfirm') 22397647c7eSAndreas Gohr ->attr('type', 'submit'); 224a635cb20SAndreas Gohr } 225a635cb20SAndreas Gohr $form->addFieldsetClose(); 226fca58076SAndreas Gohr echo $form->toHTML(); 227a635cb20SAndreas Gohr } 22897647c7eSAndreas Gohr echo '</div>'; 22997647c7eSAndreas Gohr 23097647c7eSAndreas Gohr echo '</section>'; 231fca58076SAndreas Gohr } 232fca58076SAndreas Gohr} 233fca58076SAndreas Gohr 23497647c7eSAndreas Gohr 235