xref: /plugin/twofactor/action/profile.php (revision b6119621988e00fd0cbd3059406f4ed18460274b)
1<?php
2
3use dokuwiki\Form\Form;
4use dokuwiki\plugin\twofactor\Manager;
5use dokuwiki\plugin\twofactor\MenuItem;
6use dokuwiki\plugin\twofactor\Provider;
7
8/**
9 * DokuWiki Plugin twofactor (Action Component)
10 *
11 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
12 */
13class action_plugin_twofactor_profile extends \dokuwiki\Extension\ActionPlugin
14{
15    /** @var Manager */
16    protected $manager;
17
18    /**
19     * Constructor
20     */
21    public function __construct()
22    {
23        $this->manager = Manager::getInstance();
24    }
25
26    /** @inheritDoc */
27    public function register(Doku_Event_Handler $controller)
28    {
29        if (!$this->manager->isReady()) return;
30
31        // Adds our twofactor profile to the user menu.
32        $controller->register_hook('MENU_ITEMS_ASSEMBLY', 'AFTER', $this, 'handleUserMenuAssembly');
33
34        $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'handlePreProcess');
35        $controller->register_hook('TPL_ACT_UNKNOWN', 'BEFORE', $this, 'handleUnknownAction');
36    }
37
38    /**
39     * Add 2fa Menu Item
40     *
41     * @param Doku_Event $event
42     */
43    public function handleUserMenuAssembly(Doku_Event $event)
44    {
45        global $INPUT;
46        // If this is not the user menu, then get out.
47        if ($event->data['view'] != 'user') return;
48        if (!$INPUT->server->has('REMOTE_USER')) return;
49
50        // Create the new menu item
51        $menuitem = new MenuItem($this->getLang('btn_twofactor_profile'));
52
53        // Find index of existing Profile menu item.
54        for ($index = 0; $index > count($event->data['items']); $index++) {
55            if ($event->data['items'][$index]->getType() === 'profile') {
56                break;
57            }
58        }
59        array_splice($event->data['items'], $index + 1, 0, [$menuitem]);
60    }
61
62    /**
63     * Check permissions to call the 2fa profile
64     *
65     * @param Doku_Event $event
66     */
67    public function handlePreProcess(Doku_Event $event)
68    {
69        if ($event->data != 'twofactor_profile') return;
70
71        // We will be handling this action's permissions here.
72        $event->preventDefault();
73        $event->stopPropagation();
74
75        // If not logged into the main auth plugin then send there.
76        global $INPUT;
77        global $ID;
78
79        if (!$INPUT->server->has('REMOTE_USER')) {
80            $event->result = false;
81            send_redirect(wl($ID, array('do' => 'login'), true, '&'));
82            return;
83        }
84
85        $this->handleProfile();
86
87        /** FIXME
88         *
89         * // If not logged into twofactor then send there.
90         * if (!$this->get_clearance()) {
91         * $event->result = false;
92         * send_redirect(wl($ID, array('do' => 'twofactor_login'), true, '&'));
93         * return;
94         * }
95         * // Otherwise handle the action.
96         * $event->result = $this->_process_changes($event, $param);
97         */
98
99    }
100
101    /**
102     * @param Doku_Event $event
103     */
104    public function handleUnknownAction(Doku_Event $event)
105    {
106        if ($event->data != 'twofactor_profile') return;
107
108        $event->preventDefault();
109        $event->stopPropagation();
110        $this->printProfile();
111    }
112
113    /**
114     * Handle POSTs for provider forms
115     */
116    protected function handleProfile()
117    {
118        global $INPUT;
119        if (!$INPUT->has('provider')) return;
120        $user = $INPUT->server->str('REMOTE_USER');
121
122        $class = 'helper_plugin_' . $INPUT->str('provider');
123        /** @var Provider $provider */
124        $provider = new $class($user);
125
126        if (!checkSecurityToken()) return;
127
128        if (!$provider->isConfigured()) {
129            $provider->handleProfileForm();
130        } elseif ($INPUT->has('2fa_delete')) {
131            $provider->reset();
132            $this->manager->getUserDefaultProvider(); // resets the default to the next available
133        } elseif ($INPUT->has('2fa_default')) {
134            $this->manager->setUserDefaultProvider($provider);
135        }
136    }
137
138    /**
139     * Handles the profile form rendering.  Displays user manageable settings.
140     */
141    protected function printProfile()
142    {
143        global $lang;
144
145        echo $this->locale_xhtml('profile');
146
147        // default provider selection
148        $userproviders = $this->manager->getUserProviders();
149        $default = $this->manager->getUserDefaultProvider();
150        if (count($userproviders)) {
151            $form = new Form(['method' => 'POST']);
152            $form->addFieldsetOpen('Default Provider');
153            foreach ($userproviders as $provider) {
154                $form->addRadioButton('provider', $provider->getLabel())
155                     ->val($provider->getProviderID())
156                     ->attr('checked', $provider->getProviderID() === $default->getProviderID());
157            }
158            $form->addButton('2fa_default', $lang['btn_save'])->attr('submit');
159            $form->addFieldsetClose();
160            echo $form->toHTML();
161        }
162
163        // iterate over all providers
164        $providers = $this->manager->getAllProviders();
165        foreach ($providers as $provider) {
166            $form = new dokuwiki\Form\Form(['method' => 'POST']);
167            $form->setHiddenField('do', 'twofactor_profile');
168            $form->setHiddenField('provider', $provider->getProviderID());
169            $form->addFieldsetOpen($provider->getLabel());
170            $provider->renderProfileForm($form);
171            if (!$provider->isConfigured()) {
172                $form->addButton('2fa_submit', $lang['btn_save'])->attr('submit');
173            } else {
174                $form->addButton('2fa_delete', $lang['btn_delete'])->attr('submit');
175            }
176            $form->addFieldsetClose();
177            echo $form->toHTML();
178        }
179
180        /* FIXME
181
182
183        $optinout = $this->getConf("optinout");
184        $optinvalue = $optinout == 'mandatory' ? 'in' : ($this->attribute ? $this->attribute->get("twofactor",
185            "state") : '');
186        $available = count($this->tokenMods) + count($this->otpMods) > 0;
187        // If the user is being redirected here because of mandatory two factor, then display a message saying so.
188        if (!$available && $optinout == 'mandatory') {
189            msg($this->getLang('mandatory'), -1);
190        } elseif ($this->attribute->get("twofactor", "state") == '' && $optinout == 'optout') {
191            msg($this->getLang('optout_notice'), 2);
192        } elseif ($this->attribute->get("twofactor",
193                "state") == 'in' && count($this->tokenMods) == 0 && count($this->otpMods) == 0) {
194            msg($this->getLang('not_configured_notice'), 2);
195        }
196        global $USERINFO, $lang, $conf;
197        $form = new Doku_Form(array('id' => 'twofactor_setup'));
198        // Add the checkbox to opt in and out, only if optinout is not mandatory.
199        $items = array();
200        if ($optinout != 'mandatory') {
201            if (!$this->attribute || !$optinvalue) {  // If there is no personal setting for optin, the default is based on the wiki default.
202                $optinvalue = $this->getConf("optinout") == 'optout';
203            }
204            $items[] = form_makeCheckboxField('optinout', '1', $this->getLang('twofactor_optin'), '', 'block',
205                $optinvalue == 'in' ? array('checked' => 'checked') : array());
206        }
207        // Add the notification checkbox if appropriate.
208        if ($this->getConf('loginnotice') == 'user' && $optinvalue == 'in' && count($this->otpMods) > 0) {
209            $loginnotice = $this->attribute ? $this->attribute->get("twofactor", "loginnotice") : false;
210            $items[] = form_makeCheckboxField('loginnotice', '1', $this->getLang('twofactor_notify'), '', 'block',
211                $loginnotice === true ? array('checked' => 'checked') : array());
212        }
213        // Select a notification provider.
214        if ($optinvalue == 'in') {
215            // If there is more than one choice, have the user select the default.
216            if (count($this->otpMods) > 1) {
217                $defaultMod = $this->attribute->exists("twofactor", "defaultmod") ? $this->attribute->get("twofactor",
218                    "defaultmod") : null;
219                $modList = array_merge(array($this->getLang('useallotp')), array_keys($this->otpMods));
220                $items[] = form_makeListboxField('default_module', $modList, $defaultMod,
221                    $this->getLang('defaultmodule'), '', 'block');
222            }
223        }
224        if (count($items) > 0) {
225            $form->startFieldset($this->getLang('settings'));
226            foreach ($items as $item) {
227                $form->addElement($item);
228            }
229            $form->endFieldset();
230        }
231        // TODO: Make this AJAX so that the user does not have to keep clicking
232        // submit them Update Profile!
233        // Loop through all modules and render the profile components.
234        if ($optinvalue == 'in') {
235            $parts = array();
236            foreach ($this->modules as $mod) {
237                if ($mod->getConf("enable") == 1) {
238                    $this->log('twofactor_profile_form: processing ' . get_class($mod) . '::renderProfileForm()',
239                        self::LOGGING_DEBUG);
240                    $items = $mod->renderProfileForm();
241                    if (count($items) > 0) {
242                        $form->startFieldset($mod->getLang('name'));
243                        foreach ($items as $item) {
244                            $form->addElement($item);
245                        }
246                        $form->endFieldset();
247                    }
248                }
249            }
250        }
251        if ($conf['profileconfirm']) {
252            $form->addElement('<br />');
253            $form->startFieldset($this->getLang('verify_password'));
254            $form->addElement(form_makePasswordField('oldpass', $lang['oldpass'], '', 'block',
255                array('size' => '50', 'required' => 'required')));
256            $form->endFieldset();
257        }
258        $form->addElement('<br />');
259        $form->addElement(form_makeButton('submit', '', $lang['btn_save']));
260        $form->addElement('<a href="' . wl($ID, array('do' => 'show'), true,
261                '&') . '">' . $this->getLang('btn_return') . '</a>');
262        $form->addHidden('do', 'twofactor_profile');
263        $form->addHidden('save', '1');
264        echo '<div class="centeralign">' . NL . $form->getForm() . '</div>' . NL;
265
266         */
267    }
268}
269
270