Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
56.67% |
119 / 210 |
|
41.67% |
10 / 24 |
CRAP | |
0.00% |
0 / 1 |
token_storage | |
56.67% |
119 / 210 |
|
41.67% |
10 / 24 |
262.64 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
retrieveAccessToken | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
3 | |||
storeAccessToken | |
100.00% |
22 / 22 |
|
100.00% |
1 / 1 |
3 | |||
hasAccessToken | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
3 | |||
clearToken | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
2 | |||
clearAllTokens | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
12 | |||
storeAuthorizationState | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
2 | |||
hasAuthorizationState | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
12 | |||
retrieveAuthorizationState | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
12 | |||
clearAuthorizationState | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
6 | |||
clearAllAuthorizationStates | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
6 | |||
set_user_id | |
90.00% |
9 / 10 |
|
0.00% |
0 / 1 |
2.00 | |||
has_access_token_by_session | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
2 | |||
has_state_by_session | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
6 | |||
has_access_token | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
retrieve_access_token_by_session | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
2 | |||
retrieve_state_by_session | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
6 | |||
_retrieve_access_token | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
3 | |||
_retrieve_state | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
get_access_token_row | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
get_state_row | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
2 | |||
json_encode_token | |
75.00% |
9 / 12 |
|
0.00% |
0 / 1 |
2.06 | |||
json_decode_token | |
73.33% |
11 / 15 |
|
0.00% |
0 / 1 |
3.17 | |||
get_service_name_for_db | |
66.67% |
2 / 3 |
|
0.00% |
0 / 1 |
2.15 |
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\OAuth1\Token\StdOAuth1Token; |
17 | use OAuth\Common\Token\TokenInterface; |
18 | use OAuth\Common\Storage\TokenStorageInterface; |
19 | use OAuth\Common\Storage\Exception\TokenNotFoundException; |
20 | use OAuth\Common\Storage\Exception\AuthorizationStateNotFoundException; |
21 | |
22 | /** |
23 | * OAuth storage wrapper for phpBB's cache |
24 | */ |
25 | class token_storage implements TokenStorageInterface |
26 | { |
27 | /** @var \phpbb\db\driver\driver_interface */ |
28 | protected $db; |
29 | |
30 | /** @var \phpbb\user */ |
31 | protected $user; |
32 | |
33 | /** @var string OAuth table: token storage */ |
34 | protected $oauth_token_table; |
35 | |
36 | /** @var string OAuth table: state */ |
37 | protected $oauth_state_table; |
38 | |
39 | /** @var TokenInterface OAuth token */ |
40 | protected $cachedToken; |
41 | |
42 | /** @var string OAuth state */ |
43 | protected $cachedState; |
44 | |
45 | /** |
46 | * Constructor. |
47 | * |
48 | * @param \phpbb\db\driver\driver_interface $db Database object |
49 | * @param \phpbb\user $user User object |
50 | * @param string $oauth_token_table OAuth table: token storage |
51 | * @param string $oauth_state_table OAuth table: state |
52 | */ |
53 | public function __construct(\phpbb\db\driver\driver_interface $db, \phpbb\user $user, $oauth_token_table, $oauth_state_table) |
54 | { |
55 | $this->db = $db; |
56 | $this->user = $user; |
57 | |
58 | $this->oauth_token_table = $oauth_token_table; |
59 | $this->oauth_state_table = $oauth_state_table; |
60 | } |
61 | |
62 | /** |
63 | * {@inheritdoc} |
64 | */ |
65 | public function retrieveAccessToken($service) |
66 | { |
67 | $service = $this->get_service_name_for_db($service); |
68 | |
69 | if ($this->cachedToken instanceof TokenInterface) |
70 | { |
71 | return $this->cachedToken; |
72 | } |
73 | |
74 | $data = [ |
75 | 'user_id' => (int) $this->user->data['user_id'], |
76 | 'provider' => $service, |
77 | ]; |
78 | |
79 | if ((int) $this->user->data['user_id'] === ANONYMOUS) |
80 | { |
81 | $data['session_id'] = $this->user->data['session_id']; |
82 | } |
83 | |
84 | return $this->_retrieve_access_token($data); |
85 | } |
86 | |
87 | /** |
88 | * {@inheritdoc} |
89 | */ |
90 | public function storeAccessToken($service, TokenInterface $token) |
91 | { |
92 | $service = $this->get_service_name_for_db($service); |
93 | |
94 | $this->cachedToken = $token; |
95 | |
96 | $data = [ |
97 | 'oauth_token' => $this->json_encode_token($token), |
98 | ]; |
99 | |
100 | $sql = 'UPDATE ' . $this->oauth_token_table . ' |
101 | SET ' . $this->db->sql_build_array('UPDATE', $data) . ' |
102 | WHERE user_id = ' . (int) $this->user->data['user_id'] . " |
103 | AND provider = '" . $this->db->sql_escape($service) . "'"; |
104 | |
105 | if ((int) $this->user->data['user_id'] === ANONYMOUS) |
106 | { |
107 | $sql .= " AND session_id = '" . $this->db->sql_escape($this->user->data['session_id']) . "'"; |
108 | } |
109 | |
110 | $this->db->sql_query($sql); |
111 | |
112 | if (!$this->db->sql_affectedrows()) |
113 | { |
114 | $data = [ |
115 | 'user_id' => (int) $this->user->data['user_id'], |
116 | 'provider' => $service, |
117 | 'oauth_token' => $this->json_encode_token($token), |
118 | 'session_id' => $this->user->data['session_id'], |
119 | ]; |
120 | |
121 | $sql = 'INSERT INTO ' . $this->oauth_token_table . $this->db->sql_build_array('INSERT', $data); |
122 | |
123 | $this->db->sql_query($sql); |
124 | } |
125 | |
126 | return $this; |
127 | } |
128 | |
129 | /** |
130 | * {@inheritdoc} |
131 | */ |
132 | public function hasAccessToken($service) |
133 | { |
134 | $service = $this->get_service_name_for_db($service); |
135 | |
136 | if ($this->cachedToken) |
137 | { |
138 | return true; |
139 | } |
140 | |
141 | $data = [ |
142 | 'user_id' => (int) $this->user->data['user_id'], |
143 | 'provider' => $service, |
144 | ]; |
145 | |
146 | if ((int) $this->user->data['user_id'] === ANONYMOUS) |
147 | { |
148 | $data['session_id'] = $this->user->data['session_id']; |
149 | } |
150 | |
151 | return $this->has_access_token($data); |
152 | } |
153 | |
154 | /** |
155 | * {@inheritdoc} |
156 | */ |
157 | public function clearToken($service) |
158 | { |
159 | $service = $this->get_service_name_for_db($service); |
160 | |
161 | $this->cachedToken = null; |
162 | |
163 | $sql = 'DELETE FROM ' . $this->oauth_token_table . ' |
164 | WHERE user_id = ' . (int) $this->user->data['user_id'] . " |
165 | AND provider = '" . $this->db->sql_escape($service) . "'"; |
166 | |
167 | if ((int) $this->user->data['user_id'] === ANONYMOUS) |
168 | { |
169 | $sql .= " AND session_id = '" . $this->db->sql_escape($this->user->data['session_id']) . "'"; |
170 | } |
171 | |
172 | $this->db->sql_query($sql); |
173 | |
174 | return $this; |
175 | } |
176 | |
177 | /** |
178 | * {@inheritdoc} |
179 | */ |
180 | public function clearAllTokens() |
181 | { |
182 | $this->cachedToken = null; |
183 | |
184 | $sql = 'DELETE FROM ' . $this->oauth_token_table . ' |
185 | WHERE user_id = ' . (int) $this->user->data['user_id']; |
186 | |
187 | if ((int) $this->user->data['user_id'] === ANONYMOUS && isset($this->user->data['session_id'])) |
188 | { |
189 | $sql .= " AND session_id = '" . $this->db->sql_escape($this->user->data['session_id']) . "'"; |
190 | } |
191 | |
192 | $this->db->sql_query($sql); |
193 | |
194 | return $this; |
195 | } |
196 | |
197 | /** |
198 | * {@inheritdoc} |
199 | */ |
200 | public function storeAuthorizationState($service, $state) |
201 | { |
202 | $service = $this->get_service_name_for_db($service); |
203 | |
204 | $this->cachedState = $state; |
205 | |
206 | $data = [ |
207 | 'user_id' => (int) $this->user->data['user_id'], |
208 | 'provider' => $service, |
209 | 'oauth_state' => $state, |
210 | 'session_id' => $this->user->data['session_id'], |
211 | ]; |
212 | |
213 | $sql = 'INSERT INTO ' . $this->oauth_state_table . ' ' . $this->db->sql_build_array('INSERT', $data); |
214 | $this->db->sql_query($sql); |
215 | |
216 | return $this; |
217 | } |
218 | |
219 | /** |
220 | * {@inheritdoc} |
221 | */ |
222 | public function hasAuthorizationState($service) |
223 | { |
224 | $service = $this->get_service_name_for_db($service); |
225 | |
226 | if ($this->cachedState) |
227 | { |
228 | return true; |
229 | } |
230 | |
231 | $data = [ |
232 | 'user_id' => (int) $this->user->data['user_id'], |
233 | 'provider' => $service, |
234 | ]; |
235 | |
236 | if ((int) $this->user->data['user_id'] === ANONYMOUS) |
237 | { |
238 | $data['session_id'] = $this->user->data['session_id']; |
239 | } |
240 | |
241 | return (bool) $this->get_state_row($data); |
242 | } |
243 | |
244 | /** |
245 | * {@inheritdoc} |
246 | */ |
247 | public function retrieveAuthorizationState($service) |
248 | { |
249 | $service = $this->get_service_name_for_db($service); |
250 | |
251 | if ($this->cachedState) |
252 | { |
253 | return $this->cachedState; |
254 | } |
255 | |
256 | $data = [ |
257 | 'user_id' => (int) $this->user->data['user_id'], |
258 | 'provider' => $service, |
259 | ]; |
260 | |
261 | if ((int) $this->user->data['user_id'] === ANONYMOUS) |
262 | { |
263 | $data['session_id'] = $this->user->data['session_id']; |
264 | } |
265 | |
266 | return $this->get_state_row($data)['oauth_state'] ?? ''; |
267 | } |
268 | |
269 | /** |
270 | * {@inheritdoc} |
271 | */ |
272 | public function clearAuthorizationState($service) |
273 | { |
274 | $service = $this->get_service_name_for_db($service); |
275 | |
276 | $this->cachedState = null; |
277 | |
278 | $sql = 'DELETE FROM ' . $this->oauth_state_table . ' |
279 | WHERE user_id = ' . (int) $this->user->data['user_id'] . " |
280 | AND provider = '" . $this->db->sql_escape($service) . "'"; |
281 | |
282 | if ((int) $this->user->data['user_id'] === ANONYMOUS) |
283 | { |
284 | $sql .= " AND session_id = '" . $this->db->sql_escape($this->user->data['session_id']) . "'"; |
285 | } |
286 | |
287 | $this->db->sql_query($sql); |
288 | |
289 | return $this; |
290 | } |
291 | |
292 | /** |
293 | * {@inheritdoc} |
294 | */ |
295 | public function clearAllAuthorizationStates() |
296 | { |
297 | $this->cachedState = null; |
298 | |
299 | $sql = 'DELETE FROM ' . $this->oauth_state_table . ' |
300 | WHERE user_id = ' . (int) $this->user->data['user_id']; |
301 | |
302 | if ((int) $this->user->data['user_id'] === ANONYMOUS) |
303 | { |
304 | $sql .= " AND session_id = '" . $this->db->sql_escape($this->user->data['session_id']) . "'"; |
305 | } |
306 | |
307 | $this->db->sql_query($sql); |
308 | |
309 | return $this; |
310 | } |
311 | |
312 | /** |
313 | * Updates the user_id field in the database associated with the token. |
314 | * |
315 | * @param int $user_id The user identifier |
316 | * @return void |
317 | */ |
318 | public function set_user_id($user_id) |
319 | { |
320 | if (!$this->cachedToken) |
321 | { |
322 | return; |
323 | } |
324 | |
325 | $data = [ |
326 | 'user_id' => (int) $user_id, |
327 | ]; |
328 | |
329 | $sql = 'UPDATE ' . $this->oauth_token_table . ' |
330 | SET ' . $this->db->sql_build_array('UPDATE', $data) . ' |
331 | WHERE user_id = ' . (int) $this->user->data['user_id'] . " |
332 | AND session_id = '" . $this->db->sql_escape($this->user->data['session_id']) . "'"; |
333 | $this->db->sql_query($sql); |
334 | } |
335 | |
336 | /** |
337 | * Checks to see if an access token exists solely by the session_id of the user. |
338 | * |
339 | * @param string $service The OAuth service name |
340 | * @return bool true if the user's access token exists, |
341 | * false if the user's access token does not exist |
342 | */ |
343 | public function has_access_token_by_session($service) |
344 | { |
345 | $service = $this->get_service_name_for_db($service); |
346 | |
347 | if ($this->cachedToken) |
348 | { |
349 | return true; |
350 | } |
351 | |
352 | $data = [ |
353 | 'session_id' => $this->user->data['session_id'], |
354 | 'provider' => $service, |
355 | ]; |
356 | |
357 | return $this->has_access_token($data); |
358 | } |
359 | |
360 | /** |
361 | * Checks to see if a state exists solely by the session_id of the user. |
362 | * |
363 | * @param string $service The OAuth service name |
364 | * @return bool true if the user's state exists, |
365 | * false if the user's state does not exist |
366 | */ |
367 | public function has_state_by_session($service) |
368 | { |
369 | $service = $this->get_service_name_for_db($service); |
370 | |
371 | if ($this->cachedState) |
372 | { |
373 | return true; |
374 | } |
375 | |
376 | $data = [ |
377 | 'session_id' => $this->user->data['session_id'], |
378 | 'provider' => $service, |
379 | ]; |
380 | |
381 | return (bool) $this->get_state_row($data); |
382 | } |
383 | |
384 | /** |
385 | * A helper function that performs the query for has access token functions. |
386 | * |
387 | * @param array $data The SQL WHERE data |
388 | * @return bool true if the user's access token exists, |
389 | * false if the user's access token does not exist |
390 | */ |
391 | protected function has_access_token($data) |
392 | { |
393 | return (bool) $this->get_access_token_row($data); |
394 | } |
395 | |
396 | /** |
397 | * A helper function that performs the query for retrieving access token functions by session. |
398 | * Also checks if the token is a valid token. |
399 | * |
400 | * @param string $service The OAuth service provider name |
401 | * @return TokenInterface |
402 | * @throws TokenNotFoundException |
403 | */ |
404 | public function retrieve_access_token_by_session($service) |
405 | { |
406 | $service = $this->get_service_name_for_db($service); |
407 | |
408 | if ($this->cachedToken instanceof TokenInterface) |
409 | { |
410 | return $this->cachedToken; |
411 | } |
412 | |
413 | $data = [ |
414 | 'session_id' => $this->user->data['session_id'], |
415 | 'provider' => $service, |
416 | ]; |
417 | |
418 | return $this->_retrieve_access_token($data); |
419 | } |
420 | |
421 | /** |
422 | * A helper function that performs the query for retrieving state functions by session. |
423 | * |
424 | * @param string $service The OAuth service provider name |
425 | * @return string The OAuth state |
426 | * @throws AuthorizationStateNotFoundException |
427 | */ |
428 | public function retrieve_state_by_session($service) |
429 | { |
430 | $service = $this->get_service_name_for_db($service); |
431 | |
432 | if ($this->cachedState) |
433 | { |
434 | return $this->cachedState; |
435 | } |
436 | |
437 | $data = [ |
438 | 'session_id' => $this->user->data['session_id'], |
439 | 'provider' => $service, |
440 | ]; |
441 | |
442 | return $this->_retrieve_state($data); |
443 | } |
444 | |
445 | /** |
446 | * A helper function that performs the query for retrieve access token functions. |
447 | * Also checks if the token is a valid token. |
448 | * |
449 | * @param array $data The SQL WHERE data |
450 | * @return TokenInterface |
451 | * @throws TokenNotFoundException |
452 | */ |
453 | protected function _retrieve_access_token($data) |
454 | { |
455 | $row = $this->get_access_token_row($data); |
456 | |
457 | if (!$row) |
458 | { |
459 | throw new TokenNotFoundException('AUTH_PROVIDER_OAUTH_TOKEN_ERROR_NOT_STORED'); |
460 | } |
461 | |
462 | $token = $this->json_decode_token($row['oauth_token']); |
463 | |
464 | // Ensure that the token was serialized/unserialized correctly |
465 | if (!($token instanceof TokenInterface)) |
466 | { |
467 | $this->clearToken($data['provider']); |
468 | |
469 | throw new TokenNotFoundException('AUTH_PROVIDER_OAUTH_TOKEN_ERROR_INCORRECTLY_STORED'); |
470 | } |
471 | |
472 | $this->cachedToken = $token; |
473 | |
474 | return $token; |
475 | } |
476 | |
477 | /** |
478 | * A helper function that performs the query for retrieve state functions. |
479 | * |
480 | * @param array $data The SQL WHERE data |
481 | * @return string The OAuth state |
482 | * @throws AuthorizationStateNotFoundException |
483 | */ |
484 | protected function _retrieve_state($data) |
485 | { |
486 | $row = $this->get_state_row($data); |
487 | |
488 | if (!$row) |
489 | { |
490 | throw new AuthorizationStateNotFoundException(); |
491 | } |
492 | |
493 | $this->cachedState = $row['oauth_state']; |
494 | |
495 | return $this->cachedState; |
496 | } |
497 | |
498 | /** |
499 | * A helper function that performs the query for retrieving an access token. |
500 | * |
501 | * @param array $data The SQL WHERE data |
502 | * @return array|false array with the OAuth token row, |
503 | * false if the token does not exist |
504 | */ |
505 | protected function get_access_token_row($data) |
506 | { |
507 | $sql = 'SELECT oauth_token |
508 | FROM ' . $this->oauth_token_table . ' |
509 | WHERE ' . $this->db->sql_build_array('SELECT', $data); |
510 | $result = $this->db->sql_query($sql); |
511 | $row = $this->db->sql_fetchrow($result); |
512 | $this->db->sql_freeresult($result); |
513 | |
514 | return $row; |
515 | } |
516 | |
517 | /** |
518 | * A helper function that performs the query for retrieving a state. |
519 | * |
520 | * @param array $data The SQL WHERE data |
521 | * @return array|false array with the OAuth state row, |
522 | * false if the state does not exist |
523 | */ |
524 | protected function get_state_row($data) |
525 | { |
526 | $sql = 'SELECT oauth_state |
527 | FROM ' . $this->oauth_state_table . ' |
528 | WHERE ' . $this->db->sql_build_array('SELECT', $data); |
529 | $result = $this->db->sql_query($sql); |
530 | $row = $this->db->sql_fetchrow($result); |
531 | $this->db->sql_freeresult($result); |
532 | |
533 | return $row; |
534 | } |
535 | |
536 | /** |
537 | * A helper function that JSON encodes a TokenInterface's data. |
538 | * |
539 | * @param TokenInterface $token |
540 | * @return string The json encoded TokenInterface's data |
541 | */ |
542 | public function json_encode_token(TokenInterface $token) |
543 | { |
544 | $members = [ |
545 | 'accessToken' => $token->getAccessToken(), |
546 | 'endOfLife' => $token->getEndOfLife(), |
547 | 'extraParams' => $token->getExtraParams(), |
548 | 'refreshToken' => $token->getRefreshToken(), |
549 | |
550 | 'token_class' => get_class($token), |
551 | ]; |
552 | |
553 | // Handle additional data needed for OAuth1 tokens |
554 | if ($token instanceof StdOAuth1Token) |
555 | { |
556 | $members['requestToken'] = $token->getRequestToken(); |
557 | $members['requestTokenSecret'] = $token->getRequestTokenSecret(); |
558 | $members['accessTokenSecret'] = $token->getAccessTokenSecret(); |
559 | } |
560 | |
561 | return json_encode($members); |
562 | } |
563 | |
564 | /** |
565 | * A helper function that JSON decodes a data string and creates a TokenInterface. |
566 | * |
567 | * @param string $json The json encoded TokenInterface's data |
568 | * @return TokenInterface |
569 | * @throws TokenNotFoundException |
570 | */ |
571 | public function json_decode_token($json) |
572 | { |
573 | $token_data = json_decode($json, true); |
574 | |
575 | if ($token_data === null) |
576 | { |
577 | throw new TokenNotFoundException('AUTH_PROVIDER_OAUTH_TOKEN_ERROR_INCORRECTLY_STORED'); |
578 | } |
579 | |
580 | $token_class = $token_data['token_class']; |
581 | $access_token = $token_data['accessToken']; |
582 | $refresh_token = $token_data['refreshToken']; |
583 | $endOfLife = $token_data['endOfLife']; |
584 | $extra_params = $token_data['extraParams']; |
585 | |
586 | /** |
587 | * Create the token |
588 | * @var TokenInterface $token |
589 | */ |
590 | $token = new $token_class($access_token, $refresh_token, TokenInterface::EOL_NEVER_EXPIRES, $extra_params); |
591 | $token->setEndOfLife($endOfLife); |
592 | |
593 | // Handle OAuth 1.0 specific elements |
594 | if ($token instanceof StdOAuth1Token) |
595 | { |
596 | $token->setRequestToken($token_data['requestToken']); |
597 | $token->setRequestTokenSecret($token_data['requestTokenSecret']); |
598 | $token->setAccessTokenSecret($token_data['accessTokenSecret']); |
599 | } |
600 | |
601 | return $token; |
602 | } |
603 | |
604 | /** |
605 | * Returns the service name as it must be stored in the database. |
606 | * |
607 | * @param string $provider The OAuth provider name |
608 | * @return string The OAuth service name |
609 | */ |
610 | protected function get_service_name_for_db($provider) |
611 | { |
612 | // Enforce the naming convention for oauth services |
613 | if (strpos($provider, 'auth.provider.oauth.service.') !== 0) |
614 | { |
615 | $provider = 'auth.provider.oauth.service.' . strtolower($provider); |
616 | } |
617 | |
618 | return $provider; |
619 | } |
620 | } |