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