xref: /plugin/oauth/OAuthManager.php (revision 6d9a8a49e2ddf3e7f5338a74a91c704edb6dc965)
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     * @throws Exception
16     * @throws TokenResponseException
17     */
18    public function startFlow($servicename)
19    {
20        // generate a new GUID to identify this user
21        $guid = bin2hex(random_bytes(16));
22
23        $session = Session::getInstance();
24        $session->setLoginData($servicename, $guid);
25
26        // fixme store environment
27        $service = $this->loadService($servicename);
28        $service->initOAuthService($guid);
29        $service->login(); // redirects
30
31    }
32
33    /**
34     * @return bool true if the login has been handled
35     * @throws Exception
36     * @throws \OAuth\Common\Exception\Exception
37     * @todo this probably moves over to auth
38     */
39    public function continueFlow()
40    {
41
42        return $this->loginByService() or
43            $this->loginBySession() or
44            $this->loginByCookie();
45
46    }
47
48    /**
49     * Second step in a explicit login, validates the oauth code
50     *
51     * @return bool true if successful, false if not applies
52     * @throws \OAuth\Common\Exception\Exception
53     * @throws 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 Exception("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        // fixme restore environment
85
86        return true;
87    }
88
89    /**
90     * Login based on user's current session data
91     *
92     * This will also log in plainauth users
93     *
94     * @return bool true if successful, false if not applies
95     * @throws Exception
96     */
97    protected function loginBySession()
98    {
99        $session = Session::getInstance();
100        if (!$session->isValid()) {
101            $session->clear();
102            return false;
103        }
104
105        $userdata = $session->getUser();
106        $session->setUser($userdata, false); // does a login without resetting the time
107
108        return true;
109    }
110
111    /**
112     * Login based on user cookie and a previously saved access token
113     *
114     * @return bool true if successful, false if not applies
115     * @throws Exception
116     */
117    protected function loginByCookie()
118    {
119        $session = Session::getInstance();
120        $cookie = $session->getCookie();
121        if (!$cookie) return false;
122
123        try {
124            $service = $this->loadService($cookie['servicename']);
125            $service->initOAuthService($cookie['guid']);
126        } catch (Exception $e) {
127            return false; // maybe cookie had old service that is no longer available
128        }
129
130        // this should use a previously saved token
131        $userdata = $service->getUser();
132
133        // processing
134        $userdata = $this->validateUserData($userdata, $cookie['servicename']);
135        $userdata = $this->processUserData($userdata, $cookie['servicename']);
136
137        $session->setUser($userdata); // log in
138        return true;
139    }
140
141    // endregion
142
143    /**
144     * Clean and validate the user data provided from the service
145     *
146     * @param array $userdata
147     * @param string $servicename
148     * @return array
149     * @throws Exception
150     * @todo test
151     */
152    protected function validateUserData($userdata, $servicename)
153    {
154        /** @var \auth_plugin_oauth */
155        global $auth;
156
157        // mail is required
158        if (empty($userdata['mail'])) {
159            throw new Exception("$servicename did not provide the an email address. Can't log you in");
160        }
161
162        // mail needs to be allowed
163        /** @var \helper_plugin_oauth $hlp */
164        $hlp = plugin_load('helper', 'oauth');
165        $hlp->checkMail($userdata['mail']);
166
167        // make username from mail if empty
168        $userdata['user'] = $auth->cleanUser((string)$userdata['user']);
169        if ($userdata === '') {
170            list($userdata['user']) = explode('@', $userdata['mail']);
171        }
172
173        // make full name from username if empty
174        if (empty($userdata['name'])) {
175            $userdata['name'] = $userdata['user'];
176        }
177
178        return $userdata;
179    }
180
181    /**
182     * Process the userdata, update the user info array and create the user if necessary
183     *
184     * Uses the global $auth object for user management
185     *
186     * @param array $userdata User info received from authentication
187     * @param string $servicename Auth service
188     * @return array the modified user info
189     * @throws Exception
190     */
191    protected function processUserData($userdata, $servicename)
192    {
193        /** @var \auth_plugin_oauth */
194        global $auth;
195
196        // see if the user is known already
197        $localUser = $auth->getUserByEmail($userdata['mail']);
198        if ($localUser) {
199            $localUserInfo = $auth->getUserData($localUser);
200            // check if the user allowed access via this service
201            if (!in_array($auth->cleanGroup($servicename), $localUserInfo['grps'])) {
202                throw new Exception(sprintf($auth->getLang('authnotenabled'), $servicename));
203            }
204            $userdata['user'] = $localUser;
205            $userdata['name'] = $localUserInfo['name'];
206            $userdata['grps'] = array_merge((array)$userdata['grps'], $localUserInfo['grps']);
207        } elseif (actionOK('register') || $auth->getConf('register-on-auth')) {
208            if (!$auth->addUser($userdata, $servicename)) {
209                throw new Exception('something went wrong creating your user account. please try again later.');
210            }
211        } else {
212            throw new Exception($auth->getLang('addUser not possible'));
213        }
214
215        return $userdata;
216    }
217
218    /**
219     * Instantiates a Service by name
220     *
221     * @param string $servicename
222     * @return Service
223     * @throws Exception
224     */
225    protected function loadService($servicename)
226    {
227        /** @var \helper_plugin_oauth $hlp */
228        $hlp = plugin_load('helper', 'oauth');
229        $srv = $hlp->loadService($servicename);
230
231        if ($srv === null) throw new Exception("No such service $servicename");
232        return $srv;
233    }
234
235}
236