| Total Complexity | 55 |
| Total Lines | 586 |
| Duplicated Lines | 0 % |
| Changes | 3 | ||
| Bugs | 0 | Features | 0 |
Complex classes like RApiOauth2Oauth2 often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use RApiOauth2Oauth2, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 17 | class RApiOauth2Oauth2 extends RApi |
||
| 18 | { |
||
| 19 | /** |
||
| 20 | * Option name parameter |
||
| 21 | * @var array |
||
| 22 | */ |
||
| 23 | public $optionName = null; |
||
| 24 | |||
| 25 | /** |
||
| 26 | * Main OAuth2 Server object |
||
| 27 | * @var OAuth2\Server |
||
| 28 | */ |
||
| 29 | public $server = null; |
||
| 30 | |||
| 31 | /** |
||
| 32 | * Main OAuth2 Server configuration |
||
| 33 | * @var array |
||
| 34 | */ |
||
| 35 | public $serverConfig = null; |
||
| 36 | |||
| 37 | /** |
||
| 38 | * Result of OAuth2 Server response |
||
| 39 | * @var OAuth2\ResponseInterface |
||
| 40 | */ |
||
| 41 | public $response = null; |
||
| 42 | |||
| 43 | /** |
||
| 44 | * Method to instantiate the file-based api call. |
||
| 45 | * |
||
| 46 | * @param mixed $options Optional custom options to load. JRegistry or array format |
||
| 47 | * |
||
| 48 | * @since 1.2 |
||
| 49 | */ |
||
| 50 | public function __construct($options = null) |
||
| 51 | { |
||
| 52 | parent::__construct($options); |
||
| 53 | |||
| 54 | // Get the global JAuthentication object. |
||
| 55 | jimport('joomla.user.authentication'); |
||
| 56 | |||
| 57 | // Register OAuth2 classes |
||
| 58 | require_once dirname(__FILE__) . '/Autoloader.php'; |
||
| 59 | OAuth2\Autoloader::register(); |
||
| 60 | |||
| 61 | // OAuth2 Server config from plugin |
||
| 62 | $this->serverConfig = array( |
||
| 63 | 'use_jwt_access_tokens' => (boolean) RBootstrap::getConfig('oauth2_use_jwt_access_tokens', false), |
||
| 64 | 'store_encrypted_token_string' => (boolean) RBootstrap::getConfig('oauth2_store_encrypted_token_string', true), |
||
| 65 | 'use_openid_connect' => (boolean) RBootstrap::getConfig('oauth2_use_openid_connect', false), |
||
| 66 | 'id_lifetime' => RBootstrap::getConfig('oauth2_id_lifetime', 3600), |
||
| 67 | 'access_lifetime' => RBootstrap::getConfig('oauth2_access_lifetime', 3600), |
||
| 68 | 'www_realm' => 'Service', |
||
| 69 | 'token_param_name' => RBootstrap::getConfig('oauth2_token_param_name', 'access_token'), |
||
| 70 | 'token_bearer_header_name' => RBootstrap::getConfig('oauth2_token_bearer_header_name', 'Bearer'), |
||
| 71 | 'enforce_state' => (boolean) RBootstrap::getConfig('oauth2_enforce_state', true), |
||
| 72 | 'require_exact_redirect_uri' => (boolean) RBootstrap::getConfig('oauth2_require_exact_redirect_uri', true), |
||
| 73 | 'allow_implicit' => (boolean) RBootstrap::getConfig('oauth2_allow_implicit', false), |
||
| 74 | 'allow_credentials_in_request_body' => (boolean) RBootstrap::getConfig('oauth2_allow_credentials_in_request_body', true), |
||
| 75 | 'allow_public_clients' => (boolean) RBootstrap::getConfig('oauth2_allow_public_clients', true), |
||
| 76 | 'always_issue_new_refresh_token' => (boolean) RBootstrap::getConfig('oauth2_always_issue_new_refresh_token', false), |
||
| 77 | 'unset_refresh_token_after_use' => (boolean) RBootstrap::getConfig('oauth2_unset_refresh_token_after_use', true), |
||
| 78 | 'issuer' => $_SERVER['HTTP_HOST'], |
||
| 79 | ); |
||
| 80 | |||
| 81 | // Set database names to Redcore DB tables |
||
| 82 | $prefix = JFactory::getDbo()->getPrefix(); |
||
| 83 | $databaseConfig = array( |
||
| 84 | 'client_table' => $prefix . 'redcore_oauth_clients', |
||
| 85 | 'access_token_table' => $prefix . 'redcore_oauth_access_tokens', |
||
| 86 | 'refresh_token_table' => $prefix . 'redcore_oauth_refresh_tokens', |
||
| 87 | 'code_table' => $prefix . 'redcore_oauth_authorization_codes', |
||
| 88 | 'user_table' => $prefix . 'redcore_oauth_users', |
||
| 89 | 'jwt_table' => $prefix . 'redcore_oauth_jwt', |
||
| 90 | 'jti_table' => $prefix . 'redcore_oauth_jti', |
||
| 91 | 'scope_table' => $prefix . 'redcore_oauth_scopes', |
||
| 92 | 'public_key_table' => $prefix . 'redcore_oauth_public_keys', |
||
| 93 | ); |
||
| 94 | |||
| 95 | $conf = JFactory::getConfig(); |
||
| 96 | |||
| 97 | $storage = new OAuth2\Storage\Pdoredcore([ |
||
| 98 | 'dsn' => 'mysql:dbname=' . $conf->get('db') . ';host=' . $conf->get('host'), |
||
| 99 | 'username' => $conf->get('user'), |
||
| 100 | 'password' => $conf->get('password') |
||
| 101 | ], |
||
| 102 | $databaseConfig |
||
| 103 | ); |
||
| 104 | $this->server = new OAuth2\Server($storage, $this->serverConfig); |
||
| 105 | |||
| 106 | if (RBootstrap::getConfig('public_key') |
||
| 107 | && JFile::exists(RBootstrap::getConfig('public_key')) |
||
| 108 | && RBootstrap::getConfig('private_key') |
||
| 109 | && JFile::exists(RBootstrap::getConfig('private_key'))) |
||
| 110 | { |
||
| 111 | $this->server->addStorage( |
||
| 112 | new OAuth2\Storage\Memory([ |
||
| 113 | 'keys' => [ |
||
| 114 | 'public_key' => file_get_contents(RBootstrap::getConfig('public_key')), |
||
|
|
|||
| 115 | 'private_key' => file_get_contents(RBootstrap::getConfig('private_key')), |
||
| 116 | ] |
||
| 117 | ] |
||
| 118 | ), |
||
| 119 | 'public_key' |
||
| 120 | ); |
||
| 121 | } |
||
| 122 | |||
| 123 | // Add the "Authorization Code" grant type (this is where the oauth magic happens) |
||
| 124 | $this->server->addGrantType(new OAuth2\GrantType\AuthorizationCode($storage, $this->serverConfig)); |
||
| 125 | |||
| 126 | // Add the "Client Credentials" grant type (it is the simplest of the grant types) |
||
| 127 | $this->server->addGrantType(new OAuth2\GrantType\ClientCredentials($storage, $this->serverConfig)); |
||
| 128 | |||
| 129 | // Add the "User Credentials" grant type (this is modified to suit Joomla authorization) |
||
| 130 | $this->server->addGrantType(new OAuth2\GrantType\UserCredentials($storage, $this->serverConfig)); |
||
| 131 | |||
| 132 | // Add the "Refresh Token" grant type (this is great for extending expiration time on tokens) |
||
| 133 | $this->server->addGrantType(new OAuth2\GrantType\RefreshToken($storage, $this->serverConfig)); |
||
| 134 | |||
| 135 | /* |
||
| 136 | * @todo Implement JwtBearer Grant type with public_key |
||
| 137 | // Typically, the URI of the oauth server |
||
| 138 | $audience = rtrim(JUri::base(), '/'); |
||
| 139 | |||
| 140 | // Add the "Refresh Token" grant type (this is great for extending expiration time on tokens) |
||
| 141 | $this->server->addGrantType(new OAuth2\GrantType\JwtBearer($storage, $audience)); |
||
| 142 | */ |
||
| 143 | |||
| 144 | // Init Environment |
||
| 145 | $this->setApiOperation(); |
||
| 146 | } |
||
| 147 | |||
| 148 | /** |
||
| 149 | * Set Method for Api |
||
| 150 | * |
||
| 151 | * @param string $operation Operation name |
||
| 152 | * |
||
| 153 | * @return RApi |
||
| 154 | * |
||
| 155 | * @since 1.2 |
||
| 156 | */ |
||
| 157 | public function setApiOperation($operation = '') |
||
| 158 | { |
||
| 159 | if (!empty($operation)) |
||
| 160 | { |
||
| 161 | $this->options->set('optionName', $operation); |
||
| 162 | } |
||
| 163 | |||
| 164 | $this->operation = strtolower($this->options->get('optionName', '')); |
||
| 165 | |||
| 166 | return $this; |
||
| 167 | } |
||
| 168 | |||
| 169 | /** |
||
| 170 | * Execute the Api operation. |
||
| 171 | * |
||
| 172 | * @return mixed RApi object with information on success, boolean false on failure. |
||
| 173 | * |
||
| 174 | * @since 1.2 |
||
| 175 | * @throws RuntimeException |
||
| 176 | */ |
||
| 177 | public function execute() |
||
| 178 | { |
||
| 179 | if (!$this->isOperationAllowed()) |
||
| 180 | { |
||
| 181 | throw new RuntimeException(JText::_('LIB_REDCORE_API_HAL_OPERATION_NOT_ALLOWED')); |
||
| 182 | } |
||
| 183 | |||
| 184 | switch ($this->operation) |
||
| 185 | { |
||
| 186 | case 'token': |
||
| 187 | $this->apiToken(); |
||
| 188 | $this->cleanExpiredTokens($this->operation); |
||
| 189 | break; |
||
| 190 | case 'resource': |
||
| 191 | $this->apiResource(); |
||
| 192 | break; |
||
| 193 | case 'authorize': |
||
| 194 | $this->apiAuthorize(); |
||
| 195 | $this->cleanExpiredTokens($this->operation); |
||
| 196 | break; |
||
| 197 | case 'profile': |
||
| 198 | $this->apiProfile(); |
||
| 199 | break; |
||
| 200 | } |
||
| 201 | |||
| 202 | return $this; |
||
| 203 | } |
||
| 204 | |||
| 205 | /** |
||
| 206 | * Method to update client scopes. |
||
| 207 | * |
||
| 208 | * @param string $operation Operation name |
||
| 209 | * |
||
| 210 | * @return boolean True on success, False on error. |
||
| 211 | */ |
||
| 212 | public function cleanExpiredTokens($operation) |
||
| 213 | { |
||
| 214 | $db = JFactory::getDbo(); |
||
| 215 | $query = $db->getQuery(true); |
||
| 216 | |||
| 217 | switch ($operation) |
||
| 218 | { |
||
| 219 | // Delete Access Tokens |
||
| 220 | case 'token': |
||
| 221 | $query->delete('#__redcore_oauth_access_tokens') |
||
| 222 | ->where($db->qn('expires') . ' < ' . $db->q(date('Y-m-d H:i:s', strtotime('-2 weeks')))); |
||
| 223 | $db->setQuery($query); |
||
| 224 | $db->execute(); |
||
| 225 | |||
| 226 | $query = $db->getQuery(true) |
||
| 227 | ->delete('#__redcore_oauth_refresh_tokens') |
||
| 228 | ->where($db->qn('expires') . ' < ' . $db->q(date('Y-m-d H:i:s', strtotime('-2 weeks')))); |
||
| 229 | $db->setQuery($query); |
||
| 230 | $db->execute(); |
||
| 231 | break; |
||
| 232 | |||
| 233 | // Delete Authorization codes |
||
| 234 | case 'authorize': |
||
| 235 | default: |
||
| 236 | $query->delete('#__redcore_oauth_authorization_codes') |
||
| 237 | ->where($db->qn('expires') . ' < ' . $db->q(date('Y-m-d H:i:s', strtotime('-2 weeks')))); |
||
| 238 | $db->setQuery($query); |
||
| 239 | $db->execute(); |
||
| 240 | break; |
||
| 241 | } |
||
| 242 | |||
| 243 | return true; |
||
| 244 | } |
||
| 245 | |||
| 246 | /** |
||
| 247 | * Execute the Api Profile operation. |
||
| 248 | * |
||
| 249 | * @return mixed RApi object with information on success, boolean false on failure. |
||
| 250 | * |
||
| 251 | * @since 1.7 |
||
| 252 | */ |
||
| 253 | public function apiProfile() |
||
| 283 | } |
||
| 284 | |||
| 285 | /** |
||
| 286 | * Loads up user identity when requested |
||
| 287 | * |
||
| 288 | * @param int $userId User ID |
||
| 289 | * |
||
| 290 | * @return mixed RApi object with information on success, boolean false on failure. |
||
| 291 | * |
||
| 292 | * @since 1.8 |
||
| 293 | */ |
||
| 294 | public function loadUserIdentity($userId) |
||
| 306 | } |
||
| 307 | |||
| 308 | /** |
||
| 309 | * Execute the Api Token operation. |
||
| 310 | * |
||
| 311 | * @return mixed RApi object with information on success, boolean false on failure. |
||
| 312 | * |
||
| 313 | * @since 1.2 |
||
| 314 | */ |
||
| 315 | public function apiToken() |
||
| 316 | { |
||
| 317 | $request = OAuth2\Request::createFromGlobals(); |
||
| 318 | |||
| 319 | // Implicit grant type and Authorization code grant type require user to be logged in before authorising |
||
| 320 | if ($request->request('grant_type') == 'implicit') |
||
| 321 | { |
||
| 322 | $user = $this->getLoggedUser(); |
||
| 323 | } |
||
| 324 | |||
| 325 | $this->response = $this->server->handleTokenRequest($request); |
||
| 326 | |||
| 327 | if ($this->response instanceof OAuth2\Response) |
||
| 328 | { |
||
| 329 | $expires = $this->response->getParameter('expires_in'); |
||
| 330 | |||
| 331 | if ($expires) |
||
| 332 | { |
||
| 333 | // Add expire Time |
||
| 334 | $this->response->setParameter('expireTimeFormatted', date('Y-m-d H:i:s', $expires + time())); |
||
| 335 | $this->response->setParameter('created', date('Y-m-d H:i:s')); |
||
| 336 | } |
||
| 337 | } |
||
| 338 | |||
| 339 | return $this; |
||
| 340 | } |
||
| 341 | |||
| 342 | /** |
||
| 343 | * Execute the Api Resource operation. |
||
| 344 | * |
||
| 345 | * @return mixed RApi object with information on success, boolean false on failure. |
||
| 346 | * |
||
| 347 | * @since 1.2 |
||
| 348 | */ |
||
| 349 | public function apiResource() |
||
| 409 | } |
||
| 410 | |||
| 411 | /** |
||
| 412 | * Execute the Api Authorize operation. |
||
| 413 | * |
||
| 414 | * @return mixed RApi object with information on success, boolean false on failure. |
||
| 415 | * |
||
| 416 | * @since 1.2 |
||
| 417 | */ |
||
| 418 | public function apiAuthorize() |
||
| 419 | { |
||
| 420 | $user = $this->getLoggedUser(); |
||
| 421 | $request = OAuth2\Request::createFromGlobals(); |
||
| 422 | $response = new OAuth2\Response; |
||
| 423 | |||
| 424 | // Validate the authorize request |
||
| 425 | if (!$this->server->validateAuthorizeRequest($request, $response)) |
||
| 426 | { |
||
| 427 | $this->response = $response; |
||
| 428 | |||
| 429 | return $this; |
||
| 430 | } |
||
| 431 | |||
| 432 | $clientId = $request->query('client_id'); |
||
| 433 | $scopes = RApiOauth2Helper::getClientScopes($clientId); |
||
| 434 | |||
| 435 | if ($request->request('authorized', '') == '') |
||
| 436 | { |
||
| 437 | $clientScopes = !empty($scopes) ? explode(' ', $scopes) : array(); |
||
| 438 | |||
| 439 | if (!empty($clientScopes)) |
||
| 440 | { |
||
| 441 | $clientScopes = RApiHalHelper::getWebserviceScopes($clientScopes); |
||
| 442 | } |
||
| 443 | |||
| 444 | $currentUri = JUri::getInstance(); |
||
| 445 | $formAction = JUri::root() . 'index.php?' . $currentUri->getQuery(); |
||
| 446 | |||
| 447 | // Display an authorization form |
||
| 448 | $this->response = RLayoutHelper::render( |
||
| 449 | 'oauth2.authorize', |
||
| 450 | array( |
||
| 451 | 'view' => $this, |
||
| 452 | 'options' => array ( |
||
| 453 | 'clientId' => $clientId, |
||
| 454 | 'formAction' => $formAction, |
||
| 455 | 'scopes' => $clientScopes, |
||
| 456 | ) |
||
| 457 | ) |
||
| 458 | ); |
||
| 459 | |||
| 460 | return $this; |
||
| 461 | } |
||
| 462 | |||
| 463 | // Print the authorization code if the user has authorized your client |
||
| 464 | $is_authorized = $request->request('authorized', '') === JText::_('LIB_REDCORE_API_OAUTH2_SERVER_AUTHORIZE_CLIENT_YES') |
||
| 465 | || $request->request('authorized', '') == '1'; |
||
| 466 | |||
| 467 | // We are setting client scope instead of requesting scope from user request |
||
| 468 | $request->request['scope'] = $scopes; |
||
| 469 | $this->server->handleAuthorizeRequest($request, $response, $is_authorized, $user->id); |
||
| 470 | |||
| 471 | // We add access_token directly to the URI of the redirect string (default is after the # character) |
||
| 472 | if (RBootstrap::getConfig('oauth2_redirect_with_token', 0)) |
||
| 473 | { |
||
| 474 | if ($response->isRedirection()) |
||
| 475 | { |
||
| 476 | $location = $response->getHttpHeader('Location'); |
||
| 477 | $location = explode('#', $location); |
||
| 478 | |||
| 479 | // Get access token |
||
| 480 | if (isset($location[1])) |
||
| 481 | { |
||
| 482 | $location[1] = str_replace('&', '&', $location[1]); |
||
| 483 | $uris = explode('&', $location[1]); |
||
| 484 | |||
| 485 | // We search for access_token parameter |
||
| 486 | foreach ($uris as $uri) |
||
| 487 | { |
||
| 488 | if (strpos($uri, RBootstrap::getConfig('oauth2_token_param_name', 'access_token')) === 0) |
||
| 489 | { |
||
| 490 | $location[0] .= '&' . $uri; |
||
| 491 | break; |
||
| 492 | } |
||
| 493 | } |
||
| 494 | |||
| 495 | $location = implode('#', $location); |
||
| 496 | $response->setHttpHeader('Location', $location); |
||
| 497 | } |
||
| 498 | } |
||
| 499 | } |
||
| 500 | |||
| 501 | $this->response = $response; |
||
| 502 | |||
| 503 | if (RBootstrap::getConfig('oauth2_joomla_logout_right_after_authorize', false)) |
||
| 504 | { |
||
| 505 | JFactory::getApplication()->logout($user->id); |
||
| 506 | } |
||
| 507 | |||
| 508 | return $this; |
||
| 509 | } |
||
| 510 | |||
| 511 | /** |
||
| 512 | * Checks if operation is allowed from the configuration file |
||
| 513 | * |
||
| 514 | * @return object This method may be chained. |
||
| 515 | * |
||
| 516 | * @throws RuntimeException |
||
| 517 | */ |
||
| 518 | public function isOperationAllowed() |
||
| 519 | { |
||
| 520 | if (empty($this->operation)) |
||
| 521 | { |
||
| 522 | throw new RuntimeException(JText::_('LIB_REDCORE_API_OAUTH2_OPERATION_NOT_SPECIFIED')); |
||
| 523 | } |
||
| 524 | |||
| 525 | return true; |
||
| 526 | } |
||
| 527 | |||
| 528 | /** |
||
| 529 | * Gets logged In user or redirect to login page |
||
| 530 | * |
||
| 531 | * @return JUser Instance of the logged in user |
||
| 532 | */ |
||
| 533 | public function getLoggedUser() |
||
| 534 | { |
||
| 535 | $user = JFactory::getUser(); |
||
| 536 | |||
| 537 | // If user is not logged in we redirect him to the login page |
||
| 538 | if (empty($user->id)) |
||
| 539 | { |
||
| 540 | $currentUri = JUri::getInstance(); |
||
| 541 | $returnUrl = JUri::root() . 'index.php?' . $currentUri->getQuery(); |
||
| 542 | |||
| 543 | $loginLink = RRoute::_(JUri::root() . 'index.php?option=com_users&view=login'); |
||
| 544 | |||
| 545 | $loginPage = new JUri($loginLink); |
||
| 546 | $loginPage->setVar('return', base64_encode($returnUrl)); |
||
| 547 | |||
| 548 | JFactory::getApplication()->redirect($loginPage); |
||
| 549 | JFactory::getApplication()->close(); |
||
| 550 | } |
||
| 551 | |||
| 552 | return $user; |
||
| 553 | } |
||
| 554 | |||
| 555 | /** |
||
| 556 | * Method to send the application response to the client. All headers will be sent prior to the main |
||
| 557 | * application output data. |
||
| 558 | * |
||
| 559 | * @return void |
||
| 560 | * |
||
| 561 | * @since 1.2 |
||
| 562 | */ |
||
| 563 | public function render() |
||
| 564 | { |
||
| 565 | if ($this->response instanceof OAuth2\ResponseInterface) |
||
| 566 | { |
||
| 567 | $this->response->send(); |
||
| 568 | } |
||
| 569 | else |
||
| 570 | { |
||
| 571 | $app = JFactory::getApplication(); |
||
| 572 | |||
| 573 | $body = $this->response; |
||
| 574 | |||
| 575 | // Check if the request is CORS ( Cross-origin resource sharing ) and change the body if true |
||
| 576 | $body = $this->prepareBody($body); |
||
| 577 | |||
| 578 | json_decode((string) $body); |
||
| 579 | |||
| 580 | if (json_last_error() == JSON_ERROR_NONE) |
||
| 581 | { |
||
| 582 | $app->setHeader('Content-length', strlen($body), true); |
||
| 583 | $app->setHeader('Content-type', 'application/json; charset=UTF-8', true); |
||
| 584 | $app->sendHeaders(); |
||
| 585 | } |
||
| 586 | |||
| 587 | echo (string) $body; |
||
| 588 | } |
||
| 589 | } |
||
| 590 | |||
| 591 | /** |
||
| 592 | * Prepares body for response |
||
| 593 | * |
||
| 594 | * @param string $message The return message |
||
| 595 | * |
||
| 596 | * @return string The message prepared |
||
| 597 | * |
||
| 598 | * @since 1.2 |
||
| 599 | */ |
||
| 600 | public function prepareBody($message) |
||
| 603 | } |
||
| 604 | } |
||
| 605 |