1<?php
2/**
3 * DokuWiki Plugin oauth (Action Component)
4 *
5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
6 * @author  Andreas Gohr <andi@splitbrain.org>
7 */
8
9// must be run within Dokuwiki
10if(!defined('DOKU_INC')) die();
11
12class action_plugin_evesso extends DokuWiki_Action_Plugin {
13
14    /**
15     * Registers a callback function for a given event
16     *
17     * @param Doku_Event_Handler $controller DokuWiki's event controller object
18     * @return void
19     */
20    public function register(Doku_Event_Handler $controller) {
21        global $conf;
22        if($conf['authtype'] != 'evesso') return;
23
24        $conf['profileconfirm'] = false; // password confirmation doesn't work with oauth only users
25
26        $controller->register_hook('DOKUWIKI_STARTED', 'BEFORE', $this, 'handle_start');
27        $controller->register_hook('HTML_LOGINFORM_OUTPUT', 'BEFORE', $this, 'handle_loginform');
28        $controller->register_hook('HTML_UPDATEPROFILEFORM_OUTPUT', 'BEFORE', $this, 'handle_profileform');
29        $controller->register_hook('FORM_LOGIN_OUTPUT', 'BEFORE', $this, 'handle_loginform');
30        $controller->register_hook('FORM_UPDATEPROFILE_OUTPUT', 'BEFORE', $this, 'handle_profileform');
31        $controller->register_hook('AUTH_USER_CHANGE', 'BEFORE', $this, 'handle_usermod');
32        $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'handle_dologin');
33    }
34
35    /**
36     * Start an oAuth login or restore  environment after successful login
37     *
38     * @param Doku_Event $event  event object by reference
39     * @param mixed      $param  [the parameters passed as fifth argument to register_hook() when this
40     *                           handler was registered]
41     * @return void
42     */
43    public function handle_start(Doku_Event &$event, $param) {
44        global $ID;
45        if (isset($_SESSION[DOKU_COOKIE]['oauth-logout'])){
46            unset($_SESSION[DOKU_COOKIE]['oauth-logout']);
47            send_redirect(wl($ID));
48            return;
49        }
50        if (isset($_SESSION[DOKU_COOKIE]['oauth-done']['do']) || !empty($_SESSION[DOKU_COOKIE]['oauth-done']['rev'])){
51            $this->restoreSessionEnvironment();
52            return;
53        }
54        $this->updateGroups();
55        $this->startOAuthLogin();
56    }
57
58    public function updateGroups() {
59        global $AUTH_ACL, $ID;
60        $showMessage = false;
61        $updateACL = false;
62        foreach ($AUTH_ACL as $index => $value) { //Search
63            $acl = preg_split("/\s/", $value);
64            if ($this->startsWith($acl[1], "@eve%2d")) {
65                $showMessage = true;
66                if ($acl[2] != 0) {
67                    $updateACL = true;
68                }
69            }
70        }
71        if ($updateACL) {
72            $apa = plugin_load('admin', 'acl');
73            foreach ($AUTH_ACL as $index => $value) {
74                $acl = preg_split("/\s/", $value);
75                if ($this->startsWith($acl[1], "@eve%2d") && $acl[2] != 0) {
76                    if (method_exists($apa, "addOrUpdateACL")) {
77                        $apa->addOrUpdateACL($acl[0], rawurldecode($acl[1]), 0); //Scope, User/Group
78                    } else { //Greebo and bellow
79                        $apa->_acl_del($acl[0], rawurldecode($acl[1])); // first make sure we won't end up with 2 lines matching this user and scope.
80                        $apa->_acl_add($acl[0], rawurldecode($acl[1]), 0); //Scope, User/Group
81                    }
82                    $AUTH_ACL[$index] = $acl[0] . ' ' . $acl[1] . ' ' . '0';
83                }
84            }
85        }
86        if ($showMessage) {
87            msg('<b>EVESSO:</b><br/>'
88                    . 'The naming of eve groups was changed in this version of EVESSO.<br />'
89                    . 'Access for all the deprecated groups have been set to <code>None</code>.<br />'
90                    . 'See the <a href="https://github.com/GoldenGnu/dokuwiki-plugin-evesso#updating">readme</a> for the details on how the naming have changed.<br />'
91                    . 'Update your <a href="' . wl($ID, array('do' => 'admin', 'page' => 'acl'), true, '&') .  '">ACL</a> settings to restore access.<br />'
92                    . 'This message will remain until the deprecated groups are removed from ACL.<br />'
93                    , 2, '', '', MSG_ADMINS_ONLY);
94        }
95    }
96
97    private function startOAuthLogin() {
98        global $INPUT, $ID;
99
100        /** @var helper_plugin_evesso $hlp */
101        $hlp         = plugin_load('helper', 'evesso');
102        $servicename = $INPUT->str('evessologin');
103        $service     = $hlp->loadService($servicename);
104        if(is_null($service)) return;
105
106        // remember service in session
107        session_start();
108        $_SESSION[DOKU_COOKIE]['oauth-inprogress']['service'] = $servicename;
109        $_SESSION[DOKU_COOKIE]['oauth-inprogress']['id']      = $ID;
110        session_write_close();
111
112        $service->login();
113    }
114
115    private function restoreSessionEnvironment() {
116        global $INPUT, $ACT, $TEXT, $PRE, $SUF, $SUM, $RANGE, $DATE_AT, $REV;
117        $ACT = $_SESSION[DOKU_COOKIE]['oauth-done']['do'];
118        $_REQUEST = $_SESSION[DOKU_COOKIE]['oauth-done']['$_REQUEST'];
119
120        $REV   = $INPUT->int('rev');
121        $DATE_AT = $INPUT->str('at');
122        $RANGE = $INPUT->str('range');
123        if($INPUT->post->has('wikitext')) {
124            $TEXT = cleanText($INPUT->post->str('wikitext'));
125        }
126        $PRE = cleanText(substr($INPUT->post->str('prefix'), 0, -1));
127        $SUF = cleanText($INPUT->post->str('suffix'));
128        $SUM = $INPUT->post->str('summary');
129
130        unset($_SESSION[DOKU_COOKIE]['oauth-done']);
131    }
132
133    /**
134     * Save groups for all the services a user has enabled
135     *
136     * @param Doku_Event $event  event object by reference
137     * @param mixed      $param  [the parameters passed as fifth argument to register_hook() when this
138     *                           handler was registered]
139     * @return void
140     */
141    public function handle_usermod(Doku_Event &$event, $param) {
142        global $ACT;
143        global $USERINFO;
144        global $auth;
145        global $INPUT;
146
147        if($event->data['type'] != 'modify') return;
148        if($ACT != 'profile') return;
149
150        // we want to modify the user's groups
151        $groups = $USERINFO['grps']; //current groups
152        if(isset($event->data['params'][1]['grps'])) {
153            // something already defined new groups
154            $groups = $event->data['params'][1]['grps'];
155        }
156
157        /** @var helper_plugin_evesso $hlp */
158        $hlp = plugin_load('helper', 'evesso');
159
160        // get enabled and configured services
161        $enabled  = $INPUT->arr('oauth_group');
162
163        // add all enabled services as group, remove all disabled services
164        if(isset($enabled['EveOnline'])) { //Add EveOnline
165            $groups[] = 'eveonline';
166        } else { //Remove EveOnline
167            $idx = array_search('eveonline', $groups);
168            if($idx !== false) unset($groups[$idx]);
169        }
170        $groups = array_unique($groups);
171
172        // add new group array to event data
173        $event->data['params'][1]['grps'] = $groups;
174
175    }
176
177    /**
178     * Add service selection to user profile
179     *
180     * @param Doku_Event $event  event object by reference
181     * @param mixed      $param  [the parameters passed as fifth argument to register_hook() when this
182     *                           handler was registered]
183     * @return void
184     */
185    public function handle_profileform(Doku_Event &$event, $param) {
186        global $USERINFO;
187        /** @var auth_plugin_authplain $auth */
188        global $auth;
189
190        /** @var helper_plugin_evesso $hlp */
191        $hlp = plugin_load('helper', 'evesso');
192
193        /** @var Doku_Form $form */
194        $form =& $event->data;
195        if (!in_array('eveonline', $USERINFO['grps'])) {
196            return; //Continue as normal
197        }
198        if(is_a($form, \dokuwiki\Form\Form::class)) { //Igor and later
199            //Disable fullname field
200            $pos = $form->findPositionByAttribute('name', 'fullname');
201            $form->getElementAt($pos)->attr('disabled', 'disabled');
202            //Remove all fields except username and fullname
203            $start = $form->findPositionByAttribute('name', 'fullname') + 1;
204            $done = 0;
205            while ($form->elementCount() > $start && $done < 11) {
206                $form->removeElement($start);
207                $done++;
208            }
209            $pos = $form->elementCount();
210            //Add corporation, alliance, faction fields
211            foreach ($USERINFO['grps'] as $group) {
212                if ($this->startsWith($group, helper_plugin_evesso::CORPORATION_PREFIX)) {
213                    $corp = $this->replaceFirst($group, helper_plugin_evesso::CORPORATION_PREFIX);
214                }
215                if ($this->startsWith($group, helper_plugin_evesso::ALLIANCE_PREFIX)) {
216                    $alliance = $this->replaceFirst($group, helper_plugin_evesso::ALLIANCE_PREFIX);
217                }
218                if ($this->startsWith($group, helper_plugin_evesso::FACTION_PREFIX)) {
219                    $faction = $this->replaceFirst($group, helper_plugin_evesso::FACTION_PREFIX);
220                }
221            }
222            if (isset($faction)) { //str_starts_with
223                $this->insertTextInput($form, $pos, $faction, $this->getLang('faction'));
224            }
225            if (isset($alliance)) { //str_starts_with
226                $this->insertTextInput($form, $pos, $alliance, $this->getLang('alliance'));
227            }
228            if (isset($corp)) { //str_starts_with
229                $this->insertTextInput($form, $pos, $corp, $this->getLang('corporation'));
230            }
231        } else { //Hogfather and earlier
232            //Remove all fields except username and fullname
233            array_splice($form->_content, 3);
234            //Disable fullname field
235            $form->getElementAt(3)['disabled'] = 'disabled';
236            //Add corporation, alliance, faction fields
237            $pos = count($form->_content);
238            foreach ($USERINFO['grps'] as $group) {
239                if ($this->startsWith($group, helper_plugin_evesso::CORPORATION_PREFIX)) {
240                    $corp = $this->replaceFirst($group, helper_plugin_evesso::CORPORATION_PREFIX);
241                }
242                if ($this->startsWith($group, helper_plugin_evesso::ALLIANCE_PREFIX)) {
243                    $alliance = $this->replaceFirst($group, helper_plugin_evesso::ALLIANCE_PREFIX);
244                }
245                if ($this->startsWith($group, helper_plugin_evesso::FACTION_PREFIX)) {
246                    $faction = $this->replaceFirst($group, helper_plugin_evesso::FACTION_PREFIX);
247                }
248            }
249            $form->insertElement($pos, form_closefieldset());
250            if (isset($faction)) { //str_starts_with
251                $form->insertElement($pos, form_makeTextField($faction, $faction, $this->getLang('faction'), '', 'block', array('disabled' => 'disabled', 'size' => '50')));
252            }
253            if (isset($alliance)) { //str_starts_with
254                $form->insertElement($pos, form_makeTextField($alliance, $alliance, $this->getLang('alliance'), '', 'block', array('disabled' => 'disabled', 'size' => '50')));
255            }
256            if (isset($corp)) { //str_starts_with
257                $form->insertElement($pos, form_makeTextField($corp, $corp, $this->getLang('corporation'), '', 'block', array('disabled' => 'disabled', 'size' => '50')));
258            }
259        }
260    }
261
262    private function startsWith($haystack, $needle) {
263        $length = strlen($needle);
264        return (substr($haystack, 0, $length) === $needle);
265    }
266
267    private function replaceFirst($haystack, $needle, $replace = '') {
268        $pos = strpos($haystack, $needle);
269        if ($pos !== false) {
270            $haystack = substr_replace($haystack, $replace, $pos, strlen($needle));
271        }
272        return $haystack;
273    }
274
275    private function insertTextInput($form, $pos, $value, $name) {
276        $textInput = $form->addTextInput($value, $name, $pos);
277        $textInput->attr('size', '50');
278        $textInput->attr('class', 'edit');
279        $textInput->attr('value', $value);
280        $textInput->attr('disabled', 'disabled');
281        $label = $textInput->getLabel();
282        $label->attr('class', 'block');
283        $form->addHTML('<br>', $pos);
284    }
285
286    private function insertElement($form, $pos, $out) {
287        if(is_a($form, \dokuwiki\Form\Form::class)) { //Igor and later
288            $form->addHtml($out, $pos);
289        } else { //Hogfather and earlier
290            $form->insertElement($pos, $out);
291        }
292    }
293
294    /**
295     * Add the oAuth login links
296     *
297     * @param Doku_Event $event  event object by reference
298     * @param mixed      $param  [the parameters passed as fifth argument to register_hook() when this
299     *                           handler was registered]
300     * @return void
301     */
302    public function handle_loginform(Doku_Event &$event, $param) {
303        /** @var helper_plugin_evesso $hlp */
304        $hlp = plugin_load('helper', 'evesso');
305        $service = $hlp->getService();
306
307        /** @var Doku_Form $form */
308        $form =& $event->data;
309        $html = '';
310
311        if ($hlp->isEveAuth()) {  //Set Html
312            if(is_a($form, \dokuwiki\Form\Form::class)) { //Igor and later
313                while ($form->elementCount() > 0) {
314                    $form->removeElement(0);
315                }
316                $pos  = $form->elementCount() - 3; //At the end
317            } else { //Hogfather and earlier
318                $form->_content = array();
319                $pos  =  0;
320            }
321            $html = $this->service_html($service);
322        }else{ //PlainAuth and EveAuth
323            $html .= $this->service_html($service);
324            if(is_a($form, \dokuwiki\Form\Form::class)) { //Igor and later
325                $pos  = $form->elementCount(); //At the end
326           } else { //Hogfather and earlier
327                $pos  =  count($form->_content);
328           }
329        }
330        if(is_a($form, \dokuwiki\Form\Form::class)) { //Igor and later
331            $form->addFieldsetOpen($this->getLang('loginwith'), ++$pos);
332            $form->addHtml($html, ++$pos);
333            $form->addFieldsetClose();
334        } else { //Hogfather and earlier
335            $form->insertElement(++$pos, form_openfieldset(array('_legend' => $this->getLang('loginwith'), 'class' => 'plugin_evesso')));
336            $form->insertElement(++$pos, $html);
337            $form->insertElement(++$pos, form_closefieldset());
338        }
339    }
340
341    function service_html($service) {
342        global $ID;
343        $html = '';
344        $html .= '<a href="' . wl($ID, array('evessologin' => $service)) . '" class="plugin_evesso_' . $service . '">';
345        if ($this->getConf('login-button') == 'Button') {
346            $html .= '<div class="eve-sso-login-white-large"></div>';
347        } elseif ($this->getConf('login-button') == 'LargeLight') {
348            $html .= '<div class="eve-sso-login-white-large"></div>';
349        } elseif ($this->getConf('login-button') == 'LargeDark') {
350            $html .= '<div class="eve-sso-login-black-large"></div>';
351        } elseif ($this->getConf('login-button') == 'SmallLight') {
352            $html .= '<div class="eve-sso-login-white-small"></div>';
353        } elseif ($this->getConf('login-button') == 'SmallDark') {
354            $html .= '<div class="eve-sso-login-black-small"></div>';
355        } else {
356            $html .= $this->getLang('loginButton');
357        }
358        $html .= '</a> ';
359        return $html;
360    }
361
362    public function handle_dologin(Doku_Event &$event, $param) {
363        global $lang;
364        global $ID;
365
366        $hlp = plugin_load('helper', 'evesso');
367
368        if($event->data == 'logout' && $hlp->isEveAuth()) {
369            session_start();
370            $_SESSION[DOKU_COOKIE]['oauth-logout'] = 'logout';
371            session_write_close();
372        }
373
374        if ($hlp->isAuthPlain()) return true;
375
376        if($event->data != 'login') return true;
377
378        if($hlp->isEveAuthDirect()) {
379            $lang['btn_login'] = $this->getLang('loginButton');
380            $url = wl($ID, array('evessologin' => $hlp->getService()), true, '&');
381            send_redirect($url);
382        }
383    }
384
385}
386// vim:ts=4:sw=4:et: