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