xref: /plugin/oauth/auth.php (revision 827232fc292f54ed1d01651a2db35d8dbfde1238)
180852c15SAndreas Gohr<?php
280852c15SAndreas Gohr/**
380852c15SAndreas Gohr * DokuWiki Plugin oauth (Auth Component)
480852c15SAndreas Gohr *
580852c15SAndreas Gohr * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
680852c15SAndreas Gohr * @author  Andreas Gohr <andi@splitbrain.org>
780852c15SAndreas Gohr */
880852c15SAndreas Gohr
980852c15SAndreas Gohr// must be run within Dokuwiki
1080852c15SAndreas Gohrif(!defined('DOKU_INC')) die();
1180852c15SAndreas Gohr
12f10e09e2SAndreas Gohrclass auth_plugin_oauth extends auth_plugin_authplain {
1380852c15SAndreas Gohr
14f866280eSAndreas Gohr    /**
15f866280eSAndreas Gohr     * Constructor
16f866280eSAndreas Gohr     *
17f866280eSAndreas Gohr     * Sets capabilities.
18f866280eSAndreas Gohr     */
1980852c15SAndreas Gohr    public function __construct() {
20f10e09e2SAndreas Gohr        parent::__construct();
2180852c15SAndreas Gohr
22f10e09e2SAndreas Gohr        $this->cando['external'] = true;
2380852c15SAndreas Gohr    }
2480852c15SAndreas Gohr
25f866280eSAndreas Gohr    /**
26f866280eSAndreas Gohr     * Handle the login
27f866280eSAndreas Gohr     *
28f866280eSAndreas Gohr     * This either trusts the session data (if any), processes the second oAuth step or simply
29f866280eSAndreas Gohr     * executes a normal plugin against local users.
30f866280eSAndreas Gohr     *
31f866280eSAndreas Gohr     * @param string $user
32f866280eSAndreas Gohr     * @param string $pass
33f866280eSAndreas Gohr     * @param bool   $sticky
34f866280eSAndreas Gohr     * @return bool
35f866280eSAndreas Gohr     */
36f10e09e2SAndreas Gohr    function trustExternal($user, $pass, $sticky = false) {
37a7a8f46aSAndreas Gohr        global $conf;
38a7a8f46aSAndreas Gohr        global $USERINFO;
3980852c15SAndreas Gohr
402e94f0b8SAndreas Gohr        // are we in login progress?
412e94f0b8SAndreas Gohr        if(isset($_SESSION[DOKU_COOKIE]['oauth-inprogress'])) {
422e94f0b8SAndreas Gohr            $servicename = $_SESSION[DOKU_COOKIE]['oauth-inprogress']['service'];
432e94f0b8SAndreas Gohr            $page        = $_SESSION[DOKU_COOKIE]['oauth-inprogress']['id'];
442e94f0b8SAndreas Gohr
452e94f0b8SAndreas Gohr            unset($_SESSION[DOKU_COOKIE]['oauth-inprogress']);
462e94f0b8SAndreas Gohr        }
4780852c15SAndreas Gohr
48a7a8f46aSAndreas Gohr        // check session for existing oAuth login data
49a7a8f46aSAndreas Gohr        $session = $_SESSION[DOKU_COOKIE]['auth'];
502e94f0b8SAndreas Gohr        if(!isset($servicename) && isset($session['oauth'])) {
51a7a8f46aSAndreas Gohr            $servicename = $session['oauth'];
52a7a8f46aSAndreas Gohr            // check if session data is still considered valid
53f2e164b0SMichael Große            if ($this->isSessionValid($session)) {
54a7a8f46aSAndreas Gohr                $_SERVER['REMOTE_USER'] = $session['user'];
55a7a8f46aSAndreas Gohr                $USERINFO               = $session['info'];
5680852c15SAndreas Gohr                return true;
57f10e09e2SAndreas Gohr            }
5880852c15SAndreas Gohr        }
5980852c15SAndreas Gohr
60a7a8f46aSAndreas Gohr        // either we're in oauth login or a previous log needs to be rechecked
612e94f0b8SAndreas Gohr        if(isset($servicename)) {
62a7a8f46aSAndreas Gohr            /** @var helper_plugin_oauth $hlp */
63a7a8f46aSAndreas Gohr            $hlp     = plugin_load('helper', 'oauth');
64*827232fcSMichael Große
65*827232fcSMichael Große            /** @var OAuth\Plugin\AbstractAdapter $service */
66a7a8f46aSAndreas Gohr            $service = $hlp->loadService($servicename);
67a7a8f46aSAndreas Gohr            if(is_null($service)) return false;
68a7a8f46aSAndreas Gohr
69a7a8f46aSAndreas Gohr            if($service->checkToken()) {
70*827232fcSMichael Große                return $this->processLogin($sticky, $service, $servicename, $page);
71213f4618SMichael Große            } else {
72213f4618SMichael Große                $this->relogin($servicename);
73a7a8f46aSAndreas Gohr            }
74a7a8f46aSAndreas Gohr
75936b9c9cSMichael Große            unset($_SESSION[DOKU_COOKIE]['auth']);
76a7a8f46aSAndreas Gohr            return false; // something went wrong during oAuth login
77213f4618SMichael Große        } elseif (isset($_COOKIE[DOKU_COOKIE])) {
78213f4618SMichael Große            global $INPUT;
79213f4618SMichael Große            //try cookie
80213f4618SMichael Große            list($cookieuser, $cookiesticky, $auth, $servicename) = explode('|', $_COOKIE[DOKU_COOKIE]);
81213f4618SMichael Große            $cookieuser = base64_decode($cookieuser, true);
82213f4618SMichael Große            $auth = base64_decode($auth, true);
83213f4618SMichael Große            $servicename = base64_decode($servicename, true);
84213f4618SMichael Große            if ($auth === 'oauth') {
85213f4618SMichael Große                $this->relogin($servicename);
86213f4618SMichael Große            }
8780852c15SAndreas Gohr        }
8880852c15SAndreas Gohr
89a7a8f46aSAndreas Gohr        // do the "normal" plain auth login via form
90a7a8f46aSAndreas Gohr        return auth_login($user, $pass, $sticky);
91a7a8f46aSAndreas Gohr    }
9280852c15SAndreas Gohr
93f2e164b0SMichael Große    /**
94f2e164b0SMichael Große     * @param array $session cookie auth session
95f2e164b0SMichael Große     *
96f2e164b0SMichael Große     * @return bool
97f2e164b0SMichael Große     */
98f2e164b0SMichael Große    protected function isSessionValid ($session) {
99f2e164b0SMichael Große        /** @var helper_plugin_oauth $hlp */
100f2e164b0SMichael Große        $hlp     = plugin_load('helper', 'oauth');
101f2e164b0SMichael Große        if ($hlp->validBrowserID($session)) {
102f2e164b0SMichael Große            if (!$hlp->isSessionTimedOut($session)) {
103f2e164b0SMichael Große                return true;
104f2e164b0SMichael Große            } elseif (!($hlp->isGETRequest() && $hlp->isDokuPHP())) {
105f2e164b0SMichael Große                // only force a recheck on a timed-out session during a GET request on the main script doku.php
106f2e164b0SMichael Große                return true;
107f2e164b0SMichael Große            }
108f2e164b0SMichael Große        }
109f2e164b0SMichael Große        return false;
110f2e164b0SMichael Große    }
111f2e164b0SMichael Große
112213f4618SMichael Große    protected function relogin($servicename) {
113213f4618SMichael Große        global $INPUT;
114213f4618SMichael Große
115213f4618SMichael Große        /** @var helper_plugin_oauth $hlp */
116213f4618SMichael Große        $hlp     = plugin_load('helper', 'oauth');
117213f4618SMichael Große        $service     = $hlp->loadService($servicename);
118213f4618SMichael Große        if(is_null($service)) return false;
119213f4618SMichael Große
120213f4618SMichael Große        // remember service in session
121213f4618SMichael Große        session_start();
122213f4618SMichael Große        $_SESSION[DOKU_COOKIE]['oauth-inprogress']['service'] = $servicename;
123213f4618SMichael Große        $_SESSION[DOKU_COOKIE]['oauth-inprogress']['id']      = $INPUT->str('id');
124213f4618SMichael Große
12509623faaSMichael Große        $_SESSION[DOKU_COOKIE]['oauth-done']['$_REQUEST'] = $_REQUEST;
126213f4618SMichael Große
127213f4618SMichael Große        if (is_array($INPUT->post->param('do'))) {
128213f4618SMichael Große            $doPost = key($INPUT->post->arr('do'));
129213f4618SMichael Große        } else {
130213f4618SMichael Große            $doPost = $INPUT->post->str('do');
131213f4618SMichael Große        }
132213f4618SMichael Große        $doGet = $INPUT->get->str('do');
133213f4618SMichael Große        if (!empty($doPost)) {
134213f4618SMichael Große            $_SESSION[DOKU_COOKIE]['oauth-done']['do'] = $doPost;
135213f4618SMichael Große        } elseif (!empty($doGet)) {
136213f4618SMichael Große            $_SESSION[DOKU_COOKIE]['oauth-done']['do'] = $doGet;
137213f4618SMichael Große        }
138213f4618SMichael Große
139213f4618SMichael Große        session_write_close();
140213f4618SMichael Große
141213f4618SMichael Große        $service->login();
142213f4618SMichael Große    }
143213f4618SMichael Große
144a7a8f46aSAndreas Gohr    /**
145a7a8f46aSAndreas Gohr     * @param array  $data
146a7a8f46aSAndreas Gohr     * @param string $service
147a7a8f46aSAndreas Gohr     */
148a7a8f46aSAndreas Gohr    protected function setUserSession($data, $service) {
149a7a8f46aSAndreas Gohr        global $USERINFO;
150a7a8f46aSAndreas Gohr        global $conf;
151a7a8f46aSAndreas Gohr
152a7a8f46aSAndreas Gohr        // set up groups
153a7a8f46aSAndreas Gohr        if(!is_array($data['grps'])) {
154a7a8f46aSAndreas Gohr            $data['grps'] = array();
155a7a8f46aSAndreas Gohr        }
156a7a8f46aSAndreas Gohr        $data['grps'][] = $this->cleanGroup($service);
157f866280eSAndreas Gohr        $data['grps']   = array_unique($data['grps']);
15880852c15SAndreas Gohr
159f10e09e2SAndreas Gohr        $USERINFO                               = $data;
160f10e09e2SAndreas Gohr        $_SERVER['REMOTE_USER']                 = $data['user'];
161f10e09e2SAndreas Gohr        $_SESSION[DOKU_COOKIE]['auth']['user']  = $data['user'];
162f10e09e2SAndreas Gohr        $_SESSION[DOKU_COOKIE]['auth']['pass']  = $data['pass'];
163f10e09e2SAndreas Gohr        $_SESSION[DOKU_COOKIE]['auth']['info']  = $USERINFO;
164a7a8f46aSAndreas Gohr        $_SESSION[DOKU_COOKIE]['auth']['buid']  = auth_browseruid();
165a7a8f46aSAndreas Gohr        $_SESSION[DOKU_COOKIE]['auth']['time']  = time();
166a7a8f46aSAndreas Gohr        $_SESSION[DOKU_COOKIE]['auth']['oauth'] = $service;
16780852c15SAndreas Gohr    }
16880852c15SAndreas Gohr
169f866280eSAndreas Gohr    /**
170e32c3607SAndreas Gohr     * Unset additional stuff in session on logout
171e32c3607SAndreas Gohr     */
172e32c3607SAndreas Gohr    public function logOff() {
173e32c3607SAndreas Gohr        parent::logOff();
174e32c3607SAndreas Gohr
175e32c3607SAndreas Gohr        if(isset($_SESSION[DOKU_COOKIE]['auth']['buid'])) {
176e32c3607SAndreas Gohr            unset($_SESSION[DOKU_COOKIE]['auth']['buid']);
177e32c3607SAndreas Gohr        }
178e32c3607SAndreas Gohr        if(isset($_SESSION[DOKU_COOKIE]['auth']['time'])) {
179e32c3607SAndreas Gohr            unset($_SESSION[DOKU_COOKIE]['auth']['time']);
180e32c3607SAndreas Gohr        }
181e32c3607SAndreas Gohr        if(isset($_SESSION[DOKU_COOKIE]['auth']['oauth'])) {
182e32c3607SAndreas Gohr            unset($_SESSION[DOKU_COOKIE]['auth']['oauth']);
183e32c3607SAndreas Gohr        }
184e32c3607SAndreas Gohr    }
185e32c3607SAndreas Gohr
186e32c3607SAndreas Gohr    /**
187f866280eSAndreas Gohr     * Find a user by his email address
188f866280eSAndreas Gohr     *
189f866280eSAndreas Gohr     * @param $mail
190f866280eSAndreas Gohr     * @return bool|string
191f866280eSAndreas Gohr     */
19238378fbbSAndreas Gohr    protected function getUserByEmail($mail) {
193f866280eSAndreas Gohr        if($this->users === null) $this->_loadUserData();
19438378fbbSAndreas Gohr        $mail = strtolower($mail);
195f866280eSAndreas Gohr
196f866280eSAndreas Gohr        foreach($this->users as $user => $uinfo) {
197f866280eSAndreas Gohr            if(strtolower($uinfo['mail']) == $mail) return $user;
19838378fbbSAndreas Gohr        }
19938378fbbSAndreas Gohr
200f866280eSAndreas Gohr        return false;
201f866280eSAndreas Gohr    }
20238378fbbSAndreas Gohr
203f866280eSAndreas Gohr    /**
204f07c7607SMichael Große     * Enhance function to check against duplicate emails
205f866280eSAndreas Gohr     *
206f866280eSAndreas Gohr     * @param string $user
207f866280eSAndreas Gohr     * @param string $pwd
208f866280eSAndreas Gohr     * @param string $name
209f866280eSAndreas Gohr     * @param string $mail
210f866280eSAndreas Gohr     * @param null   $grps
211f866280eSAndreas Gohr     * @return bool|null|string
212f866280eSAndreas Gohr     */
213f866280eSAndreas Gohr    public function createUser($user, $pwd, $name, $mail, $grps = null) {
214f866280eSAndreas Gohr        if($this->getUserByEmail($mail)) {
215f866280eSAndreas Gohr            msg($this->getLang('emailduplicate'), -1);
216f866280eSAndreas Gohr            return false;
217f866280eSAndreas Gohr        }
218f866280eSAndreas Gohr
21938378fbbSAndreas Gohr        return parent::createUser($user, $pwd, $name, $mail, $grps);
22038378fbbSAndreas Gohr    }
22138378fbbSAndreas Gohr
222f866280eSAndreas Gohr    /**
223f866280eSAndreas Gohr     * Enhance function to check aainst duplicate emails
224f866280eSAndreas Gohr     *
225f866280eSAndreas Gohr     * @param string $user
226f866280eSAndreas Gohr     * @param array  $changes
227f866280eSAndreas Gohr     * @return bool
228f866280eSAndreas Gohr     */
22938378fbbSAndreas Gohr    public function modifyUser($user, $changes) {
2303c0138dbSAndreas Gohr        global $conf;
2313c0138dbSAndreas Gohr
2323c0138dbSAndreas Gohr        if(isset($changes['mail'])) {
2333c0138dbSAndreas Gohr            $found = $this->getUserByEmail($changes['mail']);
2343c0138dbSAndreas Gohr            if($found != $user) {
235f866280eSAndreas Gohr                msg($this->getLang('emailduplicate'), -1);
236f866280eSAndreas Gohr                return false;
237f866280eSAndreas Gohr            }
2383c0138dbSAndreas Gohr        }
23938378fbbSAndreas Gohr
2403c0138dbSAndreas Gohr        $ok = parent::modifyUser($user, $changes);
2413c0138dbSAndreas Gohr
2423c0138dbSAndreas Gohr        // refresh session cache
2433c0138dbSAndreas Gohr        touch($conf['cachedir'] . '/sessionpurge');
2443c0138dbSAndreas Gohr
2453c0138dbSAndreas Gohr        return $ok;
24638378fbbSAndreas Gohr    }
24738378fbbSAndreas Gohr
248f07c7607SMichael Große    /**
249f07c7607SMichael Große     * new user, create him - making sure the login is unique by adding a number if needed
250f07c7607SMichael Große     *
2519928f5efSMichael Große     * @param array $uinfo user info received from the oAuth service
2529928f5efSMichael Große     * @param string $servicename
253f07c7607SMichael Große     *
254f07c7607SMichael Große     * @return bool
255f07c7607SMichael Große     */
2569928f5efSMichael Große    protected function addUser(&$uinfo, $servicename) {
257f07c7607SMichael Große        global $conf;
258f07c7607SMichael Große        $user = $uinfo['user'];
259f07c7607SMichael Große        $count = '';
260f07c7607SMichael Große        while($this->getUserData($user . $count)) {
261f07c7607SMichael Große            if($count) {
262f07c7607SMichael Große                $count++;
263f07c7607SMichael Große            } else {
264f07c7607SMichael Große                $count = 1;
265f07c7607SMichael Große            }
266f07c7607SMichael Große        }
267f07c7607SMichael Große        $user = $user . $count;
268f07c7607SMichael Große        $uinfo['user'] = $user;
269f07c7607SMichael Große        $groups_on_creation = array();
270f07c7607SMichael Große        $groups_on_creation[] = $conf['defaultgroup'];
271f07c7607SMichael Große        $groups_on_creation[] = $this->cleanGroup($servicename); // add service as group
272f07c7607SMichael Große        $uinfo['grps'] = array_merge((array) $uinfo['grps'], $groups_on_creation);
273f07c7607SMichael Große
274f07c7607SMichael Große        $ok = $this->triggerUserMod(
275f07c7607SMichael Große            'create',
276f07c7607SMichael Große            array($user, auth_pwgen($user), $uinfo['name'], $uinfo['mail'], $groups_on_creation,)
277f07c7607SMichael Große        );
278f07c7607SMichael Große        if(!$ok) {
279f07c7607SMichael Große            return false;
280f07c7607SMichael Große        }
281f07c7607SMichael Große
282f07c7607SMichael Große        // send notification about the new user
283f07c7607SMichael Große        $subscription = new Subscription();
284f07c7607SMichael Große        $subscription->send_register($user, $uinfo['name'], $uinfo['mail']);
285f07c7607SMichael Große        return true;
286f07c7607SMichael Große    }
287f07c7607SMichael Große
2889928f5efSMichael Große    /**
2899928f5efSMichael Große     * process the user and update the $uinfo array
2909928f5efSMichael Große     *
2919928f5efSMichael Große     * @param $uinfo
2929928f5efSMichael Große     * @param $servicename
2939928f5efSMichael Große     *
2949928f5efSMichael Große     * @return bool
2959928f5efSMichael Große     */
2969928f5efSMichael Große    protected function processUser(&$uinfo, $servicename) {
2979928f5efSMichael Große        $uinfo['user'] = $this->cleanUser((string) $uinfo['user']);
2989928f5efSMichael Große        if(!$uinfo['name']) $uinfo['name'] = $uinfo['user'];
2999928f5efSMichael Große
3009928f5efSMichael Große        if(!$uinfo['user'] || !$uinfo['mail']) {
3019928f5efSMichael Große            msg("$servicename did not provide the needed user info. Can't log you in", -1);
3029928f5efSMichael Große            return false;
3039928f5efSMichael Große        }
3049928f5efSMichael Große
3059928f5efSMichael Große        // see if the user is known already
3069928f5efSMichael Große        $user = $this->getUserByEmail($uinfo['mail']);
3079928f5efSMichael Große        if($user) {
3089928f5efSMichael Große            $sinfo = $this->getUserData($user);
3099928f5efSMichael Große            // check if the user allowed access via this service
3109928f5efSMichael Große            if(!in_array($this->cleanGroup($servicename), $sinfo['grps'])) {
3119928f5efSMichael Große                msg(sprintf($this->getLang('authnotenabled'), $servicename), -1);
3129928f5efSMichael Große                return false;
3139928f5efSMichael Große            }
3149928f5efSMichael Große            $uinfo['user'] = $user;
3159928f5efSMichael Große            $uinfo['name'] = $sinfo['name'];
3169928f5efSMichael Große            $uinfo['grps'] = array_merge((array) $uinfo['grps'], $sinfo['grps']);
3179928f5efSMichael Große        } elseif(actionOK('register')) {
3189928f5efSMichael Große            $ok = $this->addUser($uinfo, $servicename);
3199928f5efSMichael Große            if(!$ok) {
3209928f5efSMichael Große                msg('something went wrong creating your user account. please try again later.', -1);
3219928f5efSMichael Große                return false;
3229928f5efSMichael Große            }
3239928f5efSMichael Große        } else {
3249928f5efSMichael Große            msg($this->getLang('addUser not possible'), -1);
3259928f5efSMichael Große            return false;
3269928f5efSMichael Große        }
3279928f5efSMichael Große        return true;
3289928f5efSMichael Große    }
3299928f5efSMichael Große
3309928f5efSMichael Große    /**
3319928f5efSMichael Große     * @param string $user
3329928f5efSMichael Große     * @param string $sticky
3339928f5efSMichael Große     * @param string $servicename
3349928f5efSMichael Große     */
3359928f5efSMichael Große    private function setUserCookie($user, $sticky, $servicename) {
3369928f5efSMichael Große        $cookie = base64_encode($user).'|'.((int) $sticky).'|'.base64_encode('oauth').'|'.base64_encode($servicename);
3379928f5efSMichael Große        $cookieDir = empty($conf['cookiedir']) ? DOKU_REL : $conf['cookiedir'];
3389928f5efSMichael Große        $time      = $sticky ? (time() + 60 * 60 * 24 * 365) : 0;
3399928f5efSMichael Große        setcookie(DOKU_COOKIE,$cookie, $time, $cookieDir, '',($conf['securecookie'] && is_ssl()), true);
3409928f5efSMichael Große    }
3419928f5efSMichael Große
342*827232fcSMichael Große    /**
343*827232fcSMichael Große     * @param                              $sticky
344*827232fcSMichael Große     * @param OAuth\Plugin\AbstractAdapter $service
345*827232fcSMichael Große     * @param string                       $servicename
346*827232fcSMichael Große     * @param string                       $page
347*827232fcSMichael Große     *
348*827232fcSMichael Große     * @return bool
349*827232fcSMichael Große     */
350*827232fcSMichael Große    protected function processLogin($sticky, $service, $servicename, $page) {
351*827232fcSMichael Große        $uinfo = $service->getUser();
352*827232fcSMichael Große        $ok = $this->processUser($uinfo, $servicename);
353*827232fcSMichael Große        if(!$ok) {
354*827232fcSMichael Große            return false;
355*827232fcSMichael Große        }
356*827232fcSMichael Große        $this->setUserSession($uinfo, $servicename);
357*827232fcSMichael Große        $this->setUserCookie($uinfo['user'], $sticky, $servicename);
358*827232fcSMichael Große        if(isset($page)) {
359*827232fcSMichael Große            send_redirect(wl($page));
360*827232fcSMichael Große        }
361*827232fcSMichael Große        return true;
362*827232fcSMichael Große    }
363*827232fcSMichael Große
36480852c15SAndreas Gohr}
36580852c15SAndreas Gohr
36680852c15SAndreas Gohr// vim:ts=4:sw=4:et:
367