- Added Oauth2 authentication. Fixed non-working bearer token also.

This commit is contained in:
Dave Mc Nicoll 2024-04-09 19:13:53 -04:00
parent f035fdf0a2
commit ce4d6cdf7c
7 changed files with 105 additions and 32 deletions

View File

@ -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) {

View File

@ -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)

View File

@ -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 {}
}

View File

@ -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";

View File

@ -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;

View File

@ -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;
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) {

View File

@ -0,0 +1,12 @@
<?php
namespace Ulmus\Api\Transport;
enum Oauth2GrantTypeEnum : string
{
case AuthorizationCode = "authorization_code";
case PKCE = "pkce";
case ClientCredentials = "client_credentials";
case DeviceCode = "device_code";
case RefreshToken = "refresh_token";
}