- Added Oauth2 authentication. Fixed non-working bearer token also.
This commit is contained in:
parent
f035fdf0a2
commit
ce4d6cdf7c
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
namespace Ulmus\Api\Adapter;
|
namespace Ulmus\Api\Adapter;
|
||||||
|
|
||||||
|
use Negundo\Client\Transport\Curl;
|
||||||
use Ulmus\Adapter\AdapterInterface;
|
use Ulmus\Adapter\AdapterInterface;
|
||||||
use Ulmus\Api\ApiRepository;
|
use Ulmus\Api\ApiRepository;
|
||||||
use Ulmus\Api\Common\AuthenticationEnum;
|
use Ulmus\Api\Common\AuthenticationEnum;
|
||||||
|
@ -13,13 +14,9 @@ use Ulmus\Ulmus;
|
||||||
|
|
||||||
class Rest implements AdapterInterface
|
class Rest implements AdapterInterface
|
||||||
{
|
{
|
||||||
protected AuthenticationEnum $auth;
|
protected CurlAuthorization $authorization;
|
||||||
|
|
||||||
protected string $username;
|
protected AuthenticationEnum $authorizationMethod;
|
||||||
|
|
||||||
protected string $password;
|
|
||||||
|
|
||||||
protected string $token;
|
|
||||||
|
|
||||||
protected array $headers = [];
|
protected array $headers = [];
|
||||||
|
|
||||||
|
@ -27,9 +24,20 @@ class Rest implements AdapterInterface
|
||||||
|
|
||||||
public readonly string $url;
|
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
|
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
|
public function buildDataSourceName(): string
|
||||||
|
@ -37,18 +45,6 @@ class Rest implements AdapterInterface
|
||||||
return "https";
|
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
|
public function writableValue(mixed $value) : mixed
|
||||||
{
|
{
|
||||||
switch (true) {
|
switch (true) {
|
||||||
|
|
|
@ -13,6 +13,7 @@ use Ulmus\Api\Stream\Stream;
|
||||||
use Ulmus\Api\Request\JsonRequest;
|
use Ulmus\Api\Request\JsonRequest;
|
||||||
use Ulmus\Api\Request\Request;
|
use Ulmus\Api\Request\Request;
|
||||||
use Ulmus\Api\Transport\CurlClient;
|
use Ulmus\Api\Transport\CurlClient;
|
||||||
|
use Ulmus\Api\Transport\CurlTransport;
|
||||||
use Ulmus\EntityCollection;
|
use Ulmus\EntityCollection;
|
||||||
use Ulmus\SearchRequest\SearchRequestInterface;
|
use Ulmus\SearchRequest\SearchRequestInterface;
|
||||||
|
|
||||||
|
@ -64,6 +65,9 @@ class ApiRepository extends \Ulmus\Repository
|
||||||
|
|
||||||
$request = $this->prepareRequest($this->buildRequestUrl($attribute->url), $attribute->method, $data);
|
$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);
|
$request = $this->callApiRequestCallback($request, $attribute);
|
||||||
|
|
||||||
$transport = $this->adapter->adapter()->connect();
|
$transport = $this->adapter->adapter()->connect();
|
||||||
|
@ -74,6 +78,8 @@ class ApiRepository extends \Ulmus\Repository
|
||||||
|
|
||||||
$response = $this->callApiResponseCallback($response, $attribute);
|
$response = $this->callApiResponseCallback($response, $attribute);
|
||||||
|
|
||||||
|
$this->callApiDebugCallback($transport);
|
||||||
|
|
||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,6 +155,11 @@ class ApiRepository extends \Ulmus\Repository
|
||||||
return $this->adapter->apiHandler->handleResponse($response, $attribute);
|
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
|
public function filterServerRequest(SearchRequestInterface $searchRequest, bool $count = true) : self
|
||||||
{
|
{
|
||||||
return $searchRequest->filter($this)
|
return $searchRequest->filter($this)
|
||||||
|
|
|
@ -5,11 +5,12 @@ namespace Ulmus\Api\Common;
|
||||||
use Psr\Http\Message\RequestInterface;
|
use Psr\Http\Message\RequestInterface;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
use Ulmus\Api\ApiHandlerInterface;
|
use Ulmus\Api\ApiHandlerInterface;
|
||||||
|
use Ulmus\Api\ApiRepository;
|
||||||
use Ulmus\Api\Attribute\Obj\Api\ApiAction;
|
use Ulmus\Api\Attribute\Obj\Api\ApiAction;
|
||||||
|
use Ulmus\Api\Transport\CurlTransport;
|
||||||
|
|
||||||
class ApiHandlerPassThrough implements ApiHandlerInterface
|
class ApiHandlerPassThrough implements ApiHandlerInterface
|
||||||
{
|
{
|
||||||
|
|
||||||
public function handleRequest(RequestInterface $request, ApiAction $attribute): RequestInterface
|
public function handleRequest(RequestInterface $request, ApiAction $attribute): RequestInterface
|
||||||
{
|
{
|
||||||
return $request;
|
return $request;
|
||||||
|
@ -19,4 +20,6 @@ class ApiHandlerPassThrough implements ApiHandlerInterface
|
||||||
{
|
{
|
||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function debugResponse(CurlTransport $transport, ApiRepository $repository) : void {}
|
||||||
}
|
}
|
|
@ -11,7 +11,7 @@ enum AuthenticationEnum : string
|
||||||
case Key = "key";
|
case Key = "key";
|
||||||
case Ntlm = "ntlm";
|
case Ntlm = "ntlm";
|
||||||
case Negotiate = "negotiate";
|
case Negotiate = "negotiate";
|
||||||
# case OAuth = "oauth";
|
case OAuth2 = "oauth2";
|
||||||
# case OpenID = "openid";
|
# case OpenID = "openid";
|
||||||
# case OpenAPI = "openapi";
|
# case OpenAPI = "openapi";
|
||||||
case Token = "token";
|
case Token = "token";
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
namespace Ulmus\Api\Transport;
|
namespace Ulmus\Api\Transport;
|
||||||
|
|
||||||
|
use League\OAuth2\Client\Provider\GenericProvider;
|
||||||
use Ulmus\Api\Common\AuthenticationEnum;
|
use Ulmus\Api\Common\AuthenticationEnum;
|
||||||
|
|
||||||
class CurlAuthorization
|
class CurlAuthorization
|
||||||
|
@ -10,31 +11,35 @@ class CurlAuthorization
|
||||||
|
|
||||||
public array $options = [];
|
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) {
|
switch($auth) {
|
||||||
case AuthenticationEnum::Bearer:
|
case AuthenticationEnum::Bearer:
|
||||||
$this->bearer($token);
|
$this->bearer($configuration['token']);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AuthenticationEnum::Key:
|
case AuthenticationEnum::Key:
|
||||||
$this->key($token);
|
$this->key($configuration['token']);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AuthenticationEnum::Token:
|
case AuthenticationEnum::Token:
|
||||||
$this->token($token);
|
$this->token($configuration['token']);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AuthenticationEnum::Basic:
|
case AuthenticationEnum::Basic:
|
||||||
$this->basic($username, $password);
|
$this->basic($configuration['username'], $configuration['password']);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AuthenticationEnum::Ntlm:
|
case AuthenticationEnum::Ntlm:
|
||||||
$this->ntlm($username, $password);
|
$this->ntlm($configuration['username'], $configuration['password']);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AuthenticationEnum::Negotiate:
|
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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,6 +48,7 @@ class CurlAuthorization
|
||||||
|
|
||||||
public function bearer(#[\SensitiveParameter] string $token) : void
|
public function bearer(#[\SensitiveParameter] string $token) : void
|
||||||
{
|
{
|
||||||
|
$this->options[CURLOPT_HTTPAUTH] = CURLAUTH_BEARER;
|
||||||
$this->options[CURLOPT_XOAUTH2_BEARER] = $token;
|
$this->options[CURLOPT_XOAUTH2_BEARER] = $token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,6 +92,43 @@ class CurlAuthorization
|
||||||
$this->userpass(CURLAUTH_NEGOTIATE, $username, $password);
|
$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
|
protected function userpass(int $authType, string $username, #[\SensitiveParameter] $password) : void
|
||||||
{
|
{
|
||||||
$this->options[CURLOPT_HTTPAUTH] = $authType;
|
$this->options[CURLOPT_HTTPAUTH] = $authType;
|
||||||
|
|
|
@ -27,7 +27,7 @@ abstract class CurlTransport {
|
||||||
public ContentTypeEnum $contentType = ContentTypeEnum::Json,
|
public ContentTypeEnum $contentType = ContentTypeEnum::Json,
|
||||||
# Matching Guzzle's great user-agent syntax
|
# Matching Guzzle's great user-agent syntax
|
||||||
public string $userAgent = "ulmus-api/1.0 curl/{curl} php/{php}",
|
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);
|
$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);
|
$this->applyMethod($method, $data, $headers, $options);
|
||||||
|
|
||||||
$headers += $this->authorization->headers;
|
if ($this->authorization) {
|
||||||
|
$headers += HttpHeaderEnum::normalizeHeaderArray($this->authorization->headers);
|
||||||
$options += $this->authorization->options;
|
$options += $this->authorization->options;
|
||||||
|
}
|
||||||
|
|
||||||
$options += [
|
$options += [
|
||||||
CURLOPT_URL => $url,
|
CURLOPT_URL => $url,
|
||||||
|
@ -136,6 +137,13 @@ abstract class CurlTransport {
|
||||||
return $response;
|
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
|
protected function applyMethod(MethodEnum $method, mixed &$data, array &$headers, array &$options) : void
|
||||||
{
|
{
|
||||||
switch ($method) {
|
switch ($method) {
|
||||||
|
|
|
@ -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";
|
||||||
|
}
|
Loading…
Reference in New Issue