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