diff --git a/src/Adapter/Rest.php b/src/Adapter/Rest.php index 8912e97..19d723d 100644 --- a/src/Adapter/Rest.php +++ b/src/Adapter/Rest.php @@ -2,6 +2,7 @@ namespace Ulmus\Api\Adapter; +use Negundo\Client\Transport\Curl; use Ulmus\Adapter\AdapterInterface; use Ulmus\Api\ApiRepository; use Ulmus\Api\Common\AuthenticationEnum; @@ -13,13 +14,9 @@ use Ulmus\Ulmus; class Rest implements AdapterInterface { - protected AuthenticationEnum $auth; + protected CurlAuthorization $authorization; - protected string $username; - - protected string $password; - - protected string $token; + protected AuthenticationEnum $authorizationMethod; protected array $headers = []; @@ -27,9 +24,20 @@ class Rest implements AdapterInterface public readonly string $url; + public function setup(array $configuration): void + { + $this->url = rtrim($configuration['url'], '/'); + + $this->authorizationMethod = AuthenticationEnum::from($configuration['auth'] ?: AuthenticationEnum::Basic->value); + $this->authorization = ( new CurlAuthorization() )->fromAuthenticationEnum($this->authorizationMethod, $configuration); + + $this->headers = $configuration['headers'] ?? []; + $this->options = $configuration['options'] ?? []; + } + public function connect(): object { - return new CurlClient($this->headers, $this->options, ( new CurlAuthorization() )->fromAuthenticationEnum($this->auth, $this->username ?? false, $this->password ?? false, $this->token ?? false) ); + return new CurlClient($this->headers, $this->options, $this->authorization); } public function buildDataSourceName(): string @@ -37,18 +45,6 @@ class Rest implements AdapterInterface return "https"; } - public function setup(array $configuration): void - { - $this->auth = AuthenticationEnum::from($configuration['auth'] ?: AuthenticationEnum::Basic->value); - $this->url = rtrim($configuration['url'], '/'); - - foreach([ 'username', 'password', 'token', 'headers', 'options' ] as $conf) { - if ($configuration[$conf] ?? false) { - $this->$conf = $configuration[$conf]; - } - } - } - public function writableValue(mixed $value) : mixed { switch (true) { diff --git a/src/ApiRepository.php b/src/ApiRepository.php index 3be46d4..9a6133d 100644 --- a/src/ApiRepository.php +++ b/src/ApiRepository.php @@ -13,6 +13,7 @@ use Ulmus\Api\Stream\Stream; use Ulmus\Api\Request\JsonRequest; use Ulmus\Api\Request\Request; use Ulmus\Api\Transport\CurlClient; +use Ulmus\Api\Transport\CurlTransport; use Ulmus\EntityCollection; use Ulmus\SearchRequest\SearchRequestInterface; @@ -64,6 +65,9 @@ class ApiRepository extends \Ulmus\Repository $request = $this->prepareRequest($this->buildRequestUrl($attribute->url), $attribute->method, $data); + # @TODO !! SET HEADERS FROM CONFIG IN REQUEST HERE INSTEAD OF DIRECTLY IN TRANSPORT ! + # $request->withAddedHeader() + $request = $this->callApiRequestCallback($request, $attribute); $transport = $this->adapter->adapter()->connect(); @@ -74,6 +78,8 @@ class ApiRepository extends \Ulmus\Repository $response = $this->callApiResponseCallback($response, $attribute); + $this->callApiDebugCallback($transport); + return $response; } @@ -149,6 +155,11 @@ class ApiRepository extends \Ulmus\Repository return $this->adapter->apiHandler->handleResponse($response, $attribute); } + protected function callApiDebugCallback(CurlTransport $transport) : void + { + $this->adapter->apiHandler->debugResponse($transport, $this); + } + public function filterServerRequest(SearchRequestInterface $searchRequest, bool $count = true) : self { return $searchRequest->filter($this) diff --git a/src/Common/ApiHandlerPassThrough.php b/src/Common/ApiHandlerPassThrough.php index 972ce9e..2f8defb 100644 --- a/src/Common/ApiHandlerPassThrough.php +++ b/src/Common/ApiHandlerPassThrough.php @@ -5,11 +5,12 @@ namespace Ulmus\Api\Common; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; use Ulmus\Api\ApiHandlerInterface; +use Ulmus\Api\ApiRepository; use Ulmus\Api\Attribute\Obj\Api\ApiAction; +use Ulmus\Api\Transport\CurlTransport; class ApiHandlerPassThrough implements ApiHandlerInterface { - public function handleRequest(RequestInterface $request, ApiAction $attribute): RequestInterface { return $request; @@ -19,4 +20,6 @@ class ApiHandlerPassThrough implements ApiHandlerInterface { return $response; } + + public function debugResponse(CurlTransport $transport, ApiRepository $repository) : void {} } \ No newline at end of file diff --git a/src/Common/AuthenticationEnum.php b/src/Common/AuthenticationEnum.php index 3935c77..d9af3d4 100644 --- a/src/Common/AuthenticationEnum.php +++ b/src/Common/AuthenticationEnum.php @@ -11,7 +11,7 @@ enum AuthenticationEnum : string case Key = "key"; case Ntlm = "ntlm"; case Negotiate = "negotiate"; - # case OAuth = "oauth"; + case OAuth2 = "oauth2"; # case OpenID = "openid"; # case OpenAPI = "openapi"; case Token = "token"; diff --git a/src/Transport/CurlAuthorization.php b/src/Transport/CurlAuthorization.php index 48167ed..58b1cbf 100644 --- a/src/Transport/CurlAuthorization.php +++ b/src/Transport/CurlAuthorization.php @@ -2,6 +2,7 @@ namespace Ulmus\Api\Transport; +use League\OAuth2\Client\Provider\GenericProvider; use Ulmus\Api\Common\AuthenticationEnum; class CurlAuthorization @@ -10,31 +11,35 @@ class CurlAuthorization public array $options = []; - public function fromAuthenticationEnum(AuthenticationEnum $auth, string|false $username, #[\SensitiveParameter] string|false $password, #[\SensitiveParameter] string|false $token,) : static + public function fromAuthenticationEnum(AuthenticationEnum $auth, #[\SensitiveParameter] array $configuration,) : static { switch($auth) { case AuthenticationEnum::Bearer: - $this->bearer($token); + $this->bearer($configuration['token']); break; case AuthenticationEnum::Key: - $this->key($token); + $this->key($configuration['token']); break; case AuthenticationEnum::Token: - $this->token($token); + $this->token($configuration['token']); break; case AuthenticationEnum::Basic: - $this->basic($username, $password); + $this->basic($configuration['username'], $configuration['password']); break; case AuthenticationEnum::Ntlm: - $this->ntlm($username, $password); + $this->ntlm($configuration['username'], $configuration['password']); break; case AuthenticationEnum::Negotiate: - $this->negotiate($username, $password); + $this->negotiate($configuration['username'], $configuration['password']); + break; + + case AuthenticationEnum::OAuth2: + $this->oauth2($configuration['grant'], $configuration['oauth_token_url'], $configuration['redirect_uri'], $configuration['client_id'], $configuration['client_secret'], $configuration['scope']); break; } @@ -43,6 +48,7 @@ class CurlAuthorization public function bearer(#[\SensitiveParameter] string $token) : void { + $this->options[CURLOPT_HTTPAUTH] = CURLAUTH_BEARER; $this->options[CURLOPT_XOAUTH2_BEARER] = $token; } @@ -86,6 +92,43 @@ class CurlAuthorization $this->userpass(CURLAUTH_NEGOTIATE, $username, $password); } + public function oauth2(Oauth2GrantTypeEnum $grant, string $oauthTokenUrl, string $redirectUri, string $clientId, string $clientSecret, string $scope) : void + { + #return; + + # TMP ! + if (false === ($_SESSION['grics_token'] ?? false)) { + # IF NO TOKEN OR TIMEOUT + $provider = new GenericProvider([ + 'clientId' => $clientId, + 'clientSecret' => $clientSecret, + 'redirectUri' => $redirectUri, + 'urlAccessToken' => $oauthTokenUrl, + 'urlAuthorize' => 'https://service.example.com/authorize', + 'urlResourceOwnerDetails' => 'https://service.example.com/resource', + 'scopes' => $scope, + ]); + + try { + $accessToken = $provider->getAccessToken($grant->value); + $_SESSION['grics_token'] = $accessToken->getToken(); + + # var_dump($accessToken); die(); + + # KEEP TOKEN IN APCU ! + } catch (\League\OAuth2\Client\Provider\Exception\IdentityProviderException $e) { + // Failed to get the access token + exit($e->getMessage()); + } + } + + + $this->bearer($_SESSION['grics_token']); + + #var_dump($_SESSION['grics_token']);die(); + # SWITCH TO BEARER AUTH TYPE, SET TOKEN FROM APCU IF STILL VALID + } + protected function userpass(int $authType, string $username, #[\SensitiveParameter] $password) : void { $this->options[CURLOPT_HTTPAUTH] = $authType; diff --git a/src/Transport/CurlTransport.php b/src/Transport/CurlTransport.php index 746f0f2..f8c6dcc 100644 --- a/src/Transport/CurlTransport.php +++ b/src/Transport/CurlTransport.php @@ -27,7 +27,7 @@ abstract class CurlTransport { public ContentTypeEnum $contentType = ContentTypeEnum::Json, # Matching Guzzle's great user-agent syntax public string $userAgent = "ulmus-api/1.0 curl/{curl} php/{php}", - public bool $debug = false, + public bool $debug = true, ) { $this->userAgent = str_replace([ '{curl}', '{php}', ], [ curl_version()['version'] ?? "???", phpversion() ], $this->userAgent); } @@ -77,9 +77,10 @@ abstract class CurlTransport { $this->applyMethod($method, $data, $headers, $options); - $headers += $this->authorization->headers; - - $options += $this->authorization->options; + if ($this->authorization) { + $headers += HttpHeaderEnum::normalizeHeaderArray($this->authorization->headers); + $options += $this->authorization->options; + } $options += [ CURLOPT_URL => $url, @@ -136,6 +137,13 @@ abstract class CurlTransport { return $response; } + public function getDebugErrorLines() : array + { + $this->stderr->rewind(); + + return explode(PHP_EOL, $this->stderr->getContents()); + } + protected function applyMethod(MethodEnum $method, mixed &$data, array &$headers, array &$options) : void { switch ($method) { diff --git a/src/Transport/Oauth2GrantTypeEnum.php b/src/Transport/Oauth2GrantTypeEnum.php new file mode 100644 index 0000000..269d423 --- /dev/null +++ b/src/Transport/Oauth2GrantTypeEnum.php @@ -0,0 +1,12 @@ +