xref: /plugin/twofactor/action/profile.php (revision f6b614231a195f86217e7f374ad6d04e6561a1a5)
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') {
79a635cb20SAndreas Gohr            $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    }
854b9cff8aSAndreas Gohr
86fca58076SAndreas Gohr    /**
8797647c7eSAndreas Gohr     * Output the forms
8897647c7eSAndreas Gohr     *
89fca58076SAndreas Gohr     * @param Doku_Event $event
90fca58076SAndreas Gohr     */
91fca58076SAndreas Gohr    public function handleUnknownAction(Doku_Event $event)
92fca58076SAndreas Gohr    {
93fca58076SAndreas Gohr        if ($event->data != 'twofactor_profile') return;
945f8f561aSAndreas Gohr        if (!(Manager::getInstance())->isReady()) return;
95fca58076SAndreas Gohr        $event->preventDefault();
96fca58076SAndreas Gohr        $event->stopPropagation();
9797647c7eSAndreas Gohr
9897647c7eSAndreas Gohr        echo '<div class="plugin_twofactor_profile">';
9997647c7eSAndreas Gohr        echo $this->locale_xhtml('profile');
10097647c7eSAndreas Gohr        if ($this->printOptOutForm()) return;
10197647c7eSAndreas Gohr        $this->printDefaultProviderForm();
10297647c7eSAndreas Gohr        $this->printProviderForms();
10397647c7eSAndreas Gohr        echo '</div>';
10497647c7eSAndreas Gohr
105fca58076SAndreas Gohr    }
106fca58076SAndreas Gohr
107fca58076SAndreas Gohr    /**
108a635cb20SAndreas Gohr     * Handle POSTs for provider forms
109a635cb20SAndreas Gohr     */
110a635cb20SAndreas Gohr    protected function handleProfile()
111a635cb20SAndreas Gohr    {
112a635cb20SAndreas Gohr        global $INPUT;
113a635cb20SAndreas Gohr        if (!checkSecurityToken()) return;
1145f8f561aSAndreas Gohr        $manager = Manager::getInstance();
115a635cb20SAndreas Gohr
1164b9cff8aSAndreas Gohr        if ($INPUT->has('2fa_optout') && $this->getConf('optinout') === 'optout') {
1175f8f561aSAndreas Gohr            $manager->userOptOutState($INPUT->bool('optout'));
1184b9cff8aSAndreas Gohr            return;
1194b9cff8aSAndreas Gohr        }
1204b9cff8aSAndreas Gohr
1214b9cff8aSAndreas Gohr        if (!$INPUT->has('provider')) return;
1225f8f561aSAndreas Gohr        $providers = $manager->getAllProviders();
1234b9cff8aSAndreas Gohr        if (!isset($providers[$INPUT->str('provider')])) return;
1244b9cff8aSAndreas Gohr        $provider = $providers[$INPUT->str('provider')];
1254b9cff8aSAndreas Gohr
126a635cb20SAndreas Gohr        if (!$provider->isConfigured()) {
127a635cb20SAndreas Gohr            $provider->handleProfileForm();
128a635cb20SAndreas Gohr        } elseif ($INPUT->has('2fa_delete')) {
129a635cb20SAndreas Gohr            $provider->reset();
1305f8f561aSAndreas Gohr            $manager->getUserDefaultProvider(); // resets the default to the next available
131b6119621SAndreas Gohr        } elseif ($INPUT->has('2fa_default')) {
1325f8f561aSAndreas Gohr            $manager->setUserDefaultProvider($provider);
133a635cb20SAndreas Gohr        }
134a635cb20SAndreas Gohr    }
135a635cb20SAndreas Gohr
136a635cb20SAndreas Gohr    /**
13797647c7eSAndreas Gohr     * Print the opt-out form (if available)
1384b9cff8aSAndreas Gohr     *
13997647c7eSAndreas Gohr     * @return bool true if the user currently opted out
140fca58076SAndreas Gohr     */
14197647c7eSAndreas Gohr    protected function printOptOutForm()
142fca58076SAndreas Gohr    {
1435f8f561aSAndreas Gohr        $manager = Manager::getInstance();
14497647c7eSAndreas Gohr        $optedout = false;
14597647c7eSAndreas Gohr        $setting = $this->getConf('optinout');
146fca58076SAndreas Gohr
14797647c7eSAndreas Gohr        echo '<section class="state">';
14897647c7eSAndreas Gohr        echo $this->locale_xhtml($setting);
149fca58076SAndreas Gohr
15097647c7eSAndreas Gohr        // optout form
15197647c7eSAndreas Gohr        if ($setting == 'optout') {
1524b9cff8aSAndreas Gohr            $form = new Form(['method' => 'post']);
15397647c7eSAndreas Gohr            $cb = $form->addCheckbox('optout', $this->getLang('optout'));
1545f8f561aSAndreas Gohr            if ($manager->userOptOutState()) {
1554b9cff8aSAndreas Gohr                $cb->attr('checked', 'checked');
1564b9cff8aSAndreas Gohr            }
15797647c7eSAndreas Gohr            $form->addButton('2fa_optout', $this->getLang('btn_confirm'));
1584b9cff8aSAndreas Gohr            echo $form->toHTML();
1594b9cff8aSAndreas Gohr
1604b9cff8aSAndreas Gohr            // when user opted out, don't show the rest of the form
1615f8f561aSAndreas Gohr            if ($manager->userOptOutState()) {
16297647c7eSAndreas Gohr                $optedout = true;
1634b9cff8aSAndreas Gohr            }
1644b9cff8aSAndreas Gohr        }
1654b9cff8aSAndreas Gohr
16697647c7eSAndreas Gohr        echo '</section>';
16797647c7eSAndreas Gohr        return $optedout;
16897647c7eSAndreas Gohr    }
16997647c7eSAndreas Gohr
17097647c7eSAndreas Gohr    /**
17197647c7eSAndreas Gohr     * Print the form where a user can select their default provider
17297647c7eSAndreas Gohr     *
17397647c7eSAndreas Gohr     * @return void
17497647c7eSAndreas Gohr     */
17597647c7eSAndreas Gohr    protected function printDefaultProviderForm()
17697647c7eSAndreas Gohr    {
17797647c7eSAndreas Gohr        global $lang;
1785f8f561aSAndreas Gohr        $manager = Manager::getInstance();
17997647c7eSAndreas Gohr
1805f8f561aSAndreas Gohr        $userproviders = $manager->getUserProviders();
1815f8f561aSAndreas Gohr        $default = $manager->getUserDefaultProvider();
182b6119621SAndreas Gohr        if (count($userproviders)) {
183b6119621SAndreas Gohr            $form = new Form(['method' => 'POST']);
18497647c7eSAndreas Gohr            $form->addFieldsetOpen($this->getLang('defaultprovider'));
185b6119621SAndreas Gohr            foreach ($userproviders as $provider) {
186*f6b61423SAndreas Gohr                $el = $form->addRadioButton('provider', $provider->getLabel())->val($provider->getProviderID());
187*f6b61423SAndreas Gohr                if ($provider->getProviderID() === $default->getProviderID()) {
188*f6b61423SAndreas Gohr                    $el->attr('checked', 'checked');
189*f6b61423SAndreas Gohr                }
190b6119621SAndreas Gohr            }
191b6119621SAndreas Gohr            $form->addButton('2fa_default', $lang['btn_save'])->attr('submit');
192b6119621SAndreas Gohr            $form->addFieldsetClose();
193b6119621SAndreas Gohr            echo $form->toHTML();
194b6119621SAndreas Gohr        }
19597647c7eSAndreas Gohr    }
196b6119621SAndreas Gohr
19797647c7eSAndreas Gohr    /**
19897647c7eSAndreas Gohr     * Prints a form for each available provider to configure
19997647c7eSAndreas Gohr     *
20097647c7eSAndreas Gohr     * @return void
20197647c7eSAndreas Gohr     */
20297647c7eSAndreas Gohr    protected function printProviderForms()
20397647c7eSAndreas Gohr    {
20497647c7eSAndreas Gohr        global $lang;
2055f8f561aSAndreas Gohr        $manager = Manager::getInstance();
20697647c7eSAndreas Gohr
20797647c7eSAndreas Gohr        echo '<section class="providers">';
20897647c7eSAndreas Gohr        echo '<h2>' . $this->getLang('providers') . '</h2>';
20997647c7eSAndreas Gohr
21097647c7eSAndreas Gohr        echo '<div>';
2115f8f561aSAndreas Gohr        $providers = $manager->getAllProviders();
212a635cb20SAndreas Gohr        foreach ($providers as $provider) {
213a635cb20SAndreas Gohr            $form = new dokuwiki\Form\Form(['method' => 'POST']);
214a635cb20SAndreas Gohr            $form->setHiddenField('do', 'twofactor_profile');
215a635cb20SAndreas Gohr            $form->setHiddenField('provider', $provider->getProviderID());
216a635cb20SAndreas Gohr            $form->addFieldsetOpen($provider->getLabel());
217a635cb20SAndreas Gohr            $provider->renderProfileForm($form);
218a635cb20SAndreas Gohr            if (!$provider->isConfigured()) {
21997647c7eSAndreas Gohr                $form->addButton('2fa_submit', $lang['btn_save'])->attr('type', 'submit');
220a635cb20SAndreas Gohr            } else {
22197647c7eSAndreas Gohr                $form->addButton('2fa_delete', $lang['btn_delete'])
22297647c7eSAndreas Gohr                     ->addClass('twofactor_delconfirm')
22397647c7eSAndreas Gohr                     ->attr('type', 'submit');
224a635cb20SAndreas Gohr            }
225a635cb20SAndreas Gohr            $form->addFieldsetClose();
226fca58076SAndreas Gohr            echo $form->toHTML();
227a635cb20SAndreas Gohr        }
22897647c7eSAndreas Gohr        echo '</div>';
22997647c7eSAndreas Gohr
23097647c7eSAndreas Gohr        echo '</section>';
231fca58076SAndreas Gohr    }
232fca58076SAndreas Gohr}
233fca58076SAndreas Gohr
23497647c7eSAndreas Gohr
235