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