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') { 79*1c8522cbSAndreas Gohr if ($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 } 85*1c8522cbSAndreas Gohr } 864b9cff8aSAndreas Gohr 87fca58076SAndreas Gohr /** 8897647c7eSAndreas Gohr * Output the forms 8997647c7eSAndreas Gohr * 90fca58076SAndreas Gohr * @param Doku_Event $event 91fca58076SAndreas Gohr */ 92fca58076SAndreas Gohr public function handleUnknownAction(Doku_Event $event) 93fca58076SAndreas Gohr { 94fca58076SAndreas Gohr if ($event->data != 'twofactor_profile') return; 955f8f561aSAndreas Gohr if (!(Manager::getInstance())->isReady()) return; 96fca58076SAndreas Gohr $event->preventDefault(); 97fca58076SAndreas Gohr $event->stopPropagation(); 98*1c8522cbSAndreas Gohr global $INPUT; 9997647c7eSAndreas Gohr 10097647c7eSAndreas Gohr echo '<div class="plugin_twofactor_profile">'; 10197647c7eSAndreas Gohr echo $this->locale_xhtml('profile'); 10297647c7eSAndreas Gohr 103*1c8522cbSAndreas Gohr if ($INPUT->has('twofactor_setup')) { 104*1c8522cbSAndreas Gohr $this->printProviderSetup(); 105*1c8522cbSAndreas Gohr } else { 106*1c8522cbSAndreas Gohr if (!$this->printOptOutForm()) { 107*1c8522cbSAndreas Gohr $this->printConfiguredProviders(); 108*1c8522cbSAndreas Gohr 109*1c8522cbSAndreas Gohr $this->printProviderSetupSelect(); 110*1c8522cbSAndreas Gohr } 111*1c8522cbSAndreas Gohr } 112*1c8522cbSAndreas Gohr echo '</div>'; 113fca58076SAndreas Gohr } 114fca58076SAndreas Gohr 115fca58076SAndreas Gohr /** 116a635cb20SAndreas Gohr * Handle POSTs for provider forms 117*1c8522cbSAndreas Gohr * 118*1c8522cbSAndreas Gohr * @return bool should a redirect be made? 119a635cb20SAndreas Gohr */ 120a635cb20SAndreas Gohr protected function handleProfile() 121a635cb20SAndreas Gohr { 122a635cb20SAndreas Gohr global $INPUT; 123*1c8522cbSAndreas Gohr if (!checkSecurityToken()) return true; 1245f8f561aSAndreas Gohr $manager = Manager::getInstance(); 125a635cb20SAndreas Gohr 126*1c8522cbSAndreas Gohr if ($INPUT->has('twofactor_optout') && $this->getConf('optinout') === 'optout') { 1275f8f561aSAndreas Gohr $manager->userOptOutState($INPUT->bool('optout')); 128*1c8522cbSAndreas Gohr return true; 1294b9cff8aSAndreas Gohr } 1304b9cff8aSAndreas Gohr 131*1c8522cbSAndreas Gohr if (!$INPUT->has('provider')) return true; 1325f8f561aSAndreas Gohr $providers = $manager->getAllProviders(); 133*1c8522cbSAndreas Gohr if (!isset($providers[$INPUT->str('provider')])) return true; 1344b9cff8aSAndreas Gohr $provider = $providers[$INPUT->str('provider')]; 1354b9cff8aSAndreas Gohr 136*1c8522cbSAndreas Gohr if ($INPUT->has('twofactor_delete')) { 137*1c8522cbSAndreas Gohr $provider->reset(); 138*1c8522cbSAndreas Gohr $manager->getUserDefaultProvider(); // resets the default to the next available 139*1c8522cbSAndreas Gohr return true; 140*1c8522cbSAndreas Gohr } 141*1c8522cbSAndreas Gohr 142*1c8522cbSAndreas Gohr if ($INPUT->has('twofactor_default')) { 143*1c8522cbSAndreas Gohr $manager->setUserDefaultProvider($provider); 144*1c8522cbSAndreas Gohr return true; 145*1c8522cbSAndreas Gohr } 146*1c8522cbSAndreas Gohr 147a635cb20SAndreas Gohr if (!$provider->isConfigured()) { 148a635cb20SAndreas Gohr $provider->handleProfileForm(); 149*1c8522cbSAndreas Gohr return $provider->isConfigured(); // redirect only if configuration finished 150a635cb20SAndreas Gohr } 151*1c8522cbSAndreas Gohr 152*1c8522cbSAndreas Gohr return true; 153a635cb20SAndreas Gohr } 154a635cb20SAndreas Gohr 155a635cb20SAndreas Gohr /** 15697647c7eSAndreas Gohr * Print the opt-out form (if available) 1574b9cff8aSAndreas Gohr * 15897647c7eSAndreas Gohr * @return bool true if the user currently opted out 159fca58076SAndreas Gohr */ 16097647c7eSAndreas Gohr protected function printOptOutForm() 161fca58076SAndreas Gohr { 1625f8f561aSAndreas Gohr $manager = Manager::getInstance(); 16397647c7eSAndreas Gohr $optedout = false; 16497647c7eSAndreas Gohr $setting = $this->getConf('optinout'); 165fca58076SAndreas Gohr 16697647c7eSAndreas Gohr echo '<section class="state">'; 16797647c7eSAndreas Gohr echo $this->locale_xhtml($setting); 168fca58076SAndreas Gohr 16997647c7eSAndreas Gohr // optout form 17097647c7eSAndreas Gohr if ($setting == 'optout') { 1714b9cff8aSAndreas Gohr $form = new Form(['method' => 'post']); 17297647c7eSAndreas Gohr $cb = $form->addCheckbox('optout', $this->getLang('optout')); 1735f8f561aSAndreas Gohr if ($manager->userOptOutState()) { 1744b9cff8aSAndreas Gohr $cb->attr('checked', 'checked'); 1754b9cff8aSAndreas Gohr } 176*1c8522cbSAndreas Gohr $form->addButton('twofactor_optout', $this->getLang('btn_confirm')); 1774b9cff8aSAndreas Gohr echo $form->toHTML(); 1784b9cff8aSAndreas Gohr 1794b9cff8aSAndreas Gohr // when user opted out, don't show the rest of the form 1805f8f561aSAndreas Gohr if ($manager->userOptOutState()) { 18197647c7eSAndreas Gohr $optedout = true; 1824b9cff8aSAndreas Gohr } 1834b9cff8aSAndreas Gohr } 1844b9cff8aSAndreas Gohr 18597647c7eSAndreas Gohr echo '</section>'; 18697647c7eSAndreas Gohr return $optedout; 18797647c7eSAndreas Gohr } 18897647c7eSAndreas Gohr 18997647c7eSAndreas Gohr /** 19097647c7eSAndreas Gohr * Print the form where a user can select their default provider 19197647c7eSAndreas Gohr * 19297647c7eSAndreas Gohr * @return void 19397647c7eSAndreas Gohr */ 194*1c8522cbSAndreas Gohr protected function printConfiguredProviders() 19597647c7eSAndreas Gohr { 1965f8f561aSAndreas Gohr $manager = Manager::getInstance(); 19797647c7eSAndreas Gohr 1985f8f561aSAndreas Gohr $userproviders = $manager->getUserProviders(); 1995f8f561aSAndreas Gohr $default = $manager->getUserDefaultProvider(); 200*1c8522cbSAndreas Gohr if (!$userproviders) return; 201*1c8522cbSAndreas Gohr 202b6119621SAndreas Gohr $form = new Form(['method' => 'POST']); 203*1c8522cbSAndreas Gohr $form->addFieldsetOpen($this->getLang('providers')); 204b6119621SAndreas Gohr foreach ($userproviders as $provider) { 205f6b61423SAndreas Gohr $el = $form->addRadioButton('provider', $provider->getLabel())->val($provider->getProviderID()); 206f6b61423SAndreas Gohr if ($provider->getProviderID() === $default->getProviderID()) { 207f6b61423SAndreas Gohr $el->attr('checked', 'checked'); 208*1c8522cbSAndreas Gohr $el->getLabel()->val($provider->getLabel() . ' ' . $this->getLang('default')); 209f6b61423SAndreas Gohr } 210b6119621SAndreas Gohr } 211*1c8522cbSAndreas Gohr 212*1c8522cbSAndreas Gohr $form->addTagOpen('div')->addClass('buttons'); 213*1c8522cbSAndreas Gohr $form->addButton('twofactor_default', $this->getLang('btn_default'))->attr('submit'); 214*1c8522cbSAndreas Gohr $form->addButton('twofactor_delete', $this->getLang('btn_remove')) 215*1c8522cbSAndreas Gohr ->addClass('twofactor_delconfirm')->attr('submit'); 216*1c8522cbSAndreas Gohr $form->addTagClose('div'); 217*1c8522cbSAndreas Gohr 218b6119621SAndreas Gohr $form->addFieldsetClose(); 219b6119621SAndreas Gohr echo $form->toHTML(); 220b6119621SAndreas Gohr } 221b6119621SAndreas Gohr 22297647c7eSAndreas Gohr /** 223*1c8522cbSAndreas Gohr * List providers available for adding 22497647c7eSAndreas Gohr * 22597647c7eSAndreas Gohr * @return void 22697647c7eSAndreas Gohr */ 227*1c8522cbSAndreas Gohr protected function printProviderSetupSelect() 22897647c7eSAndreas Gohr { 2295f8f561aSAndreas Gohr $manager = Manager::getInstance(); 230*1c8522cbSAndreas Gohr $available = $manager->getUserProviders(false); 231*1c8522cbSAndreas Gohr if (!$available) return; 23297647c7eSAndreas Gohr 233*1c8522cbSAndreas Gohr $options = []; 234*1c8522cbSAndreas Gohr foreach ($available as $provider) { 235*1c8522cbSAndreas Gohr $options[$provider->getProviderID()] = $provider->getLabel(); 236a635cb20SAndreas Gohr } 237*1c8522cbSAndreas Gohr 238*1c8522cbSAndreas Gohr $form = new Form(['method' => 'post']); 239*1c8522cbSAndreas Gohr $form->setHiddenField('do', 'twofactor_profile'); 240*1c8522cbSAndreas Gohr $form->setHiddenField('init', '1'); 241*1c8522cbSAndreas Gohr $form->addFieldsetOpen($this->getLang('newprovider')); 242*1c8522cbSAndreas Gohr $form->addDropdown('provider', $options, $this->getLang('provider')); 243*1c8522cbSAndreas Gohr 244*1c8522cbSAndreas Gohr $form->addTagOpen('div')->addClass('buttons'); 245*1c8522cbSAndreas Gohr $form->addButton('twofactor_setup', $this->getLang('btn_setup'))->attr('type', 'submit'); 246*1c8522cbSAndreas Gohr $form->addTagClose('div'); 247*1c8522cbSAndreas Gohr 248a635cb20SAndreas Gohr $form->addFieldsetClose(); 249fca58076SAndreas Gohr echo $form->toHTML(); 250a635cb20SAndreas Gohr } 25197647c7eSAndreas Gohr 252*1c8522cbSAndreas Gohr /** 253*1c8522cbSAndreas Gohr * Display the setup form for a provider 254*1c8522cbSAndreas Gohr * 255*1c8522cbSAndreas Gohr * @return void 256*1c8522cbSAndreas Gohr */ 257*1c8522cbSAndreas Gohr protected function printProviderSetup() 258*1c8522cbSAndreas Gohr { 259*1c8522cbSAndreas Gohr global $lang; 260*1c8522cbSAndreas Gohr global $INPUT; 261*1c8522cbSAndreas Gohr 262*1c8522cbSAndreas Gohr $providerID = $INPUT->str('provider'); 263*1c8522cbSAndreas Gohr $providers = (Manager::getInstance())->getUserProviders(false); 264*1c8522cbSAndreas Gohr if (!isset($providers[$providerID])) return; 265*1c8522cbSAndreas Gohr $provider = $providers[$providerID]; 266*1c8522cbSAndreas Gohr 267*1c8522cbSAndreas Gohr $form = new Form(['method' => 'POST', 'class' => 'provider-' . $providerID]); 268*1c8522cbSAndreas Gohr $form->setHiddenField('do', 'twofactor_profile'); 269*1c8522cbSAndreas Gohr $form->setHiddenField('twofactor_setup', '1'); 270*1c8522cbSAndreas Gohr $form->setHiddenField('provider', $provider->getProviderID()); 271*1c8522cbSAndreas Gohr 272*1c8522cbSAndreas Gohr $form->addFieldsetOpen($provider->getLabel()); 273*1c8522cbSAndreas Gohr $provider->renderProfileForm($form); 274*1c8522cbSAndreas Gohr 275*1c8522cbSAndreas Gohr $form->addTagOpen('div')->addClass('buttons'); 276*1c8522cbSAndreas Gohr $form->addButton('twofactor_submit', $this->getLang('btn_confirm'))->attr('type', 'submit'); 277*1c8522cbSAndreas Gohr $form->addButton('twofactor_delete', $lang['btn_cancel'])->attr('type', 'submit'); 278*1c8522cbSAndreas Gohr $form->addTagClose('div'); 279*1c8522cbSAndreas Gohr 280*1c8522cbSAndreas Gohr $form->addFieldsetClose(); 281*1c8522cbSAndreas Gohr echo $form->toHTML(); 282fca58076SAndreas Gohr } 283*1c8522cbSAndreas Gohr 284fca58076SAndreas Gohr} 285fca58076SAndreas Gohr 28697647c7eSAndreas Gohr 287