xref: /plugin/oauth/OAuthManager.php (revision c82ad624fa9f1d4a669d251578a03706028cf90a)
1<?php
2
3namespace dokuwiki\plugin\oauth;
4
5/**
6 * Implements the flow control for oAuth
7 */
8class OAuthManager
9{
10    // region main flow
11
12    /**
13     * Explicitly starts the oauth flow by redirecting to IDP
14     *
15     * @throws \OAuth\Common\Exception\Exception
16     */
17    public function startFlow($servicename)
18    {
19        global $ID;
20
21        // generate a new GUID to identify this user
22        try {
23            $guid = bin2hex(random_bytes(16));
24        } catch (\Exception $e) {
25            throw new \OAuth\Common\Exception\Exception($e->getMessage());
26        }
27
28        $session = Session::getInstance();
29        $session->setLoginData($servicename, $guid, $ID);
30        $service = $this->loadService($servicename);
31        $service->initOAuthService($guid);
32        $service->login(); // redirects
33    }
34
35    /**
36     * Continues the flow from various states
37     *
38     * @return bool true if the login has been handled
39     * @throws Exception
40     * @throws \OAuth\Common\Exception\Exception
41     */
42    public function continueFlow()
43    {
44        return $this->loginByService() or
45            $this->loginBySession() or
46            $this->loginByCookie();
47    }
48
49    /**
50     * Second step in a explicit login, validates the oauth code
51     *
52     * @return bool true if successful, false if not applies
53     * @throws \OAuth\Common\Exception\Exception
54     */
55    protected function loginByService()
56    {
57        global $INPUT;
58
59        if (!$INPUT->get->has('code') && !$INPUT->get->has('oauth_token')) {
60            return false;
61        }
62
63        $session = Session::getInstance();
64
65        // init service from session
66        $logindata = $session->getLoginData();
67        if (!$logindata) return false;
68        $service = $this->loadService($logindata['servicename']);
69        $service->initOAuthService($logindata['guid']);
70        $session->clearLoginData();
71
72        // oAuth login
73        if (!$service->checkToken()) throw new \OAuth\Common\Exception\Exception("Invalid Token - Login failed");
74        $userdata = $service->getUser();
75
76        // processing
77        $userdata = $this->validateUserData($userdata, $logindata['servicename']);
78        $userdata = $this->processUserData($userdata, $logindata['servicename']);
79
80        // login
81        $session->setUser($userdata); // log in
82        $session->setCookie($logindata['servicename'], $logindata['guid']); // set cookie
83
84        // redirect to the appropriate ID
85        if (!empty($logindata['id'])) {
86            send_redirect(wl($logindata['id'], [], true, '&'));
87        }
88        return true;
89    }
90
91    /**
92     * Login based on user's current session data
93     *
94     * This will also log in plainauth users
95     *
96     * @return bool true if successful, false if not applies
97     * @throws Exception
98     */
99    protected function loginBySession()
100    {
101        $session = Session::getInstance();
102        if (!$session->isValid()) {
103            $session->clear();
104            return false;
105        }
106
107        $userdata = $session->getUser();
108        if (!$userdata) return false;
109        if (!isset($userdata['user'])) return false; // default dokuwiki does not put username here, let DW handle it
110        $session->setUser($userdata, false); // does a login without resetting the time
111        return true;
112    }
113
114    /**
115     * Login based on user cookie and a previously saved access token
116     *
117     * @return bool true if successful, false if not applies
118     * @throws \OAuth\Common\Exception\Exception
119     */
120    protected function loginByCookie()
121    {
122        $session = Session::getInstance();
123        $cookie = $session->getCookie();
124        if (!$cookie) return false;
125
126        $service = $this->loadService($cookie['servicename']);
127        $service->initOAuthService($cookie['guid']);
128
129        // ensure that we have a current access token
130        $oauth = $service->getOAuthService();
131        if (!$oauth->getStorage()->hasAccessToken($oauth->service())) return false;
132        $accessToken = $oauth->getStorage()->retrieveAccessToken($oauth->service());
133        if ($accessToken->getEndOfLife() > 0 &&
134            $accessToken->getEndOfLife() - time() < 3600 &&
135            $accessToken->getRefreshToken()) {
136            $oauth->refreshAccessToken($accessToken);
137        }
138
139        // this should use a previously saved token
140        $userdata = $service->getUser();
141
142        // processing
143        $userdata = $this->validateUserData($userdata, $cookie['servicename']);
144        $userdata = $this->processUserData($userdata, $cookie['servicename']);
145
146        $session->setUser($userdata); // log in
147        return true;
148    }
149
150    // endregion
151
152    /**
153     * Clean and validate the user data provided from the service
154     *
155     * @param array $userdata
156     * @param string $servicename
157     * @return array
158     * @throws Exception
159     * @todo test
160     */
161    protected function validateUserData($userdata, $servicename)
162    {
163        /** @var \auth_plugin_oauth */
164        global $auth;
165
166        // mail is required
167        if (empty($userdata['mail'])) {
168            throw new Exception("$servicename did not provide the an email address. Can't log you in");
169        }
170
171        // mail needs to be allowed
172        /** @var \helper_plugin_oauth $hlp */
173        $hlp = plugin_load('helper', 'oauth');
174        $hlp->checkMail($userdata['mail']);
175
176        // make username from mail if empty
177        $userdata['user'] = $auth->cleanUser((string)$userdata['user']);
178        if ($userdata === '') {
179            list($userdata['user']) = explode('@', $userdata['mail']);
180        }
181
182        // make full name from username if empty
183        if (empty($userdata['name'])) {
184            $userdata['name'] = $userdata['user'];
185        }
186
187        return $userdata;
188    }
189
190    /**
191     * Process the userdata, update the user info array and create the user if necessary
192     *
193     * Uses the global $auth object for user management
194     *
195     * @param array $userdata User info received from authentication
196     * @param string $servicename Auth service
197     * @return array the modified user info
198     * @throws Exception
199     */
200    protected function processUserData($userdata, $servicename)
201    {
202        /** @var \auth_plugin_oauth $auth */
203        global $auth;
204
205        // see if the user is known already
206        $localUser = $auth->getUserByEmail($userdata['mail']);
207        if ($localUser) {
208            $localUserInfo = $auth->getUserData($localUser);
209            // check if the user allowed access via this service
210            if (!in_array($auth->cleanGroup($servicename), $localUserInfo['grps'])) {
211                throw new Exception(sprintf($auth->getLang('authnotenabled'), $servicename));
212            }
213            $userdata['user'] = $localUser;
214            $userdata['name'] = $localUserInfo['name'];
215            $userdata['grps'] = array_merge((array)$userdata['grps'], $localUserInfo['grps']);
216        } elseif (actionOK('register') || $auth->getConf('register-on-auth')) {
217            if (!$auth->registerOAuthUser($userdata, $servicename)) {
218                throw new Exception('something went wrong creating your user account. please try again later.');
219            }
220        } else {
221            throw new Exception($auth->getLang('addUser not possible'));
222        }
223
224        return $userdata;
225    }
226
227    /**
228     * Instantiates a Service by name
229     *
230     * @param string $servicename
231     * @return Adapter
232     * @throws Exception
233     */
234    protected function loadService($servicename)
235    {
236        /** @var \helper_plugin_oauth $hlp */
237        $hlp = plugin_load('helper', 'oauth');
238        $srv = $hlp->loadService($servicename);
239
240        if ($srv === null) throw new Exception("No such service $servicename");
241        return $srv;
242    }
243
244}
245