xref: /plugin/twofactor/action/profile.php (revision b6119621988e00fd0cbd3059406f4ed18460274b)
1fca58076SAndreas Gohr<?php
2fca58076SAndreas Gohr
3*b6119621SAndreas Gohruse dokuwiki\Form\Form;
48b7620a8SAndreas Gohruse dokuwiki\plugin\twofactor\Manager;
58b7620a8SAndreas Gohruse dokuwiki\plugin\twofactor\MenuItem;
6a635cb20SAndreas Gohruse dokuwiki\plugin\twofactor\Provider;
7a635cb20SAndreas Gohr
8fca58076SAndreas Gohr/**
9fca58076SAndreas Gohr * DokuWiki Plugin twofactor (Action Component)
10fca58076SAndreas Gohr *
11fca58076SAndreas Gohr * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
12fca58076SAndreas Gohr */
13fca58076SAndreas Gohrclass action_plugin_twofactor_profile extends \dokuwiki\Extension\ActionPlugin
14fca58076SAndreas Gohr{
15*b6119621SAndreas Gohr    /** @var Manager */
16*b6119621SAndreas Gohr    protected $manager;
17*b6119621SAndreas Gohr
18*b6119621SAndreas Gohr    /**
19*b6119621SAndreas Gohr     * Constructor
20*b6119621SAndreas Gohr     */
21*b6119621SAndreas Gohr    public function __construct()
22*b6119621SAndreas Gohr    {
23*b6119621SAndreas Gohr        $this->manager = Manager::getInstance();
24*b6119621SAndreas Gohr    }
25fca58076SAndreas Gohr
26fca58076SAndreas Gohr    /** @inheritDoc */
27fca58076SAndreas Gohr    public function register(Doku_Event_Handler $controller)
28fca58076SAndreas Gohr    {
29*b6119621SAndreas Gohr        if (!$this->manager->isReady()) return;
308b7620a8SAndreas Gohr
31fca58076SAndreas Gohr        // Adds our twofactor profile to the user menu.
32fca58076SAndreas Gohr        $controller->register_hook('MENU_ITEMS_ASSEMBLY', 'AFTER', $this, 'handleUserMenuAssembly');
33fca58076SAndreas Gohr
34fca58076SAndreas Gohr        $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'handlePreProcess');
35fca58076SAndreas Gohr        $controller->register_hook('TPL_ACT_UNKNOWN', 'BEFORE', $this, 'handleUnknownAction');
36fca58076SAndreas Gohr    }
37fca58076SAndreas Gohr
38fca58076SAndreas Gohr    /**
39fca58076SAndreas Gohr     * Add 2fa Menu Item
40fca58076SAndreas Gohr     *
41fca58076SAndreas Gohr     * @param Doku_Event $event
42fca58076SAndreas Gohr     */
43fca58076SAndreas Gohr    public function handleUserMenuAssembly(Doku_Event $event)
44fca58076SAndreas Gohr    {
45fca58076SAndreas Gohr        global $INPUT;
46fca58076SAndreas Gohr        // If this is not the user menu, then get out.
47fca58076SAndreas Gohr        if ($event->data['view'] != 'user') return;
48fca58076SAndreas Gohr        if (!$INPUT->server->has('REMOTE_USER')) return;
49fca58076SAndreas Gohr
50fca58076SAndreas Gohr        // Create the new menu item
518b7620a8SAndreas Gohr        $menuitem = new MenuItem($this->getLang('btn_twofactor_profile'));
52fca58076SAndreas Gohr
53fca58076SAndreas Gohr        // Find index of existing Profile menu item.
54fca58076SAndreas Gohr        for ($index = 0; $index > count($event->data['items']); $index++) {
55fca58076SAndreas Gohr            if ($event->data['items'][$index]->getType() === 'profile') {
56fca58076SAndreas Gohr                break;
57fca58076SAndreas Gohr            }
58fca58076SAndreas Gohr        }
59fca58076SAndreas Gohr        array_splice($event->data['items'], $index + 1, 0, [$menuitem]);
60fca58076SAndreas Gohr    }
61fca58076SAndreas Gohr
62fca58076SAndreas Gohr    /**
63fca58076SAndreas Gohr     * Check permissions to call the 2fa profile
64fca58076SAndreas Gohr     *
65fca58076SAndreas Gohr     * @param Doku_Event $event
66fca58076SAndreas Gohr     */
67fca58076SAndreas Gohr    public function handlePreProcess(Doku_Event $event)
68fca58076SAndreas Gohr    {
69fca58076SAndreas Gohr        if ($event->data != 'twofactor_profile') return;
70fca58076SAndreas Gohr
71fca58076SAndreas Gohr        // We will be handling this action's permissions here.
72fca58076SAndreas Gohr        $event->preventDefault();
73fca58076SAndreas Gohr        $event->stopPropagation();
74fca58076SAndreas Gohr
75fca58076SAndreas Gohr        // If not logged into the main auth plugin then send there.
76fca58076SAndreas Gohr        global $INPUT;
77fca58076SAndreas Gohr        global $ID;
78fca58076SAndreas Gohr
79fca58076SAndreas Gohr        if (!$INPUT->server->has('REMOTE_USER')) {
80fca58076SAndreas Gohr            $event->result = false;
81fca58076SAndreas Gohr            send_redirect(wl($ID, array('do' => 'login'), true, '&'));
82fca58076SAndreas Gohr            return;
83fca58076SAndreas Gohr        }
84fca58076SAndreas Gohr
85a635cb20SAndreas Gohr        $this->handleProfile();
86a635cb20SAndreas Gohr
87fca58076SAndreas Gohr        /** FIXME
88fca58076SAndreas Gohr         *
89fca58076SAndreas Gohr         * // If not logged into twofactor then send there.
90fca58076SAndreas Gohr         * if (!$this->get_clearance()) {
91fca58076SAndreas Gohr         * $event->result = false;
92fca58076SAndreas Gohr         * send_redirect(wl($ID, array('do' => 'twofactor_login'), true, '&'));
93fca58076SAndreas Gohr         * return;
94fca58076SAndreas Gohr         * }
95fca58076SAndreas Gohr         * // Otherwise handle the action.
96fca58076SAndreas Gohr         * $event->result = $this->_process_changes($event, $param);
97fca58076SAndreas Gohr         */
98fca58076SAndreas Gohr
99fca58076SAndreas Gohr    }
100fca58076SAndreas Gohr
101fca58076SAndreas Gohr    /**
102fca58076SAndreas Gohr     * @param Doku_Event $event
103fca58076SAndreas Gohr     */
104fca58076SAndreas Gohr    public function handleUnknownAction(Doku_Event $event)
105fca58076SAndreas Gohr    {
106fca58076SAndreas Gohr        if ($event->data != 'twofactor_profile') return;
107fca58076SAndreas Gohr
108fca58076SAndreas Gohr        $event->preventDefault();
109fca58076SAndreas Gohr        $event->stopPropagation();
110fca58076SAndreas Gohr        $this->printProfile();
111fca58076SAndreas Gohr    }
112fca58076SAndreas Gohr
113fca58076SAndreas Gohr    /**
114a635cb20SAndreas Gohr     * Handle POSTs for provider forms
115a635cb20SAndreas Gohr     */
116a635cb20SAndreas Gohr    protected function handleProfile()
117a635cb20SAndreas Gohr    {
118a635cb20SAndreas Gohr        global $INPUT;
119a635cb20SAndreas Gohr        if (!$INPUT->has('provider')) return;
120a635cb20SAndreas Gohr        $user = $INPUT->server->str('REMOTE_USER');
121a635cb20SAndreas Gohr
122a635cb20SAndreas Gohr        $class = 'helper_plugin_' . $INPUT->str('provider');
123a635cb20SAndreas Gohr        /** @var Provider $provider */
124a635cb20SAndreas Gohr        $provider = new $class($user);
125a635cb20SAndreas Gohr
126a635cb20SAndreas Gohr        if (!checkSecurityToken()) return;
127a635cb20SAndreas Gohr
128a635cb20SAndreas Gohr        if (!$provider->isConfigured()) {
129a635cb20SAndreas Gohr            $provider->handleProfileForm();
130a635cb20SAndreas Gohr        } elseif ($INPUT->has('2fa_delete')) {
131a635cb20SAndreas Gohr            $provider->reset();
132*b6119621SAndreas Gohr            $this->manager->getUserDefaultProvider(); // resets the default to the next available
133*b6119621SAndreas Gohr        } elseif ($INPUT->has('2fa_default')) {
134*b6119621SAndreas Gohr            $this->manager->setUserDefaultProvider($provider);
135a635cb20SAndreas Gohr        }
136a635cb20SAndreas Gohr    }
137a635cb20SAndreas Gohr
138a635cb20SAndreas Gohr    /**
139fca58076SAndreas Gohr     * Handles the profile form rendering.  Displays user manageable settings.
140fca58076SAndreas Gohr     */
141a635cb20SAndreas Gohr    protected function printProfile()
142fca58076SAndreas Gohr    {
143fca58076SAndreas Gohr        global $lang;
144fca58076SAndreas Gohr
145a635cb20SAndreas Gohr        echo $this->locale_xhtml('profile');
146fca58076SAndreas Gohr
147*b6119621SAndreas Gohr        // default provider selection
148*b6119621SAndreas Gohr        $userproviders = $this->manager->getUserProviders();
149*b6119621SAndreas Gohr        $default = $this->manager->getUserDefaultProvider();
150*b6119621SAndreas Gohr        if (count($userproviders)) {
151*b6119621SAndreas Gohr            $form = new Form(['method' => 'POST']);
152*b6119621SAndreas Gohr            $form->addFieldsetOpen('Default Provider');
153*b6119621SAndreas Gohr            foreach ($userproviders as $provider) {
154*b6119621SAndreas Gohr                $form->addRadioButton('provider', $provider->getLabel())
155*b6119621SAndreas Gohr                     ->val($provider->getProviderID())
156*b6119621SAndreas Gohr                     ->attr('checked', $provider->getProviderID() === $default->getProviderID());
157*b6119621SAndreas Gohr            }
158*b6119621SAndreas Gohr            $form->addButton('2fa_default', $lang['btn_save'])->attr('submit');
159*b6119621SAndreas Gohr            $form->addFieldsetClose();
160*b6119621SAndreas Gohr            echo $form->toHTML();
161*b6119621SAndreas Gohr        }
162*b6119621SAndreas Gohr
163a635cb20SAndreas Gohr        // iterate over all providers
164*b6119621SAndreas Gohr        $providers = $this->manager->getAllProviders();
165a635cb20SAndreas Gohr        foreach ($providers as $provider) {
166a635cb20SAndreas Gohr            $form = new dokuwiki\Form\Form(['method' => 'POST']);
167a635cb20SAndreas Gohr            $form->setHiddenField('do', 'twofactor_profile');
168a635cb20SAndreas Gohr            $form->setHiddenField('provider', $provider->getProviderID());
169a635cb20SAndreas Gohr            $form->addFieldsetOpen($provider->getLabel());
170a635cb20SAndreas Gohr            $provider->renderProfileForm($form);
171a635cb20SAndreas Gohr            if (!$provider->isConfigured()) {
172a635cb20SAndreas Gohr                $form->addButton('2fa_submit', $lang['btn_save'])->attr('submit');
173a635cb20SAndreas Gohr            } else {
174a635cb20SAndreas Gohr                $form->addButton('2fa_delete', $lang['btn_delete'])->attr('submit');
175a635cb20SAndreas Gohr            }
176a635cb20SAndreas Gohr            $form->addFieldsetClose();
177fca58076SAndreas Gohr            echo $form->toHTML();
178a635cb20SAndreas Gohr        }
179fca58076SAndreas Gohr
180fca58076SAndreas Gohr        /* FIXME
181fca58076SAndreas Gohr
182fca58076SAndreas Gohr
183fca58076SAndreas Gohr        $optinout = $this->getConf("optinout");
184fca58076SAndreas Gohr        $optinvalue = $optinout == 'mandatory' ? 'in' : ($this->attribute ? $this->attribute->get("twofactor",
185fca58076SAndreas Gohr            "state") : '');
186fca58076SAndreas Gohr        $available = count($this->tokenMods) + count($this->otpMods) > 0;
187fca58076SAndreas Gohr        // If the user is being redirected here because of mandatory two factor, then display a message saying so.
188fca58076SAndreas Gohr        if (!$available && $optinout == 'mandatory') {
189fca58076SAndreas Gohr            msg($this->getLang('mandatory'), -1);
190fca58076SAndreas Gohr        } elseif ($this->attribute->get("twofactor", "state") == '' && $optinout == 'optout') {
191fca58076SAndreas Gohr            msg($this->getLang('optout_notice'), 2);
192fca58076SAndreas Gohr        } elseif ($this->attribute->get("twofactor",
193fca58076SAndreas Gohr                "state") == 'in' && count($this->tokenMods) == 0 && count($this->otpMods) == 0) {
194fca58076SAndreas Gohr            msg($this->getLang('not_configured_notice'), 2);
195fca58076SAndreas Gohr        }
196fca58076SAndreas Gohr        global $USERINFO, $lang, $conf;
197fca58076SAndreas Gohr        $form = new Doku_Form(array('id' => 'twofactor_setup'));
198fca58076SAndreas Gohr        // Add the checkbox to opt in and out, only if optinout is not mandatory.
199fca58076SAndreas Gohr        $items = array();
200fca58076SAndreas Gohr        if ($optinout != 'mandatory') {
201fca58076SAndreas Gohr            if (!$this->attribute || !$optinvalue) {  // If there is no personal setting for optin, the default is based on the wiki default.
202fca58076SAndreas Gohr                $optinvalue = $this->getConf("optinout") == 'optout';
203fca58076SAndreas Gohr            }
204fca58076SAndreas Gohr            $items[] = form_makeCheckboxField('optinout', '1', $this->getLang('twofactor_optin'), '', 'block',
205fca58076SAndreas Gohr                $optinvalue == 'in' ? array('checked' => 'checked') : array());
206fca58076SAndreas Gohr        }
207fca58076SAndreas Gohr        // Add the notification checkbox if appropriate.
208fca58076SAndreas Gohr        if ($this->getConf('loginnotice') == 'user' && $optinvalue == 'in' && count($this->otpMods) > 0) {
209fca58076SAndreas Gohr            $loginnotice = $this->attribute ? $this->attribute->get("twofactor", "loginnotice") : false;
210fca58076SAndreas Gohr            $items[] = form_makeCheckboxField('loginnotice', '1', $this->getLang('twofactor_notify'), '', 'block',
211fca58076SAndreas Gohr                $loginnotice === true ? array('checked' => 'checked') : array());
212fca58076SAndreas Gohr        }
213fca58076SAndreas Gohr        // Select a notification provider.
214fca58076SAndreas Gohr        if ($optinvalue == 'in') {
215fca58076SAndreas Gohr            // If there is more than one choice, have the user select the default.
216fca58076SAndreas Gohr            if (count($this->otpMods) > 1) {
217fca58076SAndreas Gohr                $defaultMod = $this->attribute->exists("twofactor", "defaultmod") ? $this->attribute->get("twofactor",
218fca58076SAndreas Gohr                    "defaultmod") : null;
219fca58076SAndreas Gohr                $modList = array_merge(array($this->getLang('useallotp')), array_keys($this->otpMods));
220fca58076SAndreas Gohr                $items[] = form_makeListboxField('default_module', $modList, $defaultMod,
221fca58076SAndreas Gohr                    $this->getLang('defaultmodule'), '', 'block');
222fca58076SAndreas Gohr            }
223fca58076SAndreas Gohr        }
224fca58076SAndreas Gohr        if (count($items) > 0) {
225fca58076SAndreas Gohr            $form->startFieldset($this->getLang('settings'));
226fca58076SAndreas Gohr            foreach ($items as $item) {
227fca58076SAndreas Gohr                $form->addElement($item);
228fca58076SAndreas Gohr            }
229fca58076SAndreas Gohr            $form->endFieldset();
230fca58076SAndreas Gohr        }
231fca58076SAndreas Gohr        // TODO: Make this AJAX so that the user does not have to keep clicking
232fca58076SAndreas Gohr        // submit them Update Profile!
233fca58076SAndreas Gohr        // Loop through all modules and render the profile components.
234fca58076SAndreas Gohr        if ($optinvalue == 'in') {
235fca58076SAndreas Gohr            $parts = array();
236fca58076SAndreas Gohr            foreach ($this->modules as $mod) {
237fca58076SAndreas Gohr                if ($mod->getConf("enable") == 1) {
238fca58076SAndreas Gohr                    $this->log('twofactor_profile_form: processing ' . get_class($mod) . '::renderProfileForm()',
239fca58076SAndreas Gohr                        self::LOGGING_DEBUG);
240fca58076SAndreas Gohr                    $items = $mod->renderProfileForm();
241fca58076SAndreas Gohr                    if (count($items) > 0) {
242fca58076SAndreas Gohr                        $form->startFieldset($mod->getLang('name'));
243fca58076SAndreas Gohr                        foreach ($items as $item) {
244fca58076SAndreas Gohr                            $form->addElement($item);
245fca58076SAndreas Gohr                        }
246fca58076SAndreas Gohr                        $form->endFieldset();
247fca58076SAndreas Gohr                    }
248fca58076SAndreas Gohr                }
249fca58076SAndreas Gohr            }
250fca58076SAndreas Gohr        }
251fca58076SAndreas Gohr        if ($conf['profileconfirm']) {
252fca58076SAndreas Gohr            $form->addElement('<br />');
253fca58076SAndreas Gohr            $form->startFieldset($this->getLang('verify_password'));
254fca58076SAndreas Gohr            $form->addElement(form_makePasswordField('oldpass', $lang['oldpass'], '', 'block',
255fca58076SAndreas Gohr                array('size' => '50', 'required' => 'required')));
256fca58076SAndreas Gohr            $form->endFieldset();
257fca58076SAndreas Gohr        }
258fca58076SAndreas Gohr        $form->addElement('<br />');
259fca58076SAndreas Gohr        $form->addElement(form_makeButton('submit', '', $lang['btn_save']));
260fca58076SAndreas Gohr        $form->addElement('<a href="' . wl($ID, array('do' => 'show'), true,
261fca58076SAndreas Gohr                '&') . '">' . $this->getLang('btn_return') . '</a>');
262fca58076SAndreas Gohr        $form->addHidden('do', 'twofactor_profile');
263fca58076SAndreas Gohr        $form->addHidden('save', '1');
264fca58076SAndreas Gohr        echo '<div class="centeralign">' . NL . $form->getForm() . '</div>' . NL;
265fca58076SAndreas Gohr
266fca58076SAndreas Gohr         */
267fca58076SAndreas Gohr    }
268fca58076SAndreas Gohr}
269fca58076SAndreas Gohr
270