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