xref: /plugin/oauth/OAuthManager.php (revision c82ad624fa9f1d4a669d251578a03706028cf90a)
174b4d4a4SAndreas Gohr<?php
274b4d4a4SAndreas Gohr
374b4d4a4SAndreas Gohrnamespace dokuwiki\plugin\oauth;
474b4d4a4SAndreas Gohr
56d9a8a49SAndreas Gohr/**
66d9a8a49SAndreas Gohr * Implements the flow control for oAuth
76d9a8a49SAndreas Gohr */
874b4d4a4SAndreas Gohrclass OAuthManager
974b4d4a4SAndreas Gohr{
106d9a8a49SAndreas Gohr    // region main flow
1174b4d4a4SAndreas Gohr
1274b4d4a4SAndreas Gohr    /**
1331039e80SAndreas Gohr     * Explicitly starts the oauth flow by redirecting to IDP
1431039e80SAndreas Gohr     *
1504a78b87SAndreas Gohr     * @throws \OAuth\Common\Exception\Exception
1674b4d4a4SAndreas Gohr     */
1774b4d4a4SAndreas Gohr    public function startFlow($servicename)
1874b4d4a4SAndreas Gohr    {
1931039e80SAndreas Gohr        global $ID;
2031039e80SAndreas Gohr
2174b4d4a4SAndreas Gohr        // generate a new GUID to identify this user
2204a78b87SAndreas Gohr        try {
2374b4d4a4SAndreas Gohr            $guid = bin2hex(random_bytes(16));
2404a78b87SAndreas Gohr        } catch (\Exception $e) {
2504a78b87SAndreas Gohr            throw new \OAuth\Common\Exception\Exception($e->getMessage());
2604a78b87SAndreas Gohr        }
2774b4d4a4SAndreas Gohr
2874b4d4a4SAndreas Gohr        $session = Session::getInstance();
2931039e80SAndreas Gohr        $session->setLoginData($servicename, $guid, $ID);
3074b4d4a4SAndreas Gohr        $service = $this->loadService($servicename);
3174b4d4a4SAndreas Gohr        $service->initOAuthService($guid);
3274b4d4a4SAndreas Gohr        $service->login(); // redirects
3374b4d4a4SAndreas Gohr    }
3474b4d4a4SAndreas Gohr
3574b4d4a4SAndreas Gohr    /**
3631039e80SAndreas Gohr     * Continues the flow from various states
3731039e80SAndreas Gohr     *
3874b4d4a4SAndreas Gohr     * @return bool true if the login has been handled
3974b4d4a4SAndreas Gohr     * @throws Exception
4074b4d4a4SAndreas Gohr     * @throws \OAuth\Common\Exception\Exception
4174b4d4a4SAndreas Gohr     */
4274b4d4a4SAndreas Gohr    public function continueFlow()
4374b4d4a4SAndreas Gohr    {
4474b4d4a4SAndreas Gohr        return $this->loginByService() or
4574b4d4a4SAndreas Gohr            $this->loginBySession() or
4674b4d4a4SAndreas Gohr            $this->loginByCookie();
4774b4d4a4SAndreas Gohr    }
4874b4d4a4SAndreas Gohr
4974b4d4a4SAndreas Gohr    /**
506d9a8a49SAndreas Gohr     * Second step in a explicit login, validates the oauth code
516d9a8a49SAndreas Gohr     *
526d9a8a49SAndreas Gohr     * @return bool true if successful, false if not applies
5374b4d4a4SAndreas Gohr     * @throws \OAuth\Common\Exception\Exception
5474b4d4a4SAndreas Gohr     */
5574b4d4a4SAndreas Gohr    protected function loginByService()
5674b4d4a4SAndreas Gohr    {
5774b4d4a4SAndreas Gohr        global $INPUT;
5874b4d4a4SAndreas Gohr
5974b4d4a4SAndreas Gohr        if (!$INPUT->get->has('code') && !$INPUT->get->has('oauth_token')) {
6074b4d4a4SAndreas Gohr            return false;
6174b4d4a4SAndreas Gohr        }
6274b4d4a4SAndreas Gohr
6374b4d4a4SAndreas Gohr        $session = Session::getInstance();
6474b4d4a4SAndreas Gohr
6574b4d4a4SAndreas Gohr        // init service from session
6674b4d4a4SAndreas Gohr        $logindata = $session->getLoginData();
6774b4d4a4SAndreas Gohr        if (!$logindata) return false;
6874b4d4a4SAndreas Gohr        $service = $this->loadService($logindata['servicename']);
6974b4d4a4SAndreas Gohr        $service->initOAuthService($logindata['guid']);
7074b4d4a4SAndreas Gohr        $session->clearLoginData();
7174b4d4a4SAndreas Gohr
7274b4d4a4SAndreas Gohr        // oAuth login
7304a78b87SAndreas Gohr        if (!$service->checkToken()) throw new \OAuth\Common\Exception\Exception("Invalid Token - Login failed");
7474b4d4a4SAndreas Gohr        $userdata = $service->getUser();
7574b4d4a4SAndreas Gohr
7674b4d4a4SAndreas Gohr        // processing
7774b4d4a4SAndreas Gohr        $userdata = $this->validateUserData($userdata, $logindata['servicename']);
7874b4d4a4SAndreas Gohr        $userdata = $this->processUserData($userdata, $logindata['servicename']);
7974b4d4a4SAndreas Gohr
8074b4d4a4SAndreas Gohr        // login
8174b4d4a4SAndreas Gohr        $session->setUser($userdata); // log in
8274b4d4a4SAndreas Gohr        $session->setCookie($logindata['servicename'], $logindata['guid']); // set cookie
8374b4d4a4SAndreas Gohr
8431039e80SAndreas Gohr        // redirect to the appropriate ID
8531039e80SAndreas Gohr        if (!empty($logindata['id'])) {
8631039e80SAndreas Gohr            send_redirect(wl($logindata['id'], [], true, '&'));
8731039e80SAndreas Gohr        }
8874b4d4a4SAndreas Gohr        return true;
8974b4d4a4SAndreas Gohr    }
9074b4d4a4SAndreas Gohr
9174b4d4a4SAndreas Gohr    /**
926d9a8a49SAndreas Gohr     * Login based on user's current session data
9374b4d4a4SAndreas Gohr     *
9474b4d4a4SAndreas Gohr     * This will also log in plainauth users
9574b4d4a4SAndreas Gohr     *
966d9a8a49SAndreas Gohr     * @return bool true if successful, false if not applies
9774b4d4a4SAndreas Gohr     * @throws Exception
9874b4d4a4SAndreas Gohr     */
9974b4d4a4SAndreas Gohr    protected function loginBySession()
10074b4d4a4SAndreas Gohr    {
10174b4d4a4SAndreas Gohr        $session = Session::getInstance();
10274b4d4a4SAndreas Gohr        if (!$session->isValid()) {
10374b4d4a4SAndreas Gohr            $session->clear();
10474b4d4a4SAndreas Gohr            return false;
10574b4d4a4SAndreas Gohr        }
10674b4d4a4SAndreas Gohr
10774b4d4a4SAndreas Gohr        $userdata = $session->getUser();
10831039e80SAndreas Gohr        if (!$userdata) return false;
10904a78b87SAndreas Gohr        if (!isset($userdata['user'])) return false; // default dokuwiki does not put username here, let DW handle it
11074b4d4a4SAndreas Gohr        $session->setUser($userdata, false); // does a login without resetting the time
11174b4d4a4SAndreas Gohr        return true;
11274b4d4a4SAndreas Gohr    }
11374b4d4a4SAndreas Gohr
11474b4d4a4SAndreas Gohr    /**
1156d9a8a49SAndreas Gohr     * Login based on user cookie and a previously saved access token
11674b4d4a4SAndreas Gohr     *
1176d9a8a49SAndreas Gohr     * @return bool true if successful, false if not applies
118*c82ad624SAndreas Gohr     * @throws \OAuth\Common\Exception\Exception
11974b4d4a4SAndreas Gohr     */
12074b4d4a4SAndreas Gohr    protected function loginByCookie()
12174b4d4a4SAndreas Gohr    {
12274b4d4a4SAndreas Gohr        $session = Session::getInstance();
12374b4d4a4SAndreas Gohr        $cookie = $session->getCookie();
12474b4d4a4SAndreas Gohr        if (!$cookie) return false;
12574b4d4a4SAndreas Gohr
12674b4d4a4SAndreas Gohr        $service = $this->loadService($cookie['servicename']);
12774b4d4a4SAndreas Gohr        $service->initOAuthService($cookie['guid']);
128*c82ad624SAndreas Gohr
129*c82ad624SAndreas Gohr        // ensure that we have a current access token
130*c82ad624SAndreas Gohr        $oauth = $service->getOAuthService();
131*c82ad624SAndreas Gohr        if (!$oauth->getStorage()->hasAccessToken($oauth->service())) return false;
132*c82ad624SAndreas Gohr        $accessToken = $oauth->getStorage()->retrieveAccessToken($oauth->service());
133*c82ad624SAndreas Gohr        if ($accessToken->getEndOfLife() > 0 &&
134*c82ad624SAndreas Gohr            $accessToken->getEndOfLife() - time() < 3600 &&
135*c82ad624SAndreas Gohr            $accessToken->getRefreshToken()) {
136*c82ad624SAndreas Gohr            $oauth->refreshAccessToken($accessToken);
13774b4d4a4SAndreas Gohr        }
13874b4d4a4SAndreas Gohr
1396d9a8a49SAndreas Gohr        // this should use a previously saved token
1406d9a8a49SAndreas Gohr        $userdata = $service->getUser();
1416d9a8a49SAndreas Gohr
1426d9a8a49SAndreas Gohr        // processing
1436d9a8a49SAndreas Gohr        $userdata = $this->validateUserData($userdata, $cookie['servicename']);
1446d9a8a49SAndreas Gohr        $userdata = $this->processUserData($userdata, $cookie['servicename']);
1456d9a8a49SAndreas Gohr
14674b4d4a4SAndreas Gohr        $session->setUser($userdata); // log in
14774b4d4a4SAndreas Gohr        return true;
14874b4d4a4SAndreas Gohr    }
14974b4d4a4SAndreas Gohr
1506d9a8a49SAndreas Gohr    // endregion
1516d9a8a49SAndreas Gohr
15274b4d4a4SAndreas Gohr    /**
15374b4d4a4SAndreas Gohr     * Clean and validate the user data provided from the service
15474b4d4a4SAndreas Gohr     *
15574b4d4a4SAndreas Gohr     * @param array $userdata
15674b4d4a4SAndreas Gohr     * @param string $servicename
15774b4d4a4SAndreas Gohr     * @return array
15874b4d4a4SAndreas Gohr     * @throws Exception
15974b4d4a4SAndreas Gohr     * @todo test
16074b4d4a4SAndreas Gohr     */
16174b4d4a4SAndreas Gohr    protected function validateUserData($userdata, $servicename)
16274b4d4a4SAndreas Gohr    {
16374b4d4a4SAndreas Gohr        /** @var \auth_plugin_oauth */
16474b4d4a4SAndreas Gohr        global $auth;
16574b4d4a4SAndreas Gohr
16674b4d4a4SAndreas Gohr        // mail is required
16774b4d4a4SAndreas Gohr        if (empty($userdata['mail'])) {
16874b4d4a4SAndreas Gohr            throw new Exception("$servicename did not provide the an email address. Can't log you in");
16974b4d4a4SAndreas Gohr        }
17074b4d4a4SAndreas Gohr
17174b4d4a4SAndreas Gohr        // mail needs to be allowed
17274b4d4a4SAndreas Gohr        /** @var \helper_plugin_oauth $hlp */
17374b4d4a4SAndreas Gohr        $hlp = plugin_load('helper', 'oauth');
17474b4d4a4SAndreas Gohr        $hlp->checkMail($userdata['mail']);
17574b4d4a4SAndreas Gohr
17674b4d4a4SAndreas Gohr        // make username from mail if empty
17774b4d4a4SAndreas Gohr        $userdata['user'] = $auth->cleanUser((string)$userdata['user']);
17874b4d4a4SAndreas Gohr        if ($userdata === '') {
17974b4d4a4SAndreas Gohr            list($userdata['user']) = explode('@', $userdata['mail']);
18074b4d4a4SAndreas Gohr        }
18174b4d4a4SAndreas Gohr
18274b4d4a4SAndreas Gohr        // make full name from username if empty
18374b4d4a4SAndreas Gohr        if (empty($userdata['name'])) {
18474b4d4a4SAndreas Gohr            $userdata['name'] = $userdata['user'];
18574b4d4a4SAndreas Gohr        }
18674b4d4a4SAndreas Gohr
18774b4d4a4SAndreas Gohr        return $userdata;
18874b4d4a4SAndreas Gohr    }
18974b4d4a4SAndreas Gohr
19074b4d4a4SAndreas Gohr    /**
19174b4d4a4SAndreas Gohr     * Process the userdata, update the user info array and create the user if necessary
19274b4d4a4SAndreas Gohr     *
19374b4d4a4SAndreas Gohr     * Uses the global $auth object for user management
19474b4d4a4SAndreas Gohr     *
19574b4d4a4SAndreas Gohr     * @param array $userdata User info received from authentication
19674b4d4a4SAndreas Gohr     * @param string $servicename Auth service
19774b4d4a4SAndreas Gohr     * @return array the modified user info
19874b4d4a4SAndreas Gohr     * @throws Exception
19974b4d4a4SAndreas Gohr     */
20074b4d4a4SAndreas Gohr    protected function processUserData($userdata, $servicename)
20174b4d4a4SAndreas Gohr    {
202e170f465SAndreas Gohr        /** @var \auth_plugin_oauth $auth */
20374b4d4a4SAndreas Gohr        global $auth;
20474b4d4a4SAndreas Gohr
20574b4d4a4SAndreas Gohr        // see if the user is known already
20674b4d4a4SAndreas Gohr        $localUser = $auth->getUserByEmail($userdata['mail']);
20774b4d4a4SAndreas Gohr        if ($localUser) {
20874b4d4a4SAndreas Gohr            $localUserInfo = $auth->getUserData($localUser);
20974b4d4a4SAndreas Gohr            // check if the user allowed access via this service
21074b4d4a4SAndreas Gohr            if (!in_array($auth->cleanGroup($servicename), $localUserInfo['grps'])) {
21174b4d4a4SAndreas Gohr                throw new Exception(sprintf($auth->getLang('authnotenabled'), $servicename));
21274b4d4a4SAndreas Gohr            }
21374b4d4a4SAndreas Gohr            $userdata['user'] = $localUser;
21474b4d4a4SAndreas Gohr            $userdata['name'] = $localUserInfo['name'];
21574b4d4a4SAndreas Gohr            $userdata['grps'] = array_merge((array)$userdata['grps'], $localUserInfo['grps']);
21674b4d4a4SAndreas Gohr        } elseif (actionOK('register') || $auth->getConf('register-on-auth')) {
217e170f465SAndreas Gohr            if (!$auth->registerOAuthUser($userdata, $servicename)) {
21874b4d4a4SAndreas Gohr                throw new Exception('something went wrong creating your user account. please try again later.');
21974b4d4a4SAndreas Gohr            }
22074b4d4a4SAndreas Gohr        } else {
22174b4d4a4SAndreas Gohr            throw new Exception($auth->getLang('addUser not possible'));
22274b4d4a4SAndreas Gohr        }
22374b4d4a4SAndreas Gohr
22474b4d4a4SAndreas Gohr        return $userdata;
22574b4d4a4SAndreas Gohr    }
22674b4d4a4SAndreas Gohr
22774b4d4a4SAndreas Gohr    /**
22874b4d4a4SAndreas Gohr     * Instantiates a Service by name
22974b4d4a4SAndreas Gohr     *
23074b4d4a4SAndreas Gohr     * @param string $servicename
23104a78b87SAndreas Gohr     * @return Adapter
23274b4d4a4SAndreas Gohr     * @throws Exception
23374b4d4a4SAndreas Gohr     */
23474b4d4a4SAndreas Gohr    protected function loadService($servicename)
23574b4d4a4SAndreas Gohr    {
23674b4d4a4SAndreas Gohr        /** @var \helper_plugin_oauth $hlp */
23774b4d4a4SAndreas Gohr        $hlp = plugin_load('helper', 'oauth');
23874b4d4a4SAndreas Gohr        $srv = $hlp->loadService($servicename);
23974b4d4a4SAndreas Gohr
24074b4d4a4SAndreas Gohr        if ($srv === null) throw new Exception("No such service $servicename");
24174b4d4a4SAndreas Gohr        return $srv;
24274b4d4a4SAndreas Gohr    }
24374b4d4a4SAndreas Gohr
24474b4d4a4SAndreas Gohr}
245