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