xref: /plugin/oauth/auth.php (revision 523e6571566be9a3cbf193d70e331f310c312312)
1<?php
2/**
3 * DokuWiki Plugin oauth (Auth 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 auth_plugin_oauth extends auth_plugin_authplain {
13
14    /**
15     * Constructor
16     *
17     * Sets capabilities.
18     */
19    public function __construct() {
20        parent::__construct();
21
22        $this->cando['external'] = true;
23    }
24
25    /**
26     * Handle the login
27     *
28     * This either trusts the session data (if any), processes the second oAuth step or simply
29     * executes a normal plugin against local users.
30     *
31     * @param string $user
32     * @param string $pass
33     * @param bool   $sticky
34     * @return bool
35     */
36    function trustExternal($user, $pass, $sticky = false) {
37        global $USERINFO;
38
39        // check session for existing oAuth login data
40        $session = $_SESSION[DOKU_COOKIE]['auth'];
41        if(isset($session['oauth'])) {
42            $servicename = $session['oauth'];
43            // check if session data is still considered valid
44            if ($this->isSessionValid($session)) {
45                $_SERVER['REMOTE_USER'] = $session['user'];
46                $USERINFO               = $session['info'];
47                return true;
48            }
49        }
50
51        $existingLoginProcess = false;
52        // are we in login progress?
53        if(isset($_SESSION[DOKU_COOKIE]['oauth-inprogress'])) {
54            $servicename = $_SESSION[DOKU_COOKIE]['oauth-inprogress']['service'];
55            $page        = $_SESSION[DOKU_COOKIE]['oauth-inprogress']['id'];
56
57            unset($_SESSION[DOKU_COOKIE]['oauth-inprogress']);
58            $existingLoginProcess = true;
59        }
60
61        // either we're in oauth login or a previous log needs to be rechecked
62        if(isset($servicename)) {
63            /** @var helper_plugin_oauth $hlp */
64            $hlp     = plugin_load('helper', 'oauth');
65
66            /** @var OAuth\Plugin\AbstractAdapter $service */
67            $service = $hlp->loadService($servicename);
68            if(is_null($service)) {
69                $this->cleanLogout();
70                return false;
71            }
72
73            if($service->checkToken()) {
74                $ok = $this->processLogin($sticky, $service, $servicename, $page);
75                if (!$ok) {
76                    $this->cleanLogout();
77                    return false;
78                }
79                return true;
80            } else {
81                if ($existingLoginProcess) {
82                    msg($this->getLang('oauth login failed'),0);
83                    $this->cleanLogout();
84                    return false;
85                } else {
86                    // first time here
87                    $this->relogin($servicename);
88                }
89            }
90
91            $this->cleanLogout();
92            return false; // something went wrong during oAuth login
93        } elseif (isset($_COOKIE[DOKU_COOKIE])) {
94            global $INPUT;
95            //try cookie
96            list($cookieuser, $cookiesticky, $auth, $servicename) = explode('|', $_COOKIE[DOKU_COOKIE]);
97            $cookieuser = base64_decode($cookieuser, true);
98            $auth = base64_decode($auth, true);
99            $servicename = base64_decode($servicename, true);
100            if ($auth === 'oauth') {
101                $this->relogin($servicename);
102            }
103        }
104
105        // do the "normal" plain auth login via form
106        return auth_login($user, $pass, $sticky);
107    }
108
109    private function cleanLogout() {
110        unset($_SESSION[DOKU_COOKIE]['oauth-done']);
111        unset($_SESSION[DOKU_COOKIE]['auth']);
112        $this->setUserCookie('',true,'',-60);
113    }
114
115    /**
116     * @param array $session cookie auth session
117     *
118     * @return bool
119     */
120    protected function isSessionValid ($session) {
121        /** @var helper_plugin_oauth $hlp */
122        $hlp     = plugin_load('helper', 'oauth');
123        if ($hlp->validBrowserID($session)) {
124            if (!$hlp->isSessionTimedOut($session)) {
125                return true;
126            } elseif (!($hlp->isGETRequest() && $hlp->isDokuPHP())) {
127                // only force a recheck on a timed-out session during a GET request on the main script doku.php
128                return true;
129            }
130        }
131        return false;
132    }
133
134    protected function relogin($servicename) {
135        global $INPUT;
136
137        /** @var helper_plugin_oauth $hlp */
138        $hlp     = plugin_load('helper', 'oauth');
139        $service     = $hlp->loadService($servicename);
140        if(is_null($service)) return false;
141
142        // remember service in session
143        session_start();
144        $_SESSION[DOKU_COOKIE]['oauth-inprogress']['service'] = $servicename;
145        $_SESSION[DOKU_COOKIE]['oauth-inprogress']['id']      = $INPUT->str('id');
146
147        $_SESSION[DOKU_COOKIE]['oauth-done']['$_REQUEST'] = $_REQUEST;
148
149        if (is_array($INPUT->post->param('do'))) {
150            $doPost = key($INPUT->post->arr('do'));
151        } else {
152            $doPost = $INPUT->post->str('do');
153        }
154        $doGet = $INPUT->get->str('do');
155        if (!empty($doPost)) {
156            $_SESSION[DOKU_COOKIE]['oauth-done']['do'] = $doPost;
157        } elseif (!empty($doGet)) {
158            $_SESSION[DOKU_COOKIE]['oauth-done']['do'] = $doGet;
159        }
160
161        session_write_close();
162
163        $service->login();
164    }
165
166    /**
167     * @param array  $data
168     * @param string $service
169     */
170    protected function setUserSession($data, $service) {
171        global $USERINFO;
172        global $conf;
173
174        // set up groups
175        if(!is_array($data['grps'])) {
176            $data['grps'] = array();
177        }
178        $data['grps'][] = $this->cleanGroup($service);
179        $data['grps']   = array_unique($data['grps']);
180
181        $USERINFO                               = $data;
182        $_SERVER['REMOTE_USER']                 = $data['user'];
183        $_SESSION[DOKU_COOKIE]['auth']['user']  = $data['user'];
184        $_SESSION[DOKU_COOKIE]['auth']['pass']  = $data['pass'];
185        $_SESSION[DOKU_COOKIE]['auth']['info']  = $USERINFO;
186        $_SESSION[DOKU_COOKIE]['auth']['buid']  = auth_browseruid();
187        $_SESSION[DOKU_COOKIE]['auth']['time']  = time();
188        $_SESSION[DOKU_COOKIE]['auth']['oauth'] = $service;
189    }
190
191    /**
192     * Unset additional stuff in session on logout
193     */
194    public function logOff() {
195        parent::logOff();
196
197        if(isset($_SESSION[DOKU_COOKIE]['auth']['buid'])) {
198            unset($_SESSION[DOKU_COOKIE]['auth']['buid']);
199        }
200        if(isset($_SESSION[DOKU_COOKIE]['auth']['time'])) {
201            unset($_SESSION[DOKU_COOKIE]['auth']['time']);
202        }
203        if(isset($_SESSION[DOKU_COOKIE]['auth']['oauth'])) {
204            unset($_SESSION[DOKU_COOKIE]['auth']['oauth']);
205        }
206    }
207
208    /**
209     * Find a user by his email address
210     *
211     * @param $mail
212     * @return bool|string
213     */
214    protected function getUserByEmail($mail) {
215        if($this->users === null) $this->_loadUserData();
216        $mail = strtolower($mail);
217
218        foreach($this->users as $user => $uinfo) {
219            if(strtolower($uinfo['mail']) == $mail) return $user;
220        }
221
222        return false;
223    }
224
225    /**
226     * Enhance function to check against duplicate emails
227     *
228     * @param string $user
229     * @param string $pwd
230     * @param string $name
231     * @param string $mail
232     * @param null   $grps
233     * @return bool|null|string
234     */
235    public function createUser($user, $pwd, $name, $mail, $grps = null) {
236        if($this->getUserByEmail($mail)) {
237            msg($this->getLang('emailduplicate'), -1);
238            return false;
239        }
240
241        return parent::createUser($user, $pwd, $name, $mail, $grps);
242    }
243
244    /**
245     * Enhance function to check aainst duplicate emails
246     *
247     * @param string $user
248     * @param array  $changes
249     * @return bool
250     */
251    public function modifyUser($user, $changes) {
252        global $conf;
253
254        if(isset($changes['mail'])) {
255            $found = $this->getUserByEmail($changes['mail']);
256            if($found != $user) {
257                msg($this->getLang('emailduplicate'), -1);
258                return false;
259            }
260        }
261
262        $ok = parent::modifyUser($user, $changes);
263
264        // refresh session cache
265        touch($conf['cachedir'] . '/sessionpurge');
266
267        return $ok;
268    }
269
270    /**
271     * new user, create him - making sure the login is unique by adding a number if needed
272     *
273     * @param array $uinfo user info received from the oAuth service
274     * @param string $servicename
275     *
276     * @return bool
277     */
278    protected function addUser(&$uinfo, $servicename) {
279        global $conf;
280        $user = $uinfo['user'];
281        $count = '';
282        while($this->getUserData($user . $count)) {
283            if($count) {
284                $count++;
285            } else {
286                $count = 1;
287            }
288        }
289        $user = $user . $count;
290        $uinfo['user'] = $user;
291        $groups_on_creation = array();
292        $groups_on_creation[] = $conf['defaultgroup'];
293        $groups_on_creation[] = $this->cleanGroup($servicename); // add service as group
294        $uinfo['grps'] = array_merge((array) $uinfo['grps'], $groups_on_creation);
295
296        $ok = $this->triggerUserMod(
297            'create',
298            array($user, auth_pwgen($user), $uinfo['name'], $uinfo['mail'], $groups_on_creation,)
299        );
300        if(!$ok) {
301            return false;
302        }
303
304        // send notification about the new user
305        $subscription = new Subscription();
306        $subscription->send_register($user, $uinfo['name'], $uinfo['mail']);
307        return true;
308    }
309
310    /**
311     * process the user and update the $uinfo array
312     *
313     * @param $uinfo
314     * @param $servicename
315     *
316     * @return bool
317     */
318    protected function processUser(&$uinfo, $servicename) {
319        $uinfo['user'] = $this->cleanUser((string) $uinfo['user']);
320        if(!$uinfo['name']) $uinfo['name'] = $uinfo['user'];
321
322        if(!$uinfo['user'] || !$uinfo['mail']) {
323            msg("$servicename did not provide the needed user info. Can't log you in", -1);
324            return false;
325        }
326
327        // see if the user is known already
328        $user = $this->getUserByEmail($uinfo['mail']);
329        if($user) {
330            $sinfo = $this->getUserData($user);
331            // check if the user allowed access via this service
332            if(!in_array($this->cleanGroup($servicename), $sinfo['grps'])) {
333                msg(sprintf($this->getLang('authnotenabled'), $servicename), -1);
334                return false;
335            }
336            $uinfo['user'] = $user;
337            $uinfo['name'] = $sinfo['name'];
338            $uinfo['grps'] = array_merge((array) $uinfo['grps'], $sinfo['grps']);
339        } elseif(actionOK('register')) {
340            $ok = $this->addUser($uinfo, $servicename);
341            if(!$ok) {
342                msg('something went wrong creating your user account. please try again later.', -1);
343                return false;
344            }
345        } else {
346            msg($this->getLang('addUser not possible'), -1);
347            return false;
348        }
349        return true;
350    }
351
352    /**
353     * @param string $user
354     * @param bool   $sticky
355     * @param string $servicename
356     * @param int    $validityPeriodInSeconds optional, per default 1 Year
357     */
358    private function setUserCookie($user, $sticky, $servicename, $validityPeriodInSeconds = 31536000) {
359        $cookie = base64_encode($user).'|'.((int) $sticky).'|'.base64_encode('oauth').'|'.base64_encode($servicename);
360        $cookieDir = empty($conf['cookiedir']) ? DOKU_REL : $conf['cookiedir'];
361        $time      = $sticky ? (time() + $validityPeriodInSeconds) : 0;
362        setcookie(DOKU_COOKIE,$cookie, $time, $cookieDir, '',($conf['securecookie'] && is_ssl()), true);
363    }
364
365    /**
366     * @param                              $sticky
367     * @param OAuth\Plugin\AbstractAdapter $service
368     * @param string                       $servicename
369     * @param string                       $page
370     *
371     * @return bool
372     */
373    protected function processLogin($sticky, $service, $servicename, $page) {
374        $uinfo = $service->getUser();
375        $ok = $this->processUser($uinfo, $servicename);
376        if(!$ok) {
377            return false;
378        }
379        $this->setUserSession($uinfo, $servicename);
380        $this->setUserCookie($uinfo['user'], $sticky, $servicename);
381        if(isset($page)) {
382            send_redirect(wl($page));
383        }
384        return true;
385    }
386
387}
388
389// vim:ts=4:sw=4:et:
390