xref: /plugin/twofactor/action/profile.php (revision 97647c7eaef0a26a9ad5adfa65a491a6ce7f8784)
1fca58076SAndreas Gohr<?php
2fca58076SAndreas Gohr
3b6119621SAndreas Gohruse dokuwiki\Form\Form;
48b7620a8SAndreas Gohruse dokuwiki\plugin\twofactor\Manager;
58b7620a8SAndreas Gohruse dokuwiki\plugin\twofactor\MenuItem;
6a635cb20SAndreas Gohr
7fca58076SAndreas Gohr/**
8fca58076SAndreas Gohr * DokuWiki Plugin twofactor (Action Component)
9fca58076SAndreas Gohr *
10*97647c7eSAndreas Gohr * This handles the 2fa profile screen where users can set their 2fa preferences and configure the
11*97647c7eSAndreas Gohr * providers they want to use.
12*97647c7eSAndreas Gohr *
13fca58076SAndreas Gohr * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
14fca58076SAndreas Gohr */
15fca58076SAndreas Gohrclass action_plugin_twofactor_profile extends \dokuwiki\Extension\ActionPlugin
16fca58076SAndreas Gohr{
17b6119621SAndreas Gohr    /** @var Manager */
18b6119621SAndreas Gohr    protected $manager;
19b6119621SAndreas Gohr
20b6119621SAndreas Gohr    /**
21b6119621SAndreas Gohr     * Constructor
22b6119621SAndreas Gohr     */
23b6119621SAndreas Gohr    public function __construct()
24b6119621SAndreas Gohr    {
25b6119621SAndreas Gohr        $this->manager = Manager::getInstance();
26b6119621SAndreas Gohr    }
27fca58076SAndreas Gohr
28fca58076SAndreas Gohr    /** @inheritDoc */
29fca58076SAndreas Gohr    public function register(Doku_Event_Handler $controller)
30fca58076SAndreas Gohr    {
31b6119621SAndreas Gohr        if (!$this->manager->isReady()) return;
328b7620a8SAndreas Gohr
33fca58076SAndreas Gohr        // Adds our twofactor profile to the user menu.
34fca58076SAndreas Gohr        $controller->register_hook('MENU_ITEMS_ASSEMBLY', 'AFTER', $this, 'handleUserMenuAssembly');
35fca58076SAndreas Gohr
36fca58076SAndreas Gohr        $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'handlePreProcess');
37fca58076SAndreas Gohr        $controller->register_hook('TPL_ACT_UNKNOWN', 'BEFORE', $this, 'handleUnknownAction');
38fca58076SAndreas Gohr    }
39fca58076SAndreas Gohr
40fca58076SAndreas Gohr    /**
41fca58076SAndreas Gohr     * Add 2fa Menu Item
42fca58076SAndreas Gohr     *
43fca58076SAndreas Gohr     * @param Doku_Event $event
44fca58076SAndreas Gohr     */
45fca58076SAndreas Gohr    public function handleUserMenuAssembly(Doku_Event $event)
46fca58076SAndreas Gohr    {
47fca58076SAndreas Gohr        global $INPUT;
48fca58076SAndreas Gohr        // If this is not the user menu, then get out.
49fca58076SAndreas Gohr        if ($event->data['view'] != 'user') return;
50fca58076SAndreas Gohr        if (!$INPUT->server->has('REMOTE_USER')) return;
51fca58076SAndreas Gohr
52fca58076SAndreas Gohr        // Create the new menu item
538b7620a8SAndreas Gohr        $menuitem = new MenuItem($this->getLang('btn_twofactor_profile'));
54fca58076SAndreas Gohr
55fca58076SAndreas Gohr        // Find index of existing Profile menu item.
56fca58076SAndreas Gohr        for ($index = 0; $index > count($event->data['items']); $index++) {
57fca58076SAndreas Gohr            if ($event->data['items'][$index]->getType() === 'profile') {
58fca58076SAndreas Gohr                break;
59fca58076SAndreas Gohr            }
60fca58076SAndreas Gohr        }
61fca58076SAndreas Gohr        array_splice($event->data['items'], $index + 1, 0, [$menuitem]);
62fca58076SAndreas Gohr    }
63fca58076SAndreas Gohr
64fca58076SAndreas Gohr    /**
65fca58076SAndreas Gohr     * Check permissions to call the 2fa profile
66fca58076SAndreas Gohr     *
67fca58076SAndreas Gohr     * @param Doku_Event $event
68fca58076SAndreas Gohr     */
69fca58076SAndreas Gohr    public function handlePreProcess(Doku_Event $event)
70fca58076SAndreas Gohr    {
71fca58076SAndreas Gohr        if ($event->data != 'twofactor_profile') return;
72fca58076SAndreas Gohr
73fca58076SAndreas Gohr        // We will be handling this action's permissions here.
74fca58076SAndreas Gohr        $event->preventDefault();
75fca58076SAndreas Gohr        $event->stopPropagation();
76fca58076SAndreas Gohr
77fca58076SAndreas Gohr        // If not logged into the main auth plugin then send there.
78fca58076SAndreas Gohr        global $INPUT;
79fca58076SAndreas Gohr        global $ID;
80fca58076SAndreas Gohr
81fca58076SAndreas Gohr        if (!$INPUT->server->has('REMOTE_USER')) {
82fca58076SAndreas Gohr            $event->result = false;
83fca58076SAndreas Gohr            send_redirect(wl($ID, array('do' => 'login'), true, '&'));
84fca58076SAndreas Gohr            return;
85fca58076SAndreas Gohr        }
86fca58076SAndreas Gohr
874b9cff8aSAndreas Gohr        if (strtolower($INPUT->server->str('REQUEST_METHOD')) == 'post') {
88a635cb20SAndreas Gohr            $this->handleProfile();
89*97647c7eSAndreas Gohr            // we might have changed something important, make sure the whole workflow restarts
90*97647c7eSAndreas Gohr            send_redirect(wl($ID, ['do' => 'twofactor_profile'], true, '&'));
91fca58076SAndreas Gohr        }
92fca58076SAndreas Gohr
934b9cff8aSAndreas Gohr    }
944b9cff8aSAndreas Gohr
95fca58076SAndreas Gohr    /**
96*97647c7eSAndreas Gohr     * Output the forms
97*97647c7eSAndreas Gohr     *
98fca58076SAndreas Gohr     * @param Doku_Event $event
99fca58076SAndreas Gohr     */
100fca58076SAndreas Gohr    public function handleUnknownAction(Doku_Event $event)
101fca58076SAndreas Gohr    {
102fca58076SAndreas Gohr        if ($event->data != 'twofactor_profile') return;
103fca58076SAndreas Gohr        $event->preventDefault();
104fca58076SAndreas Gohr        $event->stopPropagation();
105*97647c7eSAndreas Gohr
106*97647c7eSAndreas Gohr        echo '<div class="plugin_twofactor_profile">';
107*97647c7eSAndreas Gohr        echo $this->locale_xhtml('profile');
108*97647c7eSAndreas Gohr        if ($this->printOptOutForm()) return;
109*97647c7eSAndreas Gohr        $this->printDefaultProviderForm();
110*97647c7eSAndreas Gohr        $this->printProviderForms();
111*97647c7eSAndreas Gohr        echo '</div>';
112*97647c7eSAndreas Gohr
113fca58076SAndreas Gohr    }
114fca58076SAndreas Gohr
115fca58076SAndreas Gohr    /**
116a635cb20SAndreas Gohr     * Handle POSTs for provider forms
117a635cb20SAndreas Gohr     */
118a635cb20SAndreas Gohr    protected function handleProfile()
119a635cb20SAndreas Gohr    {
120a635cb20SAndreas Gohr        global $INPUT;
121a635cb20SAndreas Gohr        if (!checkSecurityToken()) return;
122a635cb20SAndreas Gohr
1234b9cff8aSAndreas Gohr        if ($INPUT->has('2fa_optout') && $this->getConf('optinout') === 'optout') {
1244b9cff8aSAndreas Gohr            $this->manager->userOptOutState($INPUT->bool('optout'));
1254b9cff8aSAndreas Gohr            return;
1264b9cff8aSAndreas Gohr        }
1274b9cff8aSAndreas Gohr
1284b9cff8aSAndreas Gohr        if (!$INPUT->has('provider')) return;
1294b9cff8aSAndreas Gohr        $providers = $this->manager->getAllProviders();
1304b9cff8aSAndreas Gohr        if (!isset($providers[$INPUT->str('provider')])) return;
1314b9cff8aSAndreas Gohr        $provider = $providers[$INPUT->str('provider')];
1324b9cff8aSAndreas Gohr
133a635cb20SAndreas Gohr        if (!$provider->isConfigured()) {
134a635cb20SAndreas Gohr            $provider->handleProfileForm();
135a635cb20SAndreas Gohr        } elseif ($INPUT->has('2fa_delete')) {
136a635cb20SAndreas Gohr            $provider->reset();
137b6119621SAndreas Gohr            $this->manager->getUserDefaultProvider(); // resets the default to the next available
138b6119621SAndreas Gohr        } elseif ($INPUT->has('2fa_default')) {
139b6119621SAndreas Gohr            $this->manager->setUserDefaultProvider($provider);
140a635cb20SAndreas Gohr        }
141a635cb20SAndreas Gohr    }
142a635cb20SAndreas Gohr
143a635cb20SAndreas Gohr    /**
144*97647c7eSAndreas Gohr     * Print the opt-out form (if available)
1454b9cff8aSAndreas Gohr     *
146*97647c7eSAndreas Gohr     * @return bool true if the user currently opted out
147fca58076SAndreas Gohr     */
148*97647c7eSAndreas Gohr    protected function printOptOutForm()
149fca58076SAndreas Gohr    {
150*97647c7eSAndreas Gohr        $optedout = false;
151*97647c7eSAndreas Gohr        $setting = $this->getConf('optinout');
152fca58076SAndreas Gohr
153*97647c7eSAndreas Gohr        echo '<section class="state">';
154*97647c7eSAndreas Gohr        echo $this->locale_xhtml($setting);
155fca58076SAndreas Gohr
156*97647c7eSAndreas Gohr        // optout form
157*97647c7eSAndreas Gohr        if ($setting == 'optout') {
1584b9cff8aSAndreas Gohr            $form = new Form(['method' => 'post']);
159*97647c7eSAndreas Gohr            $cb = $form->addCheckbox('optout', $this->getLang('optout'));
1604b9cff8aSAndreas Gohr            if ($this->manager->userOptOutState()) {
1614b9cff8aSAndreas Gohr                $cb->attr('checked', 'checked');
1624b9cff8aSAndreas Gohr            }
163*97647c7eSAndreas Gohr            $form->addButton('2fa_optout', $this->getLang('btn_confirm'));
1644b9cff8aSAndreas Gohr            echo $form->toHTML();
1654b9cff8aSAndreas Gohr
1664b9cff8aSAndreas Gohr            // when user opted out, don't show the rest of the form
1674b9cff8aSAndreas Gohr            if ($this->manager->userOptOutState()) {
168*97647c7eSAndreas Gohr                $optedout = true;
1694b9cff8aSAndreas Gohr            }
1704b9cff8aSAndreas Gohr        }
1714b9cff8aSAndreas Gohr
172*97647c7eSAndreas Gohr        echo '</section>';
173*97647c7eSAndreas Gohr        return $optedout;
174*97647c7eSAndreas Gohr    }
175*97647c7eSAndreas Gohr
176*97647c7eSAndreas Gohr    /**
177*97647c7eSAndreas Gohr     * Print the form where a user can select their default provider
178*97647c7eSAndreas Gohr     *
179*97647c7eSAndreas Gohr     * @return void
180*97647c7eSAndreas Gohr     */
181*97647c7eSAndreas Gohr    protected function printDefaultProviderForm()
182*97647c7eSAndreas Gohr    {
183*97647c7eSAndreas Gohr        global $lang;
184*97647c7eSAndreas Gohr
185b6119621SAndreas Gohr        $userproviders = $this->manager->getUserProviders();
186b6119621SAndreas Gohr        $default = $this->manager->getUserDefaultProvider();
187b6119621SAndreas Gohr        if (count($userproviders)) {
188b6119621SAndreas Gohr            $form = new Form(['method' => 'POST']);
189*97647c7eSAndreas Gohr            $form->addFieldsetOpen($this->getLang('defaultprovider'));
190b6119621SAndreas Gohr            foreach ($userproviders as $provider) {
191b6119621SAndreas Gohr                $form->addRadioButton('provider', $provider->getLabel())
192b6119621SAndreas Gohr                     ->val($provider->getProviderID())
193b6119621SAndreas Gohr                     ->attr('checked', $provider->getProviderID() === $default->getProviderID());
194b6119621SAndreas Gohr            }
195b6119621SAndreas Gohr            $form->addButton('2fa_default', $lang['btn_save'])->attr('submit');
196b6119621SAndreas Gohr            $form->addFieldsetClose();
197b6119621SAndreas Gohr            echo $form->toHTML();
198b6119621SAndreas Gohr        }
199*97647c7eSAndreas Gohr    }
200b6119621SAndreas Gohr
201*97647c7eSAndreas Gohr    /**
202*97647c7eSAndreas Gohr     * Prints a form for each available provider to configure
203*97647c7eSAndreas Gohr     *
204*97647c7eSAndreas Gohr     * @return void
205*97647c7eSAndreas Gohr     */
206*97647c7eSAndreas Gohr    protected function printProviderForms()
207*97647c7eSAndreas Gohr    {
208*97647c7eSAndreas Gohr        global $lang;
209*97647c7eSAndreas Gohr
210*97647c7eSAndreas Gohr        echo '<section class="providers">';
211*97647c7eSAndreas Gohr        echo '<h2>' . $this->getLang('providers') . '</h2>';
212*97647c7eSAndreas Gohr
213*97647c7eSAndreas Gohr        echo '<div>';
214b6119621SAndreas Gohr        $providers = $this->manager->getAllProviders();
215a635cb20SAndreas Gohr        foreach ($providers as $provider) {
216a635cb20SAndreas Gohr            $form = new dokuwiki\Form\Form(['method' => 'POST']);
217a635cb20SAndreas Gohr            $form->setHiddenField('do', 'twofactor_profile');
218a635cb20SAndreas Gohr            $form->setHiddenField('provider', $provider->getProviderID());
219a635cb20SAndreas Gohr            $form->addFieldsetOpen($provider->getLabel());
220a635cb20SAndreas Gohr            $provider->renderProfileForm($form);
221a635cb20SAndreas Gohr            if (!$provider->isConfigured()) {
222*97647c7eSAndreas Gohr                $form->addButton('2fa_submit', $lang['btn_save'])->attr('type', 'submit');
223a635cb20SAndreas Gohr            } else {
224*97647c7eSAndreas Gohr                $form->addButton('2fa_delete', $lang['btn_delete'])
225*97647c7eSAndreas Gohr                     ->addClass('twofactor_delconfirm')
226*97647c7eSAndreas Gohr                     ->attr('type', 'submit');
227a635cb20SAndreas Gohr            }
228a635cb20SAndreas Gohr            $form->addFieldsetClose();
229fca58076SAndreas Gohr            echo $form->toHTML();
230a635cb20SAndreas Gohr        }
231*97647c7eSAndreas Gohr        echo '</div>';
232*97647c7eSAndreas Gohr
233*97647c7eSAndreas Gohr        echo '</section>';
234fca58076SAndreas Gohr    }
235fca58076SAndreas Gohr}
236fca58076SAndreas Gohr
237*97647c7eSAndreas Gohr
238