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