xref: /plugin/oauth/OAuthManager.php (revision 9cbef4d7b9ae5efb4aae394eacab1875af2b2307)
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        $service->refreshOutdatedToken();
131
132        // this should use a previously saved token
133        $userdata = $service->getUser();
134
135        // processing
136        $userdata = $this->validateUserData($userdata, $cookie['servicename']);
137        $userdata = $this->processUserData($userdata, $cookie['servicename']);
138
139        $session->setUser($userdata); // log in
140        return true;
141    }
142
143    // endregion
144
145    /**
146     * Clean and validate the user data provided from the service
147     *
148     * @param array $userdata
149     * @param string $servicename
150     * @return array
151     * @throws Exception
152     * @todo test
153     */
154    protected function validateUserData($userdata, $servicename)
155    {
156        /** @var \auth_plugin_oauth */
157        global $auth;
158
159        // mail is required
160        if (empty($userdata['mail'])) {
161            throw new Exception("$servicename did not provide the an email address. Can't log you in");
162        }
163
164        // mail needs to be allowed
165        /** @var \helper_plugin_oauth $hlp */
166        $hlp = plugin_load('helper', 'oauth');
167        $hlp->checkMail($userdata['mail']);
168
169        // make username from mail if empty
170        $userdata['user'] = $auth->cleanUser((string)$userdata['user']);
171        if ($userdata === '') {
172            list($userdata['user']) = explode('@', $userdata['mail']);
173        }
174
175        // make full name from username if empty
176        if (empty($userdata['name'])) {
177            $userdata['name'] = $userdata['user'];
178        }
179
180        return $userdata;
181    }
182
183    /**
184     * Process the userdata, update the user info array and create the user if necessary
185     *
186     * Uses the global $auth object for user management
187     *
188     * @param array $userdata User info received from authentication
189     * @param string $servicename Auth service
190     * @return array the modified user info
191     * @throws Exception
192     */
193    protected function processUserData($userdata, $servicename)
194    {
195        /** @var \auth_plugin_oauth $auth */
196        global $auth;
197
198        // see if the user is known already
199        $localUser = $auth->getUserByEmail($userdata['mail']);
200        if ($localUser) {
201            $localUserInfo = $auth->getUserData($localUser);
202            // check if the user allowed access via this service
203            if (!in_array($auth->cleanGroup($servicename), $localUserInfo['grps'])) {
204                throw new Exception(sprintf($auth->getLang('authnotenabled'), $servicename));
205            }
206            $userdata['user'] = $localUser;
207            $userdata['name'] = $localUserInfo['name'];
208            $userdata['grps'] = array_merge((array)$userdata['grps'], $localUserInfo['grps']);
209        } elseif (actionOK('register') || $auth->getConf('register-on-auth')) {
210            if (!$auth->registerOAuthUser($userdata, $servicename)) {
211                throw new Exception('something went wrong creating your user account. please try again later.');
212            }
213        } else {
214            throw new Exception($auth->getLang('addUser not possible'));
215        }
216
217        return $userdata;
218    }
219
220    /**
221     * Instantiates a Service by name
222     *
223     * @param string $servicename
224     * @return Adapter
225     * @throws Exception
226     */
227    protected function loadService($servicename)
228    {
229        /** @var \helper_plugin_oauth $hlp */
230        $hlp = plugin_load('helper', 'oauth');
231        $srv = $hlp->loadService($servicename);
232
233        if ($srv === null) throw new Exception("No such service $servicename");
234        return $srv;
235    }
236
237}
238