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 if ($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 /** 88 * Output the forms 89 * 90 * @param Doku_Event $event 91 */ 92 public function handleUnknownAction(Doku_Event $event) 93 { 94 if ($event->data != 'twofactor_profile') return; 95 if (!(Manager::getInstance())->isReady()) return; 96 $event->preventDefault(); 97 $event->stopPropagation(); 98 global $INPUT; 99 100 echo '<div class="plugin_twofactor_profile">'; 101 echo $this->locale_xhtml('profile'); 102 103 if ($INPUT->has('twofactor_setup')) { 104 $this->printProviderSetup(); 105 } else { 106 if (!$this->printOptOutForm()) { 107 $this->printConfiguredProviders(); 108 109 $this->printProviderSetupSelect(); 110 } 111 } 112 echo '</div>'; 113 } 114 115 /** 116 * Handle POSTs for provider forms 117 * 118 * @return bool should a redirect be made? 119 */ 120 protected function handleProfile() 121 { 122 global $INPUT; 123 if (!checkSecurityToken()) return true; 124 $manager = Manager::getInstance(); 125 126 if ($INPUT->has('twofactor_optout') && $this->getConf('optinout') === 'optout') { 127 $manager->userOptOutState($INPUT->bool('optout')); 128 return true; 129 } 130 131 if (!$INPUT->has('provider')) return true; 132 $providers = $manager->getAllProviders(); 133 if (!isset($providers[$INPUT->str('provider')])) return true; 134 $provider = $providers[$INPUT->str('provider')]; 135 136 if ($INPUT->has('twofactor_delete')) { 137 $provider->reset(); 138 $manager->getUserDefaultProvider(); // resets the default to the next available 139 return true; 140 } 141 142 if ($INPUT->has('twofactor_default')) { 143 $manager->setUserDefaultProvider($provider); 144 return true; 145 } 146 147 if (!$provider->isConfigured()) { 148 $provider->handleProfileForm(); 149 return $provider->isConfigured(); // redirect only if configuration finished 150 } 151 152 return true; 153 } 154 155 /** 156 * Print the opt-out form (if available) 157 * 158 * @return bool true if the user currently opted out 159 */ 160 protected function printOptOutForm() 161 { 162 $manager = Manager::getInstance(); 163 $optedout = false; 164 $setting = $this->getConf('optinout'); 165 166 echo '<section class="state">'; 167 echo $this->locale_xhtml($setting); 168 169 // optout form 170 if ($setting == 'optout') { 171 $form = new Form(['method' => 'post']); 172 $cb = $form->addCheckbox('optout', $this->getLang('optout')); 173 if ($manager->userOptOutState()) { 174 $cb->attr('checked', 'checked'); 175 } 176 $form->addButton('twofactor_optout', $this->getLang('btn_confirm')); 177 echo $form->toHTML(); 178 179 // when user opted out, don't show the rest of the form 180 if ($manager->userOptOutState()) { 181 $optedout = true; 182 } 183 } 184 185 echo '</section>'; 186 return $optedout; 187 } 188 189 /** 190 * Print the form where a user can select their default provider 191 * 192 * @return void 193 */ 194 protected function printConfiguredProviders() 195 { 196 $manager = Manager::getInstance(); 197 198 $userproviders = $manager->getUserProviders(); 199 $default = $manager->getUserDefaultProvider(); 200 if (!$userproviders) return; 201 202 $form = new Form(['method' => 'POST']); 203 $form->addFieldsetOpen($this->getLang('providers')); 204 foreach ($userproviders as $provider) { 205 $el = $form->addRadioButton('provider', $provider->getLabel())->val($provider->getProviderID()); 206 if ($provider->getProviderID() === $default->getProviderID()) { 207 $el->attr('checked', 'checked'); 208 $el->getLabel()->val($provider->getLabel() . ' ' . $this->getLang('default')); 209 } 210 } 211 212 $form->addTagOpen('div')->addClass('buttons'); 213 $form->addButton('twofactor_default', $this->getLang('btn_default'))->attr('submit'); 214 $form->addButton('twofactor_delete', $this->getLang('btn_remove')) 215 ->addClass('twofactor_delconfirm')->attr('submit'); 216 $form->addTagClose('div'); 217 218 $form->addFieldsetClose(); 219 echo $form->toHTML(); 220 } 221 222 /** 223 * List providers available for adding 224 * 225 * @return void 226 */ 227 protected function printProviderSetupSelect() 228 { 229 $manager = Manager::getInstance(); 230 $available = $manager->getUserProviders(false); 231 if (!$available) return; 232 233 $options = []; 234 foreach ($available as $provider) { 235 $options[$provider->getProviderID()] = $provider->getLabel(); 236 } 237 238 $form = new Form(['method' => 'post']); 239 $form->setHiddenField('do', 'twofactor_profile'); 240 $form->setHiddenField('init', '1'); 241 $form->addFieldsetOpen($this->getLang('newprovider')); 242 $form->addDropdown('provider', $options, $this->getLang('provider')); 243 244 $form->addTagOpen('div')->addClass('buttons'); 245 $form->addButton('twofactor_setup', $this->getLang('btn_setup'))->attr('type', 'submit'); 246 $form->addTagClose('div'); 247 248 $form->addFieldsetClose(); 249 echo $form->toHTML(); 250 } 251 252 /** 253 * Display the setup form for a provider 254 * 255 * @return void 256 */ 257 protected function printProviderSetup() 258 { 259 global $lang; 260 global $INPUT; 261 262 $providerID = $INPUT->str('provider'); 263 $providers = (Manager::getInstance())->getUserProviders(false); 264 if (!isset($providers[$providerID])) return; 265 $provider = $providers[$providerID]; 266 267 $form = new Form(['method' => 'POST', 'class' => 'provider-' . $providerID]); 268 $form->setHiddenField('do', 'twofactor_profile'); 269 $form->setHiddenField('twofactor_setup', '1'); 270 $form->setHiddenField('provider', $provider->getProviderID()); 271 272 $form->addFieldsetOpen($provider->getLabel()); 273 $provider->renderProfileForm($form); 274 275 $form->addTagOpen('div')->addClass('buttons'); 276 $form->addButton('twofactor_submit', $this->getLang('btn_confirm'))->attr('type', 'submit'); 277 $form->addButton('twofactor_delete', $lang['btn_cancel'])->attr('type', 'submit'); 278 $form->addTagClose('div'); 279 280 $form->addFieldsetClose(); 281 echo $form->toHTML(); 282 } 283 284} 285 286 287