1fca58076SAndreas Gohr<?php 2fca58076SAndreas Gohr 3b6119621SAndreas Gohruse dokuwiki\Form\Form; 48b7620a8SAndreas Gohruse dokuwiki\plugin\twofactor\Manager; 58b7620a8SAndreas Gohruse dokuwiki\plugin\twofactor\MenuItem; 6a635cb20SAndreas Gohr 7fca58076SAndreas Gohr/** 8fca58076SAndreas Gohr * DokuWiki Plugin twofactor (Action Component) 9fca58076SAndreas Gohr * 10*97647c7eSAndreas Gohr * This handles the 2fa profile screen where users can set their 2fa preferences and configure the 11*97647c7eSAndreas Gohr * providers they want to use. 12*97647c7eSAndreas Gohr * 13fca58076SAndreas Gohr * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 14fca58076SAndreas Gohr */ 15fca58076SAndreas Gohrclass action_plugin_twofactor_profile extends \dokuwiki\Extension\ActionPlugin 16fca58076SAndreas Gohr{ 17b6119621SAndreas Gohr /** @var Manager */ 18b6119621SAndreas Gohr protected $manager; 19b6119621SAndreas Gohr 20b6119621SAndreas Gohr /** 21b6119621SAndreas Gohr * Constructor 22b6119621SAndreas Gohr */ 23b6119621SAndreas Gohr public function __construct() 24b6119621SAndreas Gohr { 25b6119621SAndreas Gohr $this->manager = Manager::getInstance(); 26b6119621SAndreas Gohr } 27fca58076SAndreas Gohr 28fca58076SAndreas Gohr /** @inheritDoc */ 29fca58076SAndreas Gohr public function register(Doku_Event_Handler $controller) 30fca58076SAndreas Gohr { 31b6119621SAndreas Gohr if (!$this->manager->isReady()) return; 328b7620a8SAndreas Gohr 33fca58076SAndreas Gohr // Adds our twofactor profile to the user menu. 34fca58076SAndreas Gohr $controller->register_hook('MENU_ITEMS_ASSEMBLY', 'AFTER', $this, 'handleUserMenuAssembly'); 35fca58076SAndreas Gohr 36fca58076SAndreas Gohr $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'handlePreProcess'); 37fca58076SAndreas Gohr $controller->register_hook('TPL_ACT_UNKNOWN', 'BEFORE', $this, 'handleUnknownAction'); 38fca58076SAndreas Gohr } 39fca58076SAndreas Gohr 40fca58076SAndreas Gohr /** 41fca58076SAndreas Gohr * Add 2fa Menu Item 42fca58076SAndreas Gohr * 43fca58076SAndreas Gohr * @param Doku_Event $event 44fca58076SAndreas Gohr */ 45fca58076SAndreas Gohr public function handleUserMenuAssembly(Doku_Event $event) 46fca58076SAndreas Gohr { 47fca58076SAndreas Gohr global $INPUT; 48fca58076SAndreas Gohr // If this is not the user menu, then get out. 49fca58076SAndreas Gohr if ($event->data['view'] != 'user') return; 50fca58076SAndreas Gohr if (!$INPUT->server->has('REMOTE_USER')) return; 51fca58076SAndreas Gohr 52fca58076SAndreas Gohr // Create the new menu item 538b7620a8SAndreas Gohr $menuitem = new MenuItem($this->getLang('btn_twofactor_profile')); 54fca58076SAndreas Gohr 55fca58076SAndreas Gohr // Find index of existing Profile menu item. 56fca58076SAndreas Gohr for ($index = 0; $index > count($event->data['items']); $index++) { 57fca58076SAndreas Gohr if ($event->data['items'][$index]->getType() === 'profile') { 58fca58076SAndreas Gohr break; 59fca58076SAndreas Gohr } 60fca58076SAndreas Gohr } 61fca58076SAndreas Gohr array_splice($event->data['items'], $index + 1, 0, [$menuitem]); 62fca58076SAndreas Gohr } 63fca58076SAndreas Gohr 64fca58076SAndreas Gohr /** 65fca58076SAndreas Gohr * Check permissions to call the 2fa profile 66fca58076SAndreas Gohr * 67fca58076SAndreas Gohr * @param Doku_Event $event 68fca58076SAndreas Gohr */ 69fca58076SAndreas Gohr public function handlePreProcess(Doku_Event $event) 70fca58076SAndreas Gohr { 71fca58076SAndreas Gohr if ($event->data != 'twofactor_profile') return; 72fca58076SAndreas Gohr 73fca58076SAndreas Gohr // We will be handling this action's permissions here. 74fca58076SAndreas Gohr $event->preventDefault(); 75fca58076SAndreas Gohr $event->stopPropagation(); 76fca58076SAndreas Gohr 77fca58076SAndreas Gohr // If not logged into the main auth plugin then send there. 78fca58076SAndreas Gohr global $INPUT; 79fca58076SAndreas Gohr global $ID; 80fca58076SAndreas Gohr 81fca58076SAndreas Gohr if (!$INPUT->server->has('REMOTE_USER')) { 82fca58076SAndreas Gohr $event->result = false; 83fca58076SAndreas Gohr send_redirect(wl($ID, array('do' => 'login'), true, '&')); 84fca58076SAndreas Gohr return; 85fca58076SAndreas Gohr } 86fca58076SAndreas Gohr 874b9cff8aSAndreas Gohr if (strtolower($INPUT->server->str('REQUEST_METHOD')) == 'post') { 88a635cb20SAndreas Gohr $this->handleProfile(); 89*97647c7eSAndreas Gohr // we might have changed something important, make sure the whole workflow restarts 90*97647c7eSAndreas Gohr send_redirect(wl($ID, ['do' => 'twofactor_profile'], true, '&')); 91fca58076SAndreas Gohr } 92fca58076SAndreas Gohr 934b9cff8aSAndreas Gohr } 944b9cff8aSAndreas Gohr 95fca58076SAndreas Gohr /** 96*97647c7eSAndreas Gohr * Output the forms 97*97647c7eSAndreas Gohr * 98fca58076SAndreas Gohr * @param Doku_Event $event 99fca58076SAndreas Gohr */ 100fca58076SAndreas Gohr public function handleUnknownAction(Doku_Event $event) 101fca58076SAndreas Gohr { 102fca58076SAndreas Gohr if ($event->data != 'twofactor_profile') return; 103fca58076SAndreas Gohr $event->preventDefault(); 104fca58076SAndreas Gohr $event->stopPropagation(); 105*97647c7eSAndreas Gohr 106*97647c7eSAndreas Gohr echo '<div class="plugin_twofactor_profile">'; 107*97647c7eSAndreas Gohr echo $this->locale_xhtml('profile'); 108*97647c7eSAndreas Gohr if ($this->printOptOutForm()) return; 109*97647c7eSAndreas Gohr $this->printDefaultProviderForm(); 110*97647c7eSAndreas Gohr $this->printProviderForms(); 111*97647c7eSAndreas Gohr echo '</div>'; 112*97647c7eSAndreas Gohr 113fca58076SAndreas Gohr } 114fca58076SAndreas Gohr 115fca58076SAndreas Gohr /** 116a635cb20SAndreas Gohr * Handle POSTs for provider forms 117a635cb20SAndreas Gohr */ 118a635cb20SAndreas Gohr protected function handleProfile() 119a635cb20SAndreas Gohr { 120a635cb20SAndreas Gohr global $INPUT; 121a635cb20SAndreas Gohr if (!checkSecurityToken()) return; 122a635cb20SAndreas Gohr 1234b9cff8aSAndreas Gohr if ($INPUT->has('2fa_optout') && $this->getConf('optinout') === 'optout') { 1244b9cff8aSAndreas Gohr $this->manager->userOptOutState($INPUT->bool('optout')); 1254b9cff8aSAndreas Gohr return; 1264b9cff8aSAndreas Gohr } 1274b9cff8aSAndreas Gohr 1284b9cff8aSAndreas Gohr if (!$INPUT->has('provider')) return; 1294b9cff8aSAndreas Gohr $providers = $this->manager->getAllProviders(); 1304b9cff8aSAndreas Gohr if (!isset($providers[$INPUT->str('provider')])) return; 1314b9cff8aSAndreas Gohr $provider = $providers[$INPUT->str('provider')]; 1324b9cff8aSAndreas Gohr 133a635cb20SAndreas Gohr if (!$provider->isConfigured()) { 134a635cb20SAndreas Gohr $provider->handleProfileForm(); 135a635cb20SAndreas Gohr } elseif ($INPUT->has('2fa_delete')) { 136a635cb20SAndreas Gohr $provider->reset(); 137b6119621SAndreas Gohr $this->manager->getUserDefaultProvider(); // resets the default to the next available 138b6119621SAndreas Gohr } elseif ($INPUT->has('2fa_default')) { 139b6119621SAndreas Gohr $this->manager->setUserDefaultProvider($provider); 140a635cb20SAndreas Gohr } 141a635cb20SAndreas Gohr } 142a635cb20SAndreas Gohr 143a635cb20SAndreas Gohr /** 144*97647c7eSAndreas Gohr * Print the opt-out form (if available) 1454b9cff8aSAndreas Gohr * 146*97647c7eSAndreas Gohr * @return bool true if the user currently opted out 147fca58076SAndreas Gohr */ 148*97647c7eSAndreas Gohr protected function printOptOutForm() 149fca58076SAndreas Gohr { 150*97647c7eSAndreas Gohr $optedout = false; 151*97647c7eSAndreas Gohr $setting = $this->getConf('optinout'); 152fca58076SAndreas Gohr 153*97647c7eSAndreas Gohr echo '<section class="state">'; 154*97647c7eSAndreas Gohr echo $this->locale_xhtml($setting); 155fca58076SAndreas Gohr 156*97647c7eSAndreas Gohr // optout form 157*97647c7eSAndreas Gohr if ($setting == 'optout') { 1584b9cff8aSAndreas Gohr $form = new Form(['method' => 'post']); 159*97647c7eSAndreas Gohr $cb = $form->addCheckbox('optout', $this->getLang('optout')); 1604b9cff8aSAndreas Gohr if ($this->manager->userOptOutState()) { 1614b9cff8aSAndreas Gohr $cb->attr('checked', 'checked'); 1624b9cff8aSAndreas Gohr } 163*97647c7eSAndreas Gohr $form->addButton('2fa_optout', $this->getLang('btn_confirm')); 1644b9cff8aSAndreas Gohr echo $form->toHTML(); 1654b9cff8aSAndreas Gohr 1664b9cff8aSAndreas Gohr // when user opted out, don't show the rest of the form 1674b9cff8aSAndreas Gohr if ($this->manager->userOptOutState()) { 168*97647c7eSAndreas Gohr $optedout = true; 1694b9cff8aSAndreas Gohr } 1704b9cff8aSAndreas Gohr } 1714b9cff8aSAndreas Gohr 172*97647c7eSAndreas Gohr echo '</section>'; 173*97647c7eSAndreas Gohr return $optedout; 174*97647c7eSAndreas Gohr } 175*97647c7eSAndreas Gohr 176*97647c7eSAndreas Gohr /** 177*97647c7eSAndreas Gohr * Print the form where a user can select their default provider 178*97647c7eSAndreas Gohr * 179*97647c7eSAndreas Gohr * @return void 180*97647c7eSAndreas Gohr */ 181*97647c7eSAndreas Gohr protected function printDefaultProviderForm() 182*97647c7eSAndreas Gohr { 183*97647c7eSAndreas Gohr global $lang; 184*97647c7eSAndreas Gohr 185b6119621SAndreas Gohr $userproviders = $this->manager->getUserProviders(); 186b6119621SAndreas Gohr $default = $this->manager->getUserDefaultProvider(); 187b6119621SAndreas Gohr if (count($userproviders)) { 188b6119621SAndreas Gohr $form = new Form(['method' => 'POST']); 189*97647c7eSAndreas Gohr $form->addFieldsetOpen($this->getLang('defaultprovider')); 190b6119621SAndreas Gohr foreach ($userproviders as $provider) { 191b6119621SAndreas Gohr $form->addRadioButton('provider', $provider->getLabel()) 192b6119621SAndreas Gohr ->val($provider->getProviderID()) 193b6119621SAndreas Gohr ->attr('checked', $provider->getProviderID() === $default->getProviderID()); 194b6119621SAndreas Gohr } 195b6119621SAndreas Gohr $form->addButton('2fa_default', $lang['btn_save'])->attr('submit'); 196b6119621SAndreas Gohr $form->addFieldsetClose(); 197b6119621SAndreas Gohr echo $form->toHTML(); 198b6119621SAndreas Gohr } 199*97647c7eSAndreas Gohr } 200b6119621SAndreas Gohr 201*97647c7eSAndreas Gohr /** 202*97647c7eSAndreas Gohr * Prints a form for each available provider to configure 203*97647c7eSAndreas Gohr * 204*97647c7eSAndreas Gohr * @return void 205*97647c7eSAndreas Gohr */ 206*97647c7eSAndreas Gohr protected function printProviderForms() 207*97647c7eSAndreas Gohr { 208*97647c7eSAndreas Gohr global $lang; 209*97647c7eSAndreas Gohr 210*97647c7eSAndreas Gohr echo '<section class="providers">'; 211*97647c7eSAndreas Gohr echo '<h2>' . $this->getLang('providers') . '</h2>'; 212*97647c7eSAndreas Gohr 213*97647c7eSAndreas Gohr echo '<div>'; 214b6119621SAndreas Gohr $providers = $this->manager->getAllProviders(); 215a635cb20SAndreas Gohr foreach ($providers as $provider) { 216a635cb20SAndreas Gohr $form = new dokuwiki\Form\Form(['method' => 'POST']); 217a635cb20SAndreas Gohr $form->setHiddenField('do', 'twofactor_profile'); 218a635cb20SAndreas Gohr $form->setHiddenField('provider', $provider->getProviderID()); 219a635cb20SAndreas Gohr $form->addFieldsetOpen($provider->getLabel()); 220a635cb20SAndreas Gohr $provider->renderProfileForm($form); 221a635cb20SAndreas Gohr if (!$provider->isConfigured()) { 222*97647c7eSAndreas Gohr $form->addButton('2fa_submit', $lang['btn_save'])->attr('type', 'submit'); 223a635cb20SAndreas Gohr } else { 224*97647c7eSAndreas Gohr $form->addButton('2fa_delete', $lang['btn_delete']) 225*97647c7eSAndreas Gohr ->addClass('twofactor_delconfirm') 226*97647c7eSAndreas Gohr ->attr('type', 'submit'); 227a635cb20SAndreas Gohr } 228a635cb20SAndreas Gohr $form->addFieldsetClose(); 229fca58076SAndreas Gohr echo $form->toHTML(); 230a635cb20SAndreas Gohr } 231*97647c7eSAndreas Gohr echo '</div>'; 232*97647c7eSAndreas Gohr 233*97647c7eSAndreas Gohr echo '</section>'; 234fca58076SAndreas Gohr } 235fca58076SAndreas Gohr} 236fca58076SAndreas Gohr 237*97647c7eSAndreas Gohr 238