From 9c4215b1e28f428539ea2a334c4e5a85f2fdc3d0 Mon Sep 17 00:00:00 2001 From: Iqbal Atma Muliawan Date: Wed, 1 Nov 2023 18:39:34 +0700 Subject: [PATCH] refactor sso driver with separate class --- src/Abstracts/BaseMumtazSSOService.php | 66 ---------- src/AuthService.php | 38 ------ src/HttpClientService.php | 99 ++++++++++++++ src/Interfaces/HttpClient.php | 53 ++++++++ src/Interfaces/MumtazSSOServiceInterface.php | 15 --- src/Interfaces/OauthToken.php | 31 +++++ src/Interfaces/SSO.php | 29 ++++ src/MumtazSSODriverServiceProvider.php | 26 +++- src/MumtazSSOService.php | 132 ------------------- src/OauthTokenService.php | 109 +++++++++++++++ src/SSOService.php | 75 +++++++++++ src/Services/AuthService.php | 39 ++++++ 12 files changed, 459 insertions(+), 253 deletions(-) delete mode 100644 src/Abstracts/BaseMumtazSSOService.php delete mode 100644 src/AuthService.php create mode 100644 src/HttpClientService.php create mode 100644 src/Interfaces/HttpClient.php delete mode 100644 src/Interfaces/MumtazSSOServiceInterface.php create mode 100644 src/Interfaces/OauthToken.php create mode 100644 src/Interfaces/SSO.php delete mode 100644 src/MumtazSSOService.php create mode 100644 src/OauthTokenService.php create mode 100644 src/SSOService.php create mode 100644 src/Services/AuthService.php diff --git a/src/Abstracts/BaseMumtazSSOService.php b/src/Abstracts/BaseMumtazSSOService.php deleted file mode 100644 index 3e4886a..0000000 --- a/src/Abstracts/BaseMumtazSSOService.php +++ /dev/null @@ -1,66 +0,0 @@ -headers = [ - "Accept" => "application/json" - ]; - $this->baseUrl = config("services.mumtaz_sso_client_host"); - $this->currentRegenerateAttempt = 0; - } - - - /** - * @param array $headers - * @return $this - */ - public function addHeaders(array $headers):BaseMumtazSSOService - { - $this->headers = array_merge($this->headers, $headers); - - return $this; - } - - /** - * @return array|string[] - */ - public function getHeaders(): array - { - return $this->headers; - } - - - /** - * @return string - */ - public function getBaseUrl(): string - { - return $this->baseUrl; - } - - /** - * description : use to reduce value by percentage - * (50, 0.2) => 50 - (50 * 0.2) - * => 50 - 10 - * => 40 - * @param int $value - * @param float $percentage - * @return float - */ - public static function reduceValueByPercentage(int $value, float $percentage): float - { - return $value - ($value * $percentage); - } -} diff --git a/src/AuthService.php b/src/AuthService.php deleted file mode 100644 index a232ad6..0000000 --- a/src/AuthService.php +++ /dev/null @@ -1,38 +0,0 @@ -mumtazSSOService = new MumtazSSOService(); - } - - /** - * @param array $credentials - * @return mixed - * @throws InvalidClientCredentials - * @throws InvalidRetryGenerateException - * @throws SSODriverException - * @throws UnknownErrorHandlerException - */ - public function authenticate(array $credentials): mixed - { - return $this->mumtazSSOService->setAuthorizationToken() - ->getResponse(function (MumtazSSOService $service) use ($credentials) { - return Http::withHeaders($service->getHeaders())->post($service->getBaseUrl() . "/api/v1/auth", [ - "username" => $credentials["username"], - "password" => $credentials["password"], - "institution_id" => $credentials["institution_id"], //todo : hardcode for integration testing - ]); - }); - } -} diff --git a/src/HttpClientService.php b/src/HttpClientService.php new file mode 100644 index 0000000..6b719af --- /dev/null +++ b/src/HttpClientService.php @@ -0,0 +1,99 @@ +setDefaultHttpRequestHeader() + ->setBaseUrl(); + } + + /** + * @return HttpClientService + */ + public function setBaseUrl(): HttpClientService + { + $this->baseUrl = rtrim(config("mumtaz_sso_driver.host"), "/"); + return $this; + } + + /** + * @return string + */ + public function getBaseUrl(): string + { + return $this->baseUrl; + } + + + /** + * @return HttpClientService + */ + public function setDefaultHttpRequestHeader(): HttpClientService + { + $this->httpRequestHeaders = [ + "Accept" => "application/json" + ]; + + return $this; + } + + + /** + * @param array $httpRequestHeaders + * @return HttpClientService + */ + public function setHttpRequestHeaders(array $httpRequestHeaders): HttpClientService + { + $this->httpRequestHeaders = $httpRequestHeaders; + return $this; + } + + + /** + * @param string $key + * @param string|array $value + * @return HttpClientService + */ + public function addHttpRequestHeader(string $key, string|array $value): HttpClientService + { + $this->httpRequestHeaders[$key] = $value; + return $this; + } + + + /** + * @param array $httpRequestHeaders + * @return HttpClientService + */ + public function addHttpRequestHeaders(array $httpRequestHeaders): HttpClientService + { + $this->httpRequestHeaders = array_merge($this->httpRequestHeaders, $httpRequestHeaders); + return $this; + } + + + /** + * @return array + */ + public function getHttpRequestHeaders(): array + { + return $this->httpRequestHeaders; + } + + + /** + * @param string $key + * @return array + */ + public function getHttpRequestHeader(string $key): array + { + return $this->httpRequestHeaders[$key]; + } +} diff --git a/src/Interfaces/HttpClient.php b/src/Interfaces/HttpClient.php new file mode 100644 index 0000000..b8594b8 --- /dev/null +++ b/src/Interfaces/HttpClient.php @@ -0,0 +1,53 @@ +publishes([ - __DIR__."/Config/mumtaz_sso_driver.php" => config_path("mumtaz_sso_driver.php") + __DIR__ . "/Config/mumtaz_sso_driver.php" => config_path("mumtaz_sso_driver.php") ]); - $this->mergeConfigFrom(__DIR__."/Config/mumtaz_sso_driver.php", "mumtaz_sso_driver"); + $this->mergeConfigFrom(__DIR__ . "/Config/mumtaz_sso_driver.php", "mumtaz_sso_driver"); + + $this->app->singleton(HttpClient::class, function (){ + return new HttpClientService(); + }); + + + $this->app->singleton(OauthToken::class, function (Application $app){ + $httpClient = $app->make(HttpClient::class); + return new OauthTokenService($httpClient); + }); + + $this->app->singleton(SSO::class, function (Application $app) { + $httpClient = $app->make(HttpClient::class); + $oauthTokenService = $app->make(OauthToken::class); + return new SSOService($httpClient,$oauthTokenService); + }); + + } /** diff --git a/src/MumtazSSOService.php b/src/MumtazSSOService.php deleted file mode 100644 index 39a2e55..0000000 --- a/src/MumtazSSOService.php +++ /dev/null @@ -1,132 +0,0 @@ -addHeaders([ - "Authorization" => $this->getClientAccessToken() - ]); - return $this; - } - - - /** - * @param array|null $errorResponse - * @return bool - * @throws InvalidRetryGenerateException|UnknownErrorHandlerException|InvalidClientCredentials|SSODriverException - */ - public function isRetryOnInvalidAccessToken(?array $errorResponse): bool - { - if ($errorResponse["rc"] === ResponseCode::ERR_AUTHENTICATION->name && $errorResponse['message'] === "Unauthenticated.") { - if ($this->currentRegenerateAttempt > self::RETRY_ATTEMPT_ON_INVALID_ACCESS_TOKEN) { - throw new InvalidRetryGenerateException("Access token invalid after " . self::RETRY_ATTEMPT_ON_INVALID_ACCESS_TOKEN . " retry generate attempt"); - } - - $this->addHeaders([ - "Authorization" => $this->getClientAccessToken(true) - ]); - - return true; - } - - return false; - } - - - /** - * @param bool $isRegenerate - * @return string - * @throws InvalidClientCredentials|UnknownErrorHandlerException|SSODriverException - */ - public function getClientAccessToken(bool $isRegenerate = false): string - { - //client access token is empty, generate new - if ($isRegenerate || ($clientAccessToken = Cache::get(self::ACCESS_TOKEN_CACHE_KEY)) === null) { - //hit into oauth/token - $response = Http::withHeaders($this->getHeaders())->post($this->getBaseUrl() . "/api/v1/oauth/token", [ - "grant_type" => "client_credentials", - "client_id" => config("services.mumtaz_sso_client_id"), - "client_secret" => config("services.mumtaz_sso_client_secret") , - "scope" => "*" - ]); - - if ($response->failed()){ - self::unknownResponseErrorHandler($response->json());//for unknown response - if ($response->json("rc") === ResponseCode::ERR_AUTHENTICATION->name) { - throw new InvalidClientCredentials("Regenerate access token failed. Invalid client credentials !"); - } - self::mappingErrorHandler($response->json()); - } - - - //reduce expired to prevent problem when retrieve token from cache and all progress is still valid, - //but when hit into server it's already invalid - $expiresIn = self::reduceValueByPercentage($response->json("expires_in"), self::TTL_PERCENTAGE_REDUCER); - $token = $response->json("access_token"); - - Cache::put( - self::ACCESS_TOKEN_CACHE_KEY, - $token, - $expiresIn - ); - $clientAccessToken = $token; - } - - return $clientAccessToken; - } - - - /** - * @param callable $request - * @param callable|null $customErrorHandler - * @return object|null - * @throws InvalidRetryGenerateException|UnknownErrorHandlerException|InvalidClientCredentials|SSODriverException - */ - public function getResponse(callable $request, callable $customErrorHandler = null): ?object - { - do { - $this->currentRegenerateAttempt++; - /** @var Response $response */ - $response = $request($this); - - if ($response->failed()){ - self::unknownResponseErrorHandler($response->json());//for unknown response - - if ($this->isRetryOnInvalidAccessToken($response->json())) { - continue; - } - - - if ($customErrorHandler){ - $customErrorHandler($this, $response); - } - - self::mappingErrorHandler($response->json()); //for default response - } - break; - } while ($this->currentRegenerateAttempt <= self::RETRY_ATTEMPT_ON_INVALID_ACCESS_TOKEN); - - return $response->object()->payload->data; - } -} diff --git a/src/OauthTokenService.php b/src/OauthTokenService.php new file mode 100644 index 0000000..cc19f0a --- /dev/null +++ b/src/OauthTokenService.php @@ -0,0 +1,109 @@ +retryRequestOauthTokenAttempt = 0; + } + + /** + * @param bool $isRegenerate + * @return string + * @throws InvalidClientCredentials + * @throws SSODriverException + * @throws UnknownErrorHandlerException + */ + public function getClientAccessToken(bool $isRegenerate = false): string + { + Cache::forget(self::ACCESS_TOKEN_CACHE_KEY); + //client access token is empty, generate new + if ($isRegenerate || ($clientAccessToken = Cache::get(self::ACCESS_TOKEN_CACHE_KEY)) === null) { + //hit into oauth/token + $response = Http::withHeaders($this->httpClient->getHttpRequestHeaders()) + ->post($this->httpClient->getBaseUrl() . "/api/v1/oauth/token", [ + "grant_type" => "client_credentials", + "client_id" => config("mumtaz_sso_driver.client_id"), + "client_secret" => config("mumtaz_sso_driver.client_secret"), + "scope" => "*" + ]); + + + if ($response->failed()) { + self::unknownResponseErrorHandler($response->json());//for unknown response + if ($response->json("rc") === ResponseCode::ERR_AUTHENTICATION->name) { + throw new InvalidClientCredentials("Regenerate access token failed. Invalid client credentials !"); + } + self::mappingErrorHandler($response->json()); + } + + + //reduce expired to prevent problem when retrieve token from cache and all progress is still valid, + //but when hit into server it's already invalid + $expiresIn = self::reduceValueByPercentage($response->json("expires_in"), self::TTL_PERCENTAGE_REDUCER); + $token = $response->json("access_token"); + + Cache::put( + self::ACCESS_TOKEN_CACHE_KEY, + $token, + $expiresIn + ); + $clientAccessToken = $token; + } + + return $clientAccessToken; + } + + + /** + * @param array|null $errorResponse + * @return bool + * @throws InvalidClientCredentials + * @throws SSODriverException + * @throws UnknownErrorHandlerException|InvalidRetryGenerateException + */ + public function isRetryOnInvalidAccessToken(?array $errorResponse): bool + { + if ($errorResponse["rc"] === ResponseCode::ERR_AUTHENTICATION->name && $errorResponse['message'] === "Unauthenticated.") { + if ($this->retryRequestOauthTokenAttempt > self::RETRY_ATTEMPT_ON_INVALID_ACCESS_TOKEN) { + throw new InvalidRetryGenerateException("Access token invalid after " . self::RETRY_ATTEMPT_ON_INVALID_ACCESS_TOKEN . " retry generate attempt"); + } + + $this->httpClient->addHttpRequestHeader("Authorization", $this->getClientAccessToken(true)); + + return true; + } + + return false; + } + + + /** + * @param int $value + * @param float $percentage + * @return float + */ + private static function reduceValueByPercentage(int $value, float $percentage): float + { + return $value - ($value * $percentage); + } +} diff --git a/src/SSOService.php b/src/SSOService.php new file mode 100644 index 0000000..12c87c7 --- /dev/null +++ b/src/SSOService.php @@ -0,0 +1,75 @@ +oauthToken->retryRequestOauthTokenAttempt++; + } + + + /** + * @return $this + * @throws InvalidClientCredentials + * @throws SSODriverException + * @throws UnknownErrorHandlerException + */ + public function setAuthorizationToken(): self + { + $this->httpClient->addHttpRequestHeader("Authorization", $this->oauthToken->getClientAccessToken()); + return $this; + } + + + /** + * @param callable $request + * @param callable|null $customErrorHandler + * @return object|null + * @throws InvalidRetryGenerateException|UnknownErrorHandlerException|InvalidClientCredentials|SSODriverException + */ + public function getResponse(callable $request, callable $customErrorHandler = null): ?object + { + do { + $this->oauthToken->retryRequestOauthTokenAttempt++; + /** @var Response $response */ + $response = $request($this); + + if ($response->failed()) { + self::unknownResponseErrorHandler($response->json());//for unknown response + + if ($this->oauthToken->isRetryOnInvalidAccessToken($response->json())) { + continue; + } + + + if ($customErrorHandler) { + $customErrorHandler($this, $response); + } + + self::mappingErrorHandler($response->json()); //for default response + } + break; + } while ($this->oauthToken->retryRequestOauthTokenAttempt <= OauthTokenService::RETRY_ATTEMPT_ON_INVALID_ACCESS_TOKEN); + + return $response->object()->payload->data; + } +} diff --git a/src/Services/AuthService.php b/src/Services/AuthService.php new file mode 100644 index 0000000..66ef6c9 --- /dev/null +++ b/src/Services/AuthService.php @@ -0,0 +1,39 @@ +sso->setAuthorizationToken() + ->getResponse(function (SSOService $service) use ($credentials) { + return Http::withHeaders($service->httpClient->getHttpRequestHeaders()) + ->post($service->httpClient->getBaseUrl() . "/api/v1/auth", [ + "username" => $credentials["username"], + "password" => $credentials["password"], + "institution_id" => $credentials["institution_id"], + ]); + }); + } +}