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