xref: /plugin/twofactor/action/profile.php (revision f6b614231a195f86217e7f374ad6d04e6561a1a5)
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