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