xref: /plugin/twofactor/action/profile.php (revision 1c8522cb9ed54182407ffb118486195e8b313a82)
1fca58076SAndreas Gohr<?php
2fca58076SAndreas Gohr
35f8f561aSAndreas Gohruse dokuwiki\Extension\ActionPlugin;
4b6119621SAndreas Gohruse dokuwiki\Form\Form;
58b7620a8SAndreas Gohruse dokuwiki\plugin\twofactor\Manager;
68b7620a8SAndreas Gohruse dokuwiki\plugin\twofactor\MenuItem;
7a635cb20SAndreas Gohr
8fca58076SAndreas Gohr/**
9fca58076SAndreas Gohr * DokuWiki Plugin twofactor (Action Component)
10fca58076SAndreas Gohr *
1197647c7eSAndreas Gohr * This handles the 2fa profile screen where users can set their 2fa preferences and configure the
1297647c7eSAndreas Gohr * providers they want to use.
1397647c7eSAndreas Gohr *
14fca58076SAndreas Gohr * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
15fca58076SAndreas Gohr */
165f8f561aSAndreas Gohrclass action_plugin_twofactor_profile extends ActionPlugin
17fca58076SAndreas Gohr{
18fca58076SAndreas Gohr    /** @inheritDoc */
19fca58076SAndreas Gohr    public function register(Doku_Event_Handler $controller)
20fca58076SAndreas Gohr    {
21fca58076SAndreas Gohr        // Adds our twofactor profile to the user menu.
22fca58076SAndreas Gohr        $controller->register_hook('MENU_ITEMS_ASSEMBLY', 'AFTER', $this, 'handleUserMenuAssembly');
23fca58076SAndreas Gohr
24fca58076SAndreas Gohr        $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'handlePreProcess');
25fca58076SAndreas Gohr        $controller->register_hook('TPL_ACT_UNKNOWN', 'BEFORE', $this, 'handleUnknownAction');
26fca58076SAndreas Gohr    }
27fca58076SAndreas Gohr
28fca58076SAndreas Gohr    /**
29fca58076SAndreas Gohr     * Add 2fa Menu Item
30fca58076SAndreas Gohr     *
31fca58076SAndreas Gohr     * @param Doku_Event $event
32fca58076SAndreas Gohr     */
33fca58076SAndreas Gohr    public function handleUserMenuAssembly(Doku_Event $event)
34fca58076SAndreas Gohr    {
355f8f561aSAndreas Gohr        if (!(Manager::getInstance())->isReady()) return;
365f8f561aSAndreas Gohr
37fca58076SAndreas Gohr        global $INPUT;
38fca58076SAndreas Gohr        // If this is not the user menu, then get out.
39fca58076SAndreas Gohr        if ($event->data['view'] != 'user') return;
40fca58076SAndreas Gohr        if (!$INPUT->server->has('REMOTE_USER')) return;
41fca58076SAndreas Gohr
42fca58076SAndreas Gohr        // Create the new menu item
438b7620a8SAndreas Gohr        $menuitem = new MenuItem($this->getLang('btn_twofactor_profile'));
44fca58076SAndreas Gohr
45fca58076SAndreas Gohr        // Find index of existing Profile menu item.
46fca58076SAndreas Gohr        for ($index = 0; $index > count($event->data['items']); $index++) {
47fca58076SAndreas Gohr            if ($event->data['items'][$index]->getType() === 'profile') {
48fca58076SAndreas Gohr                break;
49fca58076SAndreas Gohr            }
50fca58076SAndreas Gohr        }
51fca58076SAndreas Gohr        array_splice($event->data['items'], $index + 1, 0, [$menuitem]);
52fca58076SAndreas Gohr    }
53fca58076SAndreas Gohr
54fca58076SAndreas Gohr    /**
55fca58076SAndreas Gohr     * Check permissions to call the 2fa profile
56fca58076SAndreas Gohr     *
57fca58076SAndreas Gohr     * @param Doku_Event $event
58fca58076SAndreas Gohr     */
59fca58076SAndreas Gohr    public function handlePreProcess(Doku_Event $event)
60fca58076SAndreas Gohr    {
61fca58076SAndreas Gohr        if ($event->data != 'twofactor_profile') return;
625f8f561aSAndreas Gohr        if (!(Manager::getInstance())->isReady()) return;
63fca58076SAndreas Gohr
64fca58076SAndreas Gohr        // We will be handling this action's permissions here.
65fca58076SAndreas Gohr        $event->preventDefault();
66fca58076SAndreas Gohr        $event->stopPropagation();
67fca58076SAndreas Gohr
68fca58076SAndreas Gohr        // If not logged into the main auth plugin then send there.
69fca58076SAndreas Gohr        global $INPUT;
70fca58076SAndreas Gohr        global $ID;
71fca58076SAndreas Gohr
72fca58076SAndreas Gohr        if (!$INPUT->server->has('REMOTE_USER')) {
73fca58076SAndreas Gohr            $event->result = false;
74fca58076SAndreas Gohr            send_redirect(wl($ID, array('do' => 'login'), true, '&'));
75fca58076SAndreas Gohr            return;
76fca58076SAndreas Gohr        }
77fca58076SAndreas Gohr
784b9cff8aSAndreas Gohr        if (strtolower($INPUT->server->str('REQUEST_METHOD')) == 'post') {
79*1c8522cbSAndreas Gohr            if ($this->handleProfile()) {
8097647c7eSAndreas Gohr                // we might have changed something important, make sure the whole workflow restarts
8197647c7eSAndreas Gohr                send_redirect(wl($ID, ['do' => 'twofactor_profile'], true, '&'));
82fca58076SAndreas Gohr            }
83fca58076SAndreas Gohr
844b9cff8aSAndreas Gohr        }
85*1c8522cbSAndreas Gohr    }
864b9cff8aSAndreas Gohr
87fca58076SAndreas Gohr    /**
8897647c7eSAndreas Gohr     * Output the forms
8997647c7eSAndreas Gohr     *
90fca58076SAndreas Gohr     * @param Doku_Event $event
91fca58076SAndreas Gohr     */
92fca58076SAndreas Gohr    public function handleUnknownAction(Doku_Event $event)
93fca58076SAndreas Gohr    {
94fca58076SAndreas Gohr        if ($event->data != 'twofactor_profile') return;
955f8f561aSAndreas Gohr        if (!(Manager::getInstance())->isReady()) return;
96fca58076SAndreas Gohr        $event->preventDefault();
97fca58076SAndreas Gohr        $event->stopPropagation();
98*1c8522cbSAndreas Gohr        global $INPUT;
9997647c7eSAndreas Gohr
10097647c7eSAndreas Gohr        echo '<div class="plugin_twofactor_profile">';
10197647c7eSAndreas Gohr        echo $this->locale_xhtml('profile');
10297647c7eSAndreas Gohr
103*1c8522cbSAndreas Gohr        if ($INPUT->has('twofactor_setup')) {
104*1c8522cbSAndreas Gohr            $this->printProviderSetup();
105*1c8522cbSAndreas Gohr        } else {
106*1c8522cbSAndreas Gohr            if (!$this->printOptOutForm()) {
107*1c8522cbSAndreas Gohr                $this->printConfiguredProviders();
108*1c8522cbSAndreas Gohr
109*1c8522cbSAndreas Gohr                $this->printProviderSetupSelect();
110*1c8522cbSAndreas Gohr            }
111*1c8522cbSAndreas Gohr        }
112*1c8522cbSAndreas Gohr        echo '</div>';
113fca58076SAndreas Gohr    }
114fca58076SAndreas Gohr
115fca58076SAndreas Gohr    /**
116a635cb20SAndreas Gohr     * Handle POSTs for provider forms
117*1c8522cbSAndreas Gohr     *
118*1c8522cbSAndreas Gohr     * @return bool should a redirect be made?
119a635cb20SAndreas Gohr     */
120a635cb20SAndreas Gohr    protected function handleProfile()
121a635cb20SAndreas Gohr    {
122a635cb20SAndreas Gohr        global $INPUT;
123*1c8522cbSAndreas Gohr        if (!checkSecurityToken()) return true;
1245f8f561aSAndreas Gohr        $manager = Manager::getInstance();
125a635cb20SAndreas Gohr
126*1c8522cbSAndreas Gohr        if ($INPUT->has('twofactor_optout') && $this->getConf('optinout') === 'optout') {
1275f8f561aSAndreas Gohr            $manager->userOptOutState($INPUT->bool('optout'));
128*1c8522cbSAndreas Gohr            return true;
1294b9cff8aSAndreas Gohr        }
1304b9cff8aSAndreas Gohr
131*1c8522cbSAndreas Gohr        if (!$INPUT->has('provider')) return true;
1325f8f561aSAndreas Gohr        $providers = $manager->getAllProviders();
133*1c8522cbSAndreas Gohr        if (!isset($providers[$INPUT->str('provider')])) return true;
1344b9cff8aSAndreas Gohr        $provider = $providers[$INPUT->str('provider')];
1354b9cff8aSAndreas Gohr
136*1c8522cbSAndreas Gohr        if ($INPUT->has('twofactor_delete')) {
137*1c8522cbSAndreas Gohr            $provider->reset();
138*1c8522cbSAndreas Gohr            $manager->getUserDefaultProvider(); // resets the default to the next available
139*1c8522cbSAndreas Gohr            return true;
140*1c8522cbSAndreas Gohr        }
141*1c8522cbSAndreas Gohr
142*1c8522cbSAndreas Gohr        if ($INPUT->has('twofactor_default')) {
143*1c8522cbSAndreas Gohr            $manager->setUserDefaultProvider($provider);
144*1c8522cbSAndreas Gohr            return true;
145*1c8522cbSAndreas Gohr        }
146*1c8522cbSAndreas Gohr
147a635cb20SAndreas Gohr        if (!$provider->isConfigured()) {
148a635cb20SAndreas Gohr            $provider->handleProfileForm();
149*1c8522cbSAndreas Gohr            return $provider->isConfigured(); // redirect only if configuration finished
150a635cb20SAndreas Gohr        }
151*1c8522cbSAndreas Gohr
152*1c8522cbSAndreas Gohr        return true;
153a635cb20SAndreas Gohr    }
154a635cb20SAndreas Gohr
155a635cb20SAndreas Gohr    /**
15697647c7eSAndreas Gohr     * Print the opt-out form (if available)
1574b9cff8aSAndreas Gohr     *
15897647c7eSAndreas Gohr     * @return bool true if the user currently opted out
159fca58076SAndreas Gohr     */
16097647c7eSAndreas Gohr    protected function printOptOutForm()
161fca58076SAndreas Gohr    {
1625f8f561aSAndreas Gohr        $manager = Manager::getInstance();
16397647c7eSAndreas Gohr        $optedout = false;
16497647c7eSAndreas Gohr        $setting = $this->getConf('optinout');
165fca58076SAndreas Gohr
16697647c7eSAndreas Gohr        echo '<section class="state">';
16797647c7eSAndreas Gohr        echo $this->locale_xhtml($setting);
168fca58076SAndreas Gohr
16997647c7eSAndreas Gohr        // optout form
17097647c7eSAndreas Gohr        if ($setting == 'optout') {
1714b9cff8aSAndreas Gohr            $form = new Form(['method' => 'post']);
17297647c7eSAndreas Gohr            $cb = $form->addCheckbox('optout', $this->getLang('optout'));
1735f8f561aSAndreas Gohr            if ($manager->userOptOutState()) {
1744b9cff8aSAndreas Gohr                $cb->attr('checked', 'checked');
1754b9cff8aSAndreas Gohr            }
176*1c8522cbSAndreas Gohr            $form->addButton('twofactor_optout', $this->getLang('btn_confirm'));
1774b9cff8aSAndreas Gohr            echo $form->toHTML();
1784b9cff8aSAndreas Gohr
1794b9cff8aSAndreas Gohr            // when user opted out, don't show the rest of the form
1805f8f561aSAndreas Gohr            if ($manager->userOptOutState()) {
18197647c7eSAndreas Gohr                $optedout = true;
1824b9cff8aSAndreas Gohr            }
1834b9cff8aSAndreas Gohr        }
1844b9cff8aSAndreas Gohr
18597647c7eSAndreas Gohr        echo '</section>';
18697647c7eSAndreas Gohr        return $optedout;
18797647c7eSAndreas Gohr    }
18897647c7eSAndreas Gohr
18997647c7eSAndreas Gohr    /**
19097647c7eSAndreas Gohr     * Print the form where a user can select their default provider
19197647c7eSAndreas Gohr     *
19297647c7eSAndreas Gohr     * @return void
19397647c7eSAndreas Gohr     */
194*1c8522cbSAndreas Gohr    protected function printConfiguredProviders()
19597647c7eSAndreas Gohr    {
1965f8f561aSAndreas Gohr        $manager = Manager::getInstance();
19797647c7eSAndreas Gohr
1985f8f561aSAndreas Gohr        $userproviders = $manager->getUserProviders();
1995f8f561aSAndreas Gohr        $default = $manager->getUserDefaultProvider();
200*1c8522cbSAndreas Gohr        if (!$userproviders) return;
201*1c8522cbSAndreas Gohr
202b6119621SAndreas Gohr        $form = new Form(['method' => 'POST']);
203*1c8522cbSAndreas Gohr        $form->addFieldsetOpen($this->getLang('providers'));
204b6119621SAndreas Gohr        foreach ($userproviders as $provider) {
205f6b61423SAndreas Gohr            $el = $form->addRadioButton('provider', $provider->getLabel())->val($provider->getProviderID());
206f6b61423SAndreas Gohr            if ($provider->getProviderID() === $default->getProviderID()) {
207f6b61423SAndreas Gohr                $el->attr('checked', 'checked');
208*1c8522cbSAndreas Gohr                $el->getLabel()->val($provider->getLabel() . ' ' . $this->getLang('default'));
209f6b61423SAndreas Gohr            }
210b6119621SAndreas Gohr        }
211*1c8522cbSAndreas Gohr
212*1c8522cbSAndreas Gohr        $form->addTagOpen('div')->addClass('buttons');
213*1c8522cbSAndreas Gohr        $form->addButton('twofactor_default', $this->getLang('btn_default'))->attr('submit');
214*1c8522cbSAndreas Gohr        $form->addButton('twofactor_delete', $this->getLang('btn_remove'))
215*1c8522cbSAndreas Gohr             ->addClass('twofactor_delconfirm')->attr('submit');
216*1c8522cbSAndreas Gohr        $form->addTagClose('div');
217*1c8522cbSAndreas Gohr
218b6119621SAndreas Gohr        $form->addFieldsetClose();
219b6119621SAndreas Gohr        echo $form->toHTML();
220b6119621SAndreas Gohr    }
221b6119621SAndreas Gohr
22297647c7eSAndreas Gohr    /**
223*1c8522cbSAndreas Gohr     * List providers available for adding
22497647c7eSAndreas Gohr     *
22597647c7eSAndreas Gohr     * @return void
22697647c7eSAndreas Gohr     */
227*1c8522cbSAndreas Gohr    protected function printProviderSetupSelect()
22897647c7eSAndreas Gohr    {
2295f8f561aSAndreas Gohr        $manager = Manager::getInstance();
230*1c8522cbSAndreas Gohr        $available = $manager->getUserProviders(false);
231*1c8522cbSAndreas Gohr        if (!$available) return;
23297647c7eSAndreas Gohr
233*1c8522cbSAndreas Gohr        $options = [];
234*1c8522cbSAndreas Gohr        foreach ($available as $provider) {
235*1c8522cbSAndreas Gohr            $options[$provider->getProviderID()] = $provider->getLabel();
236a635cb20SAndreas Gohr        }
237*1c8522cbSAndreas Gohr
238*1c8522cbSAndreas Gohr        $form = new Form(['method' => 'post']);
239*1c8522cbSAndreas Gohr        $form->setHiddenField('do', 'twofactor_profile');
240*1c8522cbSAndreas Gohr        $form->setHiddenField('init', '1');
241*1c8522cbSAndreas Gohr        $form->addFieldsetOpen($this->getLang('newprovider'));
242*1c8522cbSAndreas Gohr        $form->addDropdown('provider', $options, $this->getLang('provider'));
243*1c8522cbSAndreas Gohr
244*1c8522cbSAndreas Gohr        $form->addTagOpen('div')->addClass('buttons');
245*1c8522cbSAndreas Gohr        $form->addButton('twofactor_setup', $this->getLang('btn_setup'))->attr('type', 'submit');
246*1c8522cbSAndreas Gohr        $form->addTagClose('div');
247*1c8522cbSAndreas Gohr
248a635cb20SAndreas Gohr        $form->addFieldsetClose();
249fca58076SAndreas Gohr        echo $form->toHTML();
250a635cb20SAndreas Gohr    }
25197647c7eSAndreas Gohr
252*1c8522cbSAndreas Gohr    /**
253*1c8522cbSAndreas Gohr     * Display the setup form for a provider
254*1c8522cbSAndreas Gohr     *
255*1c8522cbSAndreas Gohr     * @return void
256*1c8522cbSAndreas Gohr     */
257*1c8522cbSAndreas Gohr    protected function printProviderSetup()
258*1c8522cbSAndreas Gohr    {
259*1c8522cbSAndreas Gohr        global $lang;
260*1c8522cbSAndreas Gohr        global $INPUT;
261*1c8522cbSAndreas Gohr
262*1c8522cbSAndreas Gohr        $providerID = $INPUT->str('provider');
263*1c8522cbSAndreas Gohr        $providers = (Manager::getInstance())->getUserProviders(false);
264*1c8522cbSAndreas Gohr        if (!isset($providers[$providerID])) return;
265*1c8522cbSAndreas Gohr        $provider = $providers[$providerID];
266*1c8522cbSAndreas Gohr
267*1c8522cbSAndreas Gohr        $form = new Form(['method' => 'POST', 'class' => 'provider-' . $providerID]);
268*1c8522cbSAndreas Gohr        $form->setHiddenField('do', 'twofactor_profile');
269*1c8522cbSAndreas Gohr        $form->setHiddenField('twofactor_setup', '1');
270*1c8522cbSAndreas Gohr        $form->setHiddenField('provider', $provider->getProviderID());
271*1c8522cbSAndreas Gohr
272*1c8522cbSAndreas Gohr        $form->addFieldsetOpen($provider->getLabel());
273*1c8522cbSAndreas Gohr        $provider->renderProfileForm($form);
274*1c8522cbSAndreas Gohr
275*1c8522cbSAndreas Gohr        $form->addTagOpen('div')->addClass('buttons');
276*1c8522cbSAndreas Gohr        $form->addButton('twofactor_submit', $this->getLang('btn_confirm'))->attr('type', 'submit');
277*1c8522cbSAndreas Gohr        $form->addButton('twofactor_delete', $lang['btn_cancel'])->attr('type', 'submit');
278*1c8522cbSAndreas Gohr        $form->addTagClose('div');
279*1c8522cbSAndreas Gohr
280*1c8522cbSAndreas Gohr        $form->addFieldsetClose();
281*1c8522cbSAndreas Gohr        echo $form->toHTML();
282fca58076SAndreas Gohr    }
283*1c8522cbSAndreas Gohr
284fca58076SAndreas Gohr}
285fca58076SAndreas Gohr
28697647c7eSAndreas Gohr
287