Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
18.03% |
55 / 305 |
|
25.00% |
5 / 20 |
CRAP | |
0.00% |
0 / 1 |
oauth | |
18.03% |
55 / 305 |
|
25.00% |
5 / 20 |
3876.82 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
1 | |||
init | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
42 | |||
login | |
0.00% |
0 / 92 |
|
0.00% |
0 / 1 |
210 | |||
get_login_data | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
20 | |||
acp | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
6 | |||
get_acp_template | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
6 | |||
login_link_has_necessary_data | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
42 | |||
link_account | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
110 | |||
logout | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
get_auth_link_data | |
100.00% |
26 / 26 |
|
100.00% |
1 / 1 |
7 | |||
unlink_account | |
90.91% |
10 / 11 |
|
0.00% |
0 / 1 |
4.01 | |||
link_account_login_link | |
0.00% |
0 / 19 |
|
0.00% |
0 / 1 |
20 | |||
link_account_auth_link | |
0.00% |
0 / 19 |
|
0.00% |
0 / 1 |
20 | |||
link_account_perform_link | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
6 | |||
get_service | |
0.00% |
0 / 20 |
|
0.00% |
0 / 1 |
30 | |||
get_service_name | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
get_provider | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
get_provider_title | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
is_set_code | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
20 | |||
set_redirect | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
12 |
1 | <?php |
2 | /** |
3 | * |
4 | * This file is part of the phpBB Forum Software package. |
5 | * |
6 | * @copyright (c) phpBB Limited <https://www.phpbb.com> |
7 | * @license GNU General Public License, version 2 (GPL-2.0) |
8 | * |
9 | * For full copyright and license information, please see |
10 | * the docs/CREDITS.txt file. |
11 | * |
12 | */ |
13 | |
14 | namespace phpbb\auth\provider\oauth; |
15 | |
16 | use OAuth\Common\Http\Exception\TokenResponseException; |
17 | use OAuth\ServiceFactory; |
18 | use OAuth\Common\Consumer\Credentials; |
19 | use OAuth\Common\Service\ServiceInterface; |
20 | use OAuth\OAuth1\Service\AbstractService as OAuth1Service; |
21 | use OAuth\OAuth2\Service\AbstractService as OAuth2Service; |
22 | use phpbb\auth\provider\base; |
23 | use phpbb\auth\provider\db; |
24 | use phpbb\auth\provider\oauth\service\exception; |
25 | use phpbb\config\config; |
26 | use phpbb\db\driver\driver_interface; |
27 | use phpbb\di\service_collection; |
28 | use phpbb\event\dispatcher; |
29 | use phpbb\language\language; |
30 | use phpbb\request\request_interface; |
31 | use phpbb\user; |
32 | |
33 | /** |
34 | * OAuth authentication provider for phpBB3 |
35 | */ |
36 | class oauth extends base |
37 | { |
38 | /** @var config */ |
39 | protected $config; |
40 | |
41 | /** @var driver_interface */ |
42 | protected $db; |
43 | |
44 | /** @var db */ |
45 | protected $db_auth; |
46 | |
47 | /** @var dispatcher */ |
48 | protected $dispatcher; |
49 | |
50 | /** @var language */ |
51 | protected $language; |
52 | |
53 | /** @var request_interface */ |
54 | protected $request; |
55 | |
56 | /** @var service_collection */ |
57 | protected $service_providers; |
58 | |
59 | /** @var user */ |
60 | protected $user; |
61 | |
62 | /** @var string OAuth table: token storage */ |
63 | protected $oauth_token_table; |
64 | |
65 | /** @var string OAuth table: state */ |
66 | protected $oauth_state_table; |
67 | |
68 | /** @var string OAuth table: account association */ |
69 | protected $oauth_account_table; |
70 | |
71 | /** @var string Users table */ |
72 | protected $users_table; |
73 | |
74 | /** @var string phpBB root path */ |
75 | protected $root_path; |
76 | |
77 | /** @var string php File extension */ |
78 | protected $php_ext; |
79 | |
80 | /** |
81 | * Constructor. |
82 | * |
83 | * @param config $config Config object |
84 | * @param driver_interface $db Database object |
85 | * @param db $db_auth DB auth provider |
86 | * @param dispatcher $dispatcher Event dispatcher object |
87 | * @param language $language Language object |
88 | * @param request_interface $request Request object |
89 | * @param service_collection $service_providers OAuth providers service collection |
90 | * @param user $user User object |
91 | * @param string $oauth_token_table OAuth table: token storage |
92 | * @param string $oauth_state_table OAuth table: state |
93 | * @param string $oauth_account_table OAuth table: account association |
94 | * @param string $users_table User table |
95 | * @param string $root_path phpBB root path |
96 | * @param string $php_ext php File extension |
97 | */ |
98 | public function __construct( |
99 | config $config, |
100 | driver_interface $db, |
101 | db $db_auth, |
102 | dispatcher $dispatcher, |
103 | language $language, |
104 | request_interface $request, |
105 | service_collection $service_providers, |
106 | user $user, |
107 | $oauth_token_table, |
108 | $oauth_state_table, |
109 | $oauth_account_table, |
110 | $users_table, |
111 | $root_path, |
112 | $php_ext |
113 | ) |
114 | { |
115 | $this->config = $config; |
116 | $this->db = $db; |
117 | $this->db_auth = $db_auth; |
118 | $this->dispatcher = $dispatcher; |
119 | $this->language = $language; |
120 | $this->service_providers = $service_providers; |
121 | $this->request = $request; |
122 | $this->user = $user; |
123 | |
124 | $this->oauth_token_table = $oauth_token_table; |
125 | $this->oauth_state_table = $oauth_state_table; |
126 | $this->oauth_account_table = $oauth_account_table; |
127 | $this->users_table = $users_table; |
128 | $this->root_path = $root_path; |
129 | $this->php_ext = $php_ext; |
130 | } |
131 | |
132 | /** |
133 | * {@inheritdoc} |
134 | */ |
135 | public function init() |
136 | { |
137 | // This does not test whether or not the key and secret provided are valid. |
138 | foreach ($this->service_providers as $service_provider) |
139 | { |
140 | $credentials = $service_provider->get_service_credentials(); |
141 | |
142 | if (($credentials['key'] && !$credentials['secret']) || (!$credentials['key'] && $credentials['secret'])) |
143 | { |
144 | return $this->language->lang('AUTH_PROVIDER_OAUTH_ERROR_ELEMENT_MISSING'); |
145 | } |
146 | } |
147 | |
148 | return false; |
149 | } |
150 | |
151 | /** |
152 | * {@inheritdoc} |
153 | */ |
154 | public function login($username, $password) |
155 | { |
156 | // Temporary workaround for only having one authentication provider available |
157 | if (!$this->request->is_set('oauth_service')) |
158 | { |
159 | return $this->db_auth->login($username, $password); |
160 | } |
161 | |
162 | // Request the name of the OAuth service |
163 | $provider = $this->request->variable('oauth_service', '', false); |
164 | $service_name = $this->get_service_name($provider); |
165 | |
166 | if ($provider === '' || !$this->service_providers->offsetExists($service_name)) |
167 | { |
168 | return [ |
169 | 'status' => LOGIN_ERROR_EXTERNAL_AUTH, |
170 | 'error_msg' => 'LOGIN_ERROR_OAUTH_SERVICE_DOES_NOT_EXIST', |
171 | 'user_row' => ['user_id' => ANONYMOUS], |
172 | ]; |
173 | } |
174 | |
175 | // Get the service credentials for the given service |
176 | $storage = new token_storage($this->db, $this->user, $this->oauth_token_table, $this->oauth_state_table); |
177 | $query = 'mode=login&login=external&oauth_service=' . $provider; |
178 | |
179 | try |
180 | { |
181 | /** @var OAuth1Service|OAuth2Service $service */ |
182 | $service = $this->get_service($provider, $storage, $query); |
183 | } |
184 | catch (\Exception $e) |
185 | { |
186 | return [ |
187 | 'status' => LOGIN_ERROR_EXTERNAL_AUTH, |
188 | 'error_msg' => $e->getMessage(), |
189 | 'user_row' => ['user_id' => ANONYMOUS], |
190 | ]; |
191 | } |
192 | |
193 | if ($this->is_set_code($service)) |
194 | { |
195 | $this->service_providers[$service_name]->set_external_service_provider($service); |
196 | |
197 | try |
198 | { |
199 | $unique_id = $this->service_providers[$service_name]->perform_auth_login(); |
200 | } |
201 | catch (exception $e) |
202 | { |
203 | return [ |
204 | 'status' => LOGIN_ERROR_EXTERNAL_AUTH, |
205 | 'error_msg' => $e->getMessage(), |
206 | 'user_row' => ['user_id' => ANONYMOUS], |
207 | ]; |
208 | } |
209 | |
210 | /** |
211 | * Check to see if this provider is already associated with an account. |
212 | * |
213 | * Enforcing a data type to make sure it are strings and not integers, |
214 | * so values are quoted in the SQL WHERE statement. |
215 | */ |
216 | $data = [ |
217 | 'provider' => (string) utf8_strtolower($provider), |
218 | 'oauth_provider_id' => (string) $unique_id |
219 | ]; |
220 | |
221 | $sql = 'SELECT user_id |
222 | FROM ' . $this->oauth_account_table . ' |
223 | WHERE ' . $this->db->sql_build_array('SELECT', $data); |
224 | $result = $this->db->sql_query($sql); |
225 | $row = $this->db->sql_fetchrow($result); |
226 | $this->db->sql_freeresult($result); |
227 | |
228 | $redirect_data = array( |
229 | 'auth_provider' => 'oauth', |
230 | 'login_link_oauth_service' => $provider, |
231 | ); |
232 | |
233 | /** |
234 | * Event is triggered before check if provider is already associated with an account |
235 | * |
236 | * @event core.oauth_login_after_check_if_provider_id_has_match |
237 | * @var array row User row |
238 | * @var array data Provider data |
239 | * @var array redirect_data Data to be appended to the redirect url |
240 | * @var ServiceInterface service OAuth service |
241 | * @since 3.2.3-RC1 |
242 | * @changed 3.2.6-RC1 Added redirect_data |
243 | * @psalm-var string[] $vars |
244 | */ |
245 | $vars = [ |
246 | 'row', |
247 | 'data', |
248 | 'redirect_data', |
249 | 'service', |
250 | ]; |
251 | extract($this->dispatcher->trigger_event('core.oauth_login_after_check_if_provider_id_has_match', compact($vars))); |
252 | |
253 | if (!$row) |
254 | { |
255 | // The user does not yet exist, ask to link or create profile |
256 | return [ |
257 | 'status' => LOGIN_SUCCESS_LINK_PROFILE, |
258 | 'error_msg' => 'LOGIN_OAUTH_ACCOUNT_NOT_LINKED', |
259 | 'user_row' => [], |
260 | 'redirect_data' => $redirect_data, |
261 | ]; |
262 | } |
263 | |
264 | // Retrieve the user's account |
265 | $sql = 'SELECT user_id, username, user_password, user_passchg, user_email, user_ip, user_type, user_login_attempts |
266 | FROM ' . $this->users_table . ' |
267 | WHERE user_id = ' . (int) $row['user_id']; |
268 | $result = $this->db->sql_query($sql); |
269 | $row = $this->db->sql_fetchrow($result); |
270 | $this->db->sql_freeresult($result); |
271 | |
272 | if (!$row) |
273 | { |
274 | return [ |
275 | 'status' => LOGIN_ERROR_EXTERNAL_AUTH, |
276 | 'error_msg' => 'AUTH_PROVIDER_OAUTH_ERROR_INVALID_ENTRY', |
277 | 'user_row' => ['user_id' => ANONYMOUS], |
278 | ]; |
279 | } |
280 | |
281 | /** |
282 | * Check if the user is banned. |
283 | * The fourth parameter (return) has to be true, otherwise the OAuth login is still called and |
284 | * an uncaught exception is thrown as there is no token stored in the database. |
285 | */ |
286 | $ban = $this->user->check_ban($row['user_id'], $row['user_ip'], $row['user_email'], true); |
287 | |
288 | if (!empty($ban)) |
289 | { |
290 | $till_date = !empty($ban['ban_end']) ? $this->user->format_date($ban['ban_end']) : ''; |
291 | $message = !empty($ban['ban_end']) ? 'BOARD_BAN_TIME' : 'BOARD_BAN_PERM'; |
292 | |
293 | $contact_link = phpbb_get_board_contact_link($this->config, $this->root_path, $this->php_ext); |
294 | |
295 | $message = $this->language->lang($message, $till_date, '<a href="' . $contact_link . '">', '</a>'); |
296 | $message .= !empty($ban['ban_give_reason']) ? '<br /><br />' . $this->language->lang('BOARD_BAN_REASON', $ban['ban_give_reason']) : ''; |
297 | $message .= !empty($ban['ban_triggered_by']) ? '<br /><br /><em>' . $this->language->lang('BAN_TRIGGERED_BY_' . utf8_strtoupper($ban['ban_triggered_by'])) . '</em>' : ''; |
298 | |
299 | return [ |
300 | 'status' => LOGIN_BREAK, |
301 | 'error_msg' => $message, |
302 | 'user_row' => $row, |
303 | ]; |
304 | } |
305 | |
306 | // Update token storage to store the user_id |
307 | $storage->set_user_id($row['user_id']); |
308 | |
309 | /** |
310 | * Event is triggered after user is successfully logged in via OAuth. |
311 | * |
312 | * @event core.auth_oauth_login_after |
313 | * @var array row User row |
314 | * @since 3.1.11-RC1 |
315 | */ |
316 | $vars = [ |
317 | 'row', |
318 | ]; |
319 | extract($this->dispatcher->trigger_event('core.auth_oauth_login_after', compact($vars))); |
320 | |
321 | // The user is now authenticated and can be logged in |
322 | return [ |
323 | 'status' => LOGIN_SUCCESS, |
324 | 'error_msg' => false, |
325 | 'user_row' => $row, |
326 | ]; |
327 | } |
328 | else |
329 | { |
330 | return $this->set_redirect($service); |
331 | } |
332 | } |
333 | |
334 | /** |
335 | * {@inheritdoc} |
336 | */ |
337 | public function get_login_data() |
338 | { |
339 | $login_data = [ |
340 | 'TEMPLATE_FILE' => 'login_body_oauth.html', |
341 | 'BLOCK_VAR_NAME' => 'oauth', |
342 | 'BLOCK_VARS' => [], |
343 | ]; |
344 | |
345 | foreach ($this->service_providers as $service_name => $service_provider) |
346 | { |
347 | // Only include data if the credentials are set |
348 | $credentials = $service_provider->get_service_credentials(); |
349 | |
350 | if ($credentials['key'] && $credentials['secret']) |
351 | { |
352 | $provider = $this->get_provider($service_name); |
353 | $redirect_url = generate_board_url() . '/ucp.' . $this->php_ext . '?mode=login&login=external&oauth_service=' . $provider; |
354 | |
355 | $login_data['BLOCK_VARS'][$service_name] = [ |
356 | 'REDIRECT_URL' => redirect($redirect_url, true), |
357 | 'SERVICE_NAME' => $this->get_provider_title($provider), |
358 | ]; |
359 | } |
360 | } |
361 | |
362 | return $login_data; |
363 | } |
364 | |
365 | /** |
366 | * {@inheritdoc} |
367 | */ |
368 | public function acp() |
369 | { |
370 | $ret = []; |
371 | |
372 | foreach ($this->service_providers as $service_name => $service_provider) |
373 | { |
374 | $provider = $this->get_provider($service_name); |
375 | |
376 | $provider = utf8_strtolower($provider); |
377 | |
378 | $ret[] = 'auth_oauth_' . $provider . '_key'; |
379 | $ret[] = 'auth_oauth_' . $provider . '_secret'; |
380 | } |
381 | |
382 | return $ret; |
383 | } |
384 | |
385 | /** |
386 | * {@inheritdoc} |
387 | */ |
388 | public function get_acp_template($new_config) |
389 | { |
390 | $ret = [ |
391 | 'BLOCK_VAR_NAME' => 'oauth_services', |
392 | 'BLOCK_VARS' => [], |
393 | 'TEMPLATE_FILE' => 'auth_provider_oauth.html', |
394 | 'TEMPLATE_VARS' => [], |
395 | ]; |
396 | |
397 | foreach ($this->service_providers as $service_name => $service_provider) |
398 | { |
399 | $provider = $this->get_provider($service_name); |
400 | |
401 | $ret['BLOCK_VARS'][$provider] = [ |
402 | 'NAME' => $provider, |
403 | 'ACTUAL_NAME' => $this->get_provider_title($provider), |
404 | 'KEY' => $new_config['auth_oauth_' . utf8_strtolower($provider) . '_key'], |
405 | 'SECRET' => $new_config['auth_oauth_' . utf8_strtolower($provider) . '_secret'], |
406 | ]; |
407 | } |
408 | |
409 | return $ret; |
410 | } |
411 | |
412 | /** |
413 | * {@inheritdoc} |
414 | */ |
415 | public function login_link_has_necessary_data(array $login_link_data) |
416 | { |
417 | if (empty($login_link_data)) |
418 | { |
419 | return 'LOGIN_LINK_NO_DATA_PROVIDED'; |
420 | } |
421 | |
422 | if (!array_key_exists('oauth_service', $login_link_data) || !$login_link_data['oauth_service'] || |
423 | !array_key_exists('link_method', $login_link_data) || !$login_link_data['link_method']) |
424 | { |
425 | return 'LOGIN_LINK_MISSING_DATA'; |
426 | } |
427 | |
428 | return null; |
429 | } |
430 | |
431 | /** |
432 | * {@inheritdoc} |
433 | */ |
434 | public function link_account(array $link_data) |
435 | { |
436 | // Check for a valid link method (auth_link or login_link) |
437 | if (!array_key_exists('link_method', $link_data) || |
438 | !in_array($link_data['link_method'], ['auth_link', 'login_link'])) |
439 | { |
440 | return 'LOGIN_LINK_MISSING_DATA'; |
441 | } |
442 | |
443 | // We must have an oauth_service listed, check for it two ways |
444 | if (!array_key_exists('oauth_service', $link_data) || !$link_data['oauth_service']) |
445 | { |
446 | $link_data['oauth_service'] = $this->request->variable('oauth_service', ''); |
447 | |
448 | if (!$link_data['oauth_service']) |
449 | { |
450 | return 'LOGIN_LINK_MISSING_DATA'; |
451 | } |
452 | } |
453 | |
454 | $service_name = $this->get_service_name($link_data['oauth_service']); |
455 | |
456 | if (!$this->service_providers->offsetExists($service_name)) |
457 | { |
458 | return 'LOGIN_ERROR_OAUTH_SERVICE_DOES_NOT_EXIST'; |
459 | } |
460 | |
461 | switch ($link_data['link_method']) |
462 | { |
463 | case 'auth_link': |
464 | return $this->link_account_auth_link($link_data, $service_name); |
465 | case 'login_link': |
466 | return $this->link_account_login_link($link_data, $service_name); |
467 | default: |
468 | return 'LOGIN_LINK_MISSING_DATA'; |
469 | } |
470 | } |
471 | |
472 | /** |
473 | * {@inheritdoc} |
474 | */ |
475 | public function logout($data, $new_session) |
476 | { |
477 | // Clear all tokens belonging to the user |
478 | $storage = new token_storage($this->db, $this->user, $this->oauth_token_table, $this->oauth_state_table); |
479 | $storage->clearAllTokens(); |
480 | } |
481 | |
482 | /** |
483 | * {@inheritdoc} |
484 | */ |
485 | public function get_auth_link_data($user_id = 0) |
486 | { |
487 | $user_ids = []; |
488 | $block_vars = []; |
489 | |
490 | $sql = 'SELECT oauth_provider_id, provider |
491 | FROM ' . $this->oauth_account_table . ' |
492 | WHERE user_id = ' . ($user_id > 0 ? (int) $user_id : (int) $this->user->data['user_id']); |
493 | $result = $this->db->sql_query($sql); |
494 | while ($row = $this->db->sql_fetchrow($result)) |
495 | { |
496 | $user_ids[$row['provider']] = $row['oauth_provider_id']; |
497 | } |
498 | $this->db->sql_freeresult($result); |
499 | |
500 | foreach ($this->service_providers as $service_name => $service_provider) |
501 | { |
502 | // Only include data if the credentials are set |
503 | $credentials = $service_provider->get_service_credentials(); |
504 | |
505 | if ($credentials['key'] && $credentials['secret']) |
506 | { |
507 | $provider = $this->get_provider($service_name); |
508 | |
509 | $block_vars[$service_name] = [ |
510 | 'SERVICE_NAME' => $this->get_provider_title($provider), |
511 | 'UNIQUE_ID' => isset($user_ids[$provider]) ? $user_ids[$provider] : null, |
512 | 'HIDDEN_FIELDS' => [ |
513 | 'link' => !isset($user_ids[$provider]), |
514 | 'oauth_service' => $provider, |
515 | ], |
516 | ]; |
517 | } |
518 | } |
519 | |
520 | return [ |
521 | 'BLOCK_VAR_NAME' => 'oauth', |
522 | 'BLOCK_VARS' => $block_vars, |
523 | |
524 | 'TEMPLATE_FILE' => 'ucp_auth_link_oauth.html', |
525 | ]; |
526 | } |
527 | |
528 | /** |
529 | * {@inheritdoc} |
530 | */ |
531 | public function unlink_account(array $link_data) |
532 | { |
533 | if (!array_key_exists('oauth_service', $link_data) || !$link_data['oauth_service']) |
534 | { |
535 | return 'LOGIN_LINK_MISSING_DATA'; |
536 | } |
537 | |
538 | // Remove user specified in $link_data if possible |
539 | $user_id = isset($link_data['user_id']) ? $link_data['user_id'] : $this->user->data['user_id']; |
540 | |
541 | // Remove the link |
542 | $sql = 'DELETE FROM ' . $this->oauth_account_table . " |
543 | WHERE provider = '" . $this->db->sql_escape($link_data['oauth_service']) . "' |
544 | AND user_id = " . (int) $user_id; |
545 | $this->db->sql_query($sql); |
546 | |
547 | $service_name = $this->get_service_name($link_data['oauth_service']); |
548 | |
549 | // Clear all tokens belonging to the user on this service |
550 | $storage = new token_storage($this->db, $this->user, $this->oauth_token_table, $this->oauth_state_table); |
551 | $storage->clearToken($service_name); |
552 | |
553 | return false; |
554 | } |
555 | |
556 | /** |
557 | * Performs the account linking for login_link. |
558 | * |
559 | * @param array $link_data The same variable given to |
560 | * {@see \phpbb\auth\provider\provider_interface::link_account} |
561 | * @param string $service_name The name of the service being used in linking. |
562 | * @return string|false Returns a language key (string) if an error is encountered, |
563 | * or false on success. |
564 | */ |
565 | protected function link_account_login_link(array $link_data, $service_name) |
566 | { |
567 | $storage = new token_storage($this->db, $this->user, $this->oauth_token_table, $this->oauth_state_table); |
568 | |
569 | // Check for an access token, they should have one |
570 | if (!$storage->has_access_token_by_session($service_name)) |
571 | { |
572 | return 'LOGIN_LINK_ERROR_OAUTH_NO_ACCESS_TOKEN'; |
573 | } |
574 | |
575 | // Prepare for an authentication request |
576 | $query = 'mode=login_link&login_link_oauth_service=' . $link_data['oauth_service']; |
577 | |
578 | try |
579 | { |
580 | $service = $this->get_service($link_data['oauth_service'], $storage, $query); |
581 | } |
582 | catch (\Exception $e) |
583 | { |
584 | return $e->getMessage(); |
585 | } |
586 | |
587 | $this->service_providers[$service_name]->set_external_service_provider($service); |
588 | |
589 | try |
590 | { |
591 | // The user has already authenticated successfully, request to authenticate again |
592 | $unique_id = $this->service_providers[$service_name]->perform_token_auth(); |
593 | } |
594 | catch (exception $e) |
595 | { |
596 | return $e->getMessage(); |
597 | } |
598 | |
599 | // Insert into table, they will be able to log in after this |
600 | $data = [ |
601 | 'user_id' => $link_data['user_id'], |
602 | 'provider' => utf8_strtolower($link_data['oauth_service']), |
603 | 'oauth_provider_id' => $unique_id, |
604 | ]; |
605 | |
606 | $this->link_account_perform_link($data); |
607 | |
608 | // Update token storage to store the user_id |
609 | $storage->set_user_id($link_data['user_id']); |
610 | |
611 | return false; |
612 | } |
613 | |
614 | /** |
615 | * Performs the account linking for auth_link. |
616 | * |
617 | * @param array $link_data The same variable given to |
618 | * {@see \phpbb\auth\provider\provider_interface::link_account} |
619 | * @param string $service_name The name of the service being used in linking. |
620 | * @return string|false|never Returns a language constant (string) if an error is encountered, |
621 | * an array with error info or false on success. |
622 | */ |
623 | protected function link_account_auth_link(array $link_data, $service_name) |
624 | { |
625 | $storage = new token_storage($this->db, $this->user, $this->oauth_token_table, $this->oauth_state_table); |
626 | $query = 'i=ucp_auth_link&mode=auth_link&link=1&oauth_service=' . $link_data['oauth_service']; |
627 | |
628 | try |
629 | { |
630 | /** @var OAuth1Service|OAuth2Service $service */ |
631 | $service = $this->get_service($link_data['oauth_service'], $storage, $query); |
632 | } |
633 | catch (\Exception $e) |
634 | { |
635 | return $e->getMessage(); |
636 | } |
637 | |
638 | if ($this->is_set_code($service)) |
639 | { |
640 | $this->service_providers[$service_name]->set_external_service_provider($service); |
641 | |
642 | try |
643 | { |
644 | $unique_id = $this->service_providers[$service_name]->perform_auth_login(); |
645 | } |
646 | catch (exception $e) |
647 | { |
648 | return $e->getMessage(); |
649 | } |
650 | |
651 | // Insert into table, they will be able to log in after this |
652 | $data = [ |
653 | 'user_id' => $this->user->data['user_id'], |
654 | 'provider' => utf8_strtolower($link_data['oauth_service']), |
655 | 'oauth_provider_id' => $unique_id, |
656 | ]; |
657 | |
658 | $this->link_account_perform_link($data); |
659 | |
660 | return false; |
661 | } |
662 | else |
663 | { |
664 | $this->set_redirect($service); |
665 | |
666 | return false; // Not reached |
667 | } |
668 | } |
669 | |
670 | /** |
671 | * Performs the query that inserts an account link |
672 | * |
673 | * @param array $data This array is passed to db->sql_build_array |
674 | * @return void |
675 | */ |
676 | protected function link_account_perform_link(array $data) |
677 | { |
678 | // Check if the external account is already associated with other user |
679 | $sql = 'SELECT user_id |
680 | FROM ' . $this->oauth_account_table . " |
681 | WHERE provider = '" . $this->db->sql_escape($data['provider']) . "' |
682 | AND oauth_provider_id = '" . $this->db->sql_escape($data['oauth_provider_id']) . "'"; |
683 | $result = $this->db->sql_query($sql); |
684 | $row = $this->db->sql_fetchrow($result); |
685 | $this->db->sql_freeresult($result); |
686 | |
687 | if ($row) |
688 | { |
689 | trigger_error('AUTH_PROVIDER_OAUTH_ERROR_ALREADY_LINKED'); |
690 | } |
691 | |
692 | // Link account |
693 | $sql = 'INSERT INTO ' . $this->oauth_account_table . ' ' . $this->db->sql_build_array('INSERT', $data); |
694 | $this->db->sql_query($sql); |
695 | |
696 | /** |
697 | * Event is triggered after user links account. |
698 | * |
699 | * @event core.auth_oauth_link_after |
700 | * @var array data User row |
701 | * @since 3.1.11-RC1 |
702 | */ |
703 | $vars = [ |
704 | 'data', |
705 | ]; |
706 | extract($this->dispatcher->trigger_event('core.auth_oauth_link_after', compact($vars))); |
707 | } |
708 | |
709 | /** |
710 | * Returns a new service object. |
711 | * |
712 | * @param string $provider The name of the provider |
713 | * @param token_storage $storage Token storage object |
714 | * @param string $query The query string used for the redirect uri |
715 | * @return ServiceInterface |
716 | * @throws exception When OAuth service was not created |
717 | */ |
718 | protected function get_service($provider, token_storage $storage, $query) |
719 | { |
720 | $service_name = $this->get_service_name($provider); |
721 | |
722 | /** @see \phpbb\auth\provider\oauth\service\service_interface::get_service_credentials */ |
723 | $service_credentials = $this->service_providers[$service_name]->get_service_credentials(); |
724 | |
725 | /** @see \phpbb\auth\provider\oauth\service\service_interface::get_auth_scope */ |
726 | $scopes = $this->service_providers[$service_name]->get_auth_scope(); |
727 | |
728 | $callback = generate_board_url() . "/ucp.{$this->php_ext}?{$query}"; |
729 | |
730 | // Setup the credentials for the requests |
731 | $credentials = new Credentials( |
732 | $service_credentials['key'], |
733 | $service_credentials['secret'], |
734 | $callback |
735 | ); |
736 | |
737 | $service_factory = new ServiceFactory; |
738 | |
739 | // Allow providers to register a custom class or override the provider name |
740 | if ($class = $this->service_providers[$service_name]->get_external_service_class()) |
741 | { |
742 | if (class_exists($class)) |
743 | { |
744 | try |
745 | { |
746 | $service_factory->registerService($provider, $class); |
747 | } |
748 | catch (\OAuth\Common\Exception\Exception $e) |
749 | { |
750 | throw new exception('AUTH_PROVIDER_OAUTH_ERROR_INVALID_SERVICE_TYPE'); |
751 | } |
752 | } |
753 | else |
754 | { |
755 | $provider = $class; |
756 | } |
757 | } |
758 | |
759 | $service = $service_factory->createService($provider, $credentials, $storage, $scopes); |
760 | |
761 | if (!$service) |
762 | { |
763 | throw new exception('AUTH_PROVIDER_OAUTH_ERROR_SERVICE_NOT_CREATED'); |
764 | } |
765 | |
766 | return $service; |
767 | } |
768 | |
769 | /** |
770 | * Returns the service name for an OAuth provider name. |
771 | * |
772 | * @param string $provider The OAuth provider name |
773 | * @return string The service name |
774 | */ |
775 | protected function get_service_name($provider) |
776 | { |
777 | if (strpos($provider, 'auth.provider.oauth.service.') !== 0) |
778 | { |
779 | $provider = 'auth.provider.oauth.service.' . utf8_strtolower($provider); |
780 | } |
781 | |
782 | return $provider; |
783 | } |
784 | |
785 | /** |
786 | * Returns the OAuth provider name from a service name. |
787 | * |
788 | * @param string $service_name The service name |
789 | * @return string The OAuth provider name |
790 | */ |
791 | protected function get_provider($service_name) |
792 | { |
793 | return str_replace('auth.provider.oauth.service.', '', $service_name); |
794 | } |
795 | |
796 | /** |
797 | * Returns the localized title for the OAuth provider. |
798 | * |
799 | * @param string $provider The OAuth provider name |
800 | * @return string The OAuth provider title |
801 | */ |
802 | protected function get_provider_title($provider) |
803 | { |
804 | return $this->language->lang('AUTH_PROVIDER_OAUTH_SERVICE_' . utf8_strtoupper($provider)); |
805 | } |
806 | |
807 | /** |
808 | * Returns whether or not the authorization code is set. |
809 | * |
810 | * @param OAuth1Service|OAuth2Service $service The external OAuth service |
811 | * @return bool Whether or not the authorization code is set in the URL |
812 | * for the respective OAuth service's version |
813 | */ |
814 | protected function is_set_code($service) |
815 | { |
816 | switch ($service::OAUTH_VERSION) |
817 | { |
818 | case 1: |
819 | return $this->request->is_set('oauth_token', request_interface::GET); |
820 | |
821 | case 2: |
822 | return $this->request->is_set('code', request_interface::GET); |
823 | |
824 | default: |
825 | return false; |
826 | } |
827 | } |
828 | |
829 | /** |
830 | * Sets a redirect to the authorization uri. |
831 | * |
832 | * @param OAuth1Service|OAuth2Service $service The external OAuth service |
833 | * @return array|never Array if an error occurred, won't return on success |
834 | */ |
835 | protected function set_redirect($service) |
836 | { |
837 | $parameters = []; |
838 | |
839 | if ($service::OAUTH_VERSION === 1) |
840 | { |
841 | try |
842 | { |
843 | $token = $service->requestRequestToken(); |
844 | $parameters = ['oauth_token' => $token->getRequestToken()]; |
845 | } |
846 | catch (TokenResponseException $e) |
847 | { |
848 | return [ |
849 | 'status' => LOGIN_ERROR_EXTERNAL_AUTH, |
850 | 'error_msg' => $e->getMessage(), |
851 | 'user_row' => ['user_id' => ANONYMOUS], |
852 | ]; |
853 | } |
854 | } |
855 | |
856 | redirect($service->getAuthorizationUri($parameters), false, true); |
857 | |
858 | return []; // Never reached |
859 | } |
860 | } |