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