Skip to content

Commit

Permalink
Use PHP 8 code features
Browse files Browse the repository at this point in the history
  • Loading branch information
pjcdawkins committed Nov 25, 2024
1 parent 2c939a7 commit db3d6db
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 182 deletions.
2 changes: 2 additions & 0 deletions ecs.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@
])
->withPreparedSets(
psr12: true,
common: true,
strict: true,
);
12 changes: 4 additions & 8 deletions src/Grant/ApiToken.php
Original file line number Diff line number Diff line change
@@ -1,23 +1,19 @@
<?php

declare(strict_types=1);

namespace Platformsh\OAuth2\Client\Grant;

use League\OAuth2\Client\Grant\AbstractGrant;

class ApiToken extends AbstractGrant
{
/**
* {@inheritdoc}
*/
protected function getName()
protected function getName(): string
{
return 'api_token';
}

/**
* {@inheritdoc}
*/
protected function getRequiredRequestParameters()
protected function getRequiredRequestParameters(): array
{
return ['api_token'];
}
Expand Down
204 changes: 77 additions & 127 deletions src/GuzzleMiddleware.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
<?php

declare(strict_types=1);

namespace Platformsh\OAuth2\Client;

use Closure;
use GuzzleHttp\Exception\BadResponseException;
use League\OAuth2\Client\Grant\AbstractGrant;
use League\OAuth2\Client\Grant\ClientCredentials;
Expand All @@ -14,57 +17,77 @@

class GuzzleMiddleware
{
/** @var AbstractProvider $provider */
private $provider;
private readonly AbstractProvider $provider;

/** @var AbstractGrant $grant */
private $grant;
private readonly AbstractGrant $grant;

/** @var AccessToken|null */
private $accessToken;
private readonly array $grantOptions;

/** @var array */
private $grantOptions;
private ?AccessToken $accessToken;

/** @var callable|null */
private $tokenSave;
private ?Closure $tokenSave;

/** @var callable|null */
protected $onRefreshStart;
private ?Closure $onRefreshStart;

/** @var callable|null */
protected $onRefreshEnd;
private ?Closure $onRefreshEnd;

/** @var callable|null */
protected $onRefreshError;
private ?Closure $onRefreshError;

/** @var callable|null */
protected $onStepUpAuthResponse;
private ?Closure $onStepUpAuthResponse;

/**
* GuzzleMiddleware constructor.
*
* @param AbstractProvider $provider
* @param AbstractGrant $grant
* @param array $grantOptions
*/
public function __construct(AbstractProvider $provider, AbstractGrant $grant = null, array $grantOptions = [])
public function __construct(AbstractProvider $provider, ?AbstractGrant $grant = null, array $grantOptions = [])
{
$this->provider = $provider;
$this->grant = $grant ?: new ClientCredentials();
$this->grantOptions = $grantOptions;
}

/**
* Main middleware callback.
*/
public function __invoke(callable $next): callable
{
return function (RequestInterface $request, array $options) use ($next) {
if (! $this->isOAuth2($request, $options)) {
return $next($request, $options);
}

$token = $this->getAccessToken();
$request = $this->authenticateRequest($request, $token);

/** @var \GuzzleHttp\Promise\PromiseInterface $promise */
$promise = $next($request, $options);

return $promise->then(function (ResponseInterface $response) use ($request, $options, $token, $next) {
if ($response->getStatusCode() !== 401) {
return $response;
}

if (isset($this->onStepUpAuthResponse) && $this->isStepUpAuthenticationResponse($response)) {
$newToken = call_user_func($this->onStepUpAuthResponse, $response);
$this->setAccessToken($newToken);
} else {
// Consider the old token invalid, and get a new one.
$this->getAccessToken($token);
}

// Retry the request.
$request = $this->authenticateRequest($request, $token);
return $next($request, $options);
});
};
}

/**
* Set a callback that will save a token whenever a new one is acquired.
*
* @param callable $tokenSave
* A callback accepting one argument (the AccessToken) that will save a
* token.
*/
public function setTokenSaveCallback(callable $tokenSave)
public function setTokenSaveCallback(callable $tokenSave): void
{
$this->tokenSave = $tokenSave;
$this->tokenSave = Closure::fromCallable($tokenSave);
}

/**
Expand All @@ -74,9 +97,9 @@ public function setTokenSaveCallback(callable $tokenSave)
* A callback which accepts 1 argument, the refresh token being used if
* available (a string or null), and returns an AccessToken or null.
*/
public function setOnRefreshStart(callable $callback)
public function setOnRefreshStart(callable $callback): void
{
$this->onRefreshStart = $callback;
$this->onRefreshStart = Closure::fromCallable($callback);
}

/**
Expand All @@ -86,9 +109,9 @@ public function setOnRefreshStart(callable $callback)
* A callback which accepts 1 argument, the refresh token which was used
* if available (a string or null).
*/
public function setOnRefreshEnd(callable $callback)
public function setOnRefreshEnd(callable $callback): void
{
$this->onRefreshEnd = $callback;
$this->onRefreshEnd = Closure::fromCallable($callback);
}

/**
Expand All @@ -98,9 +121,9 @@ public function setOnRefreshEnd(callable $callback)
* A callback which accepts one argument, the BadResponseException, and
* returns an AccessToken or null.
*/
public function setOnRefreshError(callable $callback)
public function setOnRefreshError(callable $callback): void
{
$this->onRefreshError = $callback;
$this->onRefreshError = Closure::fromCallable($callback);
}

/**
Expand All @@ -110,79 +133,38 @@ public function setOnRefreshError(callable $callback)
* A callback which accepts one argument, the response, of type \GuzzleHttp\Message\ResponseInterface,
* and returns an AccessToken or null.
*/
public function setOnStepUpAuthResponse(callable $callback)
public function setOnStepUpAuthResponse(callable $callback): void
{
$this->onStepUpAuthResponse = $callback;
$this->onStepUpAuthResponse = Closure::fromCallable($callback);
}

/**
* Main middleware callback.
*
* @param callable $next
*
* @return callable
* Sets the access token for the next request(s) and saves it to storage.
*/
public function __invoke(callable $next)
public function setAccessToken(AccessToken $token): void
{
return function (RequestInterface $request, array $options) use ($next) {
if (!$this->isOAuth2($request, $options)) {
return $next($request, $options);
}

$token = $this->getAccessToken();
$request = $this->authenticateRequest($request, $token);

/** @var \GuzzleHttp\Promise\PromiseInterface $promise */
$promise = $next($request, $options);

return $promise->then(function (ResponseInterface $response) use ($request, $options, $token, $next) {
if ($response->getStatusCode() !== 401) {
return $response;
}

if (isset($this->onStepUpAuthResponse) && $this->isStepUpAuthenticationResponse($response)) {
$newToken = call_user_func($this->onStepUpAuthResponse, $response);
$this->accessToken = $newToken;
if (is_callable($this->tokenSave)) {
call_user_func($this->tokenSave, $this->accessToken);
}
} else {
// Consider the old token invalid, and get a new one.
$this->getAccessToken($token);
}

// Retry the request.
$request = $this->authenticateRequest($request, $token);
return $next($request, $options);
});
};
$this->accessToken = $token;
if ($this->tokenSave) {
($this->tokenSave)($this->accessToken);
}
}

/**
* Checks for a step-up authentication response (RFC 9470).
*
* @param ResponseInterface $response
*
* @return bool
*/
protected function isStepUpAuthenticationResponse(ResponseInterface $response)
protected function isStepUpAuthenticationResponse(ResponseInterface $response): bool
{
$authHeader = implode("\n", $response->getHeader('WWW-Authenticate'));
return stripos($authHeader, 'Bearer') !== false && strpos($authHeader, 'insufficient_user_authentication') !== false;
return stripos($authHeader, 'Bearer') !== false && str_contains($authHeader, 'insufficient_user_authentication');
}

/**
* Check if a request is configured to use OAuth2.
*
* @param RequestInterface $request
* @param array $options
*
* @return bool
*/
private function isOAuth2(RequestInterface $request, array $options)
private function isOAuth2(RequestInterface $request, array $options): bool
{
// The 'auth' option must be set to 'oauth2'.
if (!isset($options['auth']) || $options['auth'] !== 'oauth2') {
if (! isset($options['auth']) || $options['auth'] !== 'oauth2') {
return false;
}

Expand All @@ -196,13 +178,8 @@ private function isOAuth2(RequestInterface $request, array $options)

/**
* Add authentication to an HTTP request.
*
* @param RequestInterface $request
* @param AccessToken $token
*
* @return RequestInterface
*/
private function authenticateRequest(RequestInterface $request, AccessToken $token)
private function authenticateRequest(RequestInterface $request, AccessToken $token): RequestInterface
{
foreach ($this->provider->getHeaders($token->getToken()) as $name => $value) {
$request = $request->withHeader($name, $value);
Expand All @@ -217,17 +194,12 @@ private function authenticateRequest(RequestInterface $request, AccessToken $tok
* @param AccessToken|null $invalid
* A token to consider invalid.
*
* @return AccessToken
* The OAuth2 access token.
* @throws IdentityProviderException
*/
private function getAccessToken(AccessToken $invalid = null)
private function getAccessToken(AccessToken $invalid = null): AccessToken
{
if (!isset($this->accessToken) || $this->accessToken->hasExpired() || ($invalid && $this->accessToken === $invalid)) {
$this->accessToken = $this->acquireAccessToken();
if (is_callable($this->tokenSave)) {
call_user_func($this->tokenSave, $this->accessToken);
}
if (! isset($this->accessToken) || $this->accessToken->hasExpired() || ($invalid && $this->accessToken === $invalid)) {
$this->setAccessToken($this->acquireAccessToken());
}

return $this->accessToken;
Expand All @@ -236,10 +208,9 @@ private function getAccessToken(AccessToken $invalid = null)
/**
* Acquire a new access token using a refresh token or the configured grant.
*
* @return AccessToken
* @throws IdentityProviderException
*/
private function acquireAccessToken()
private function acquireAccessToken(): AccessToken
{
if (isset($this->accessToken) && $this->accessToken->getRefreshToken()) {
$currentRefreshToken = $this->accessToken->getRefreshToken();
Expand All @@ -250,7 +221,9 @@ private function acquireAccessToken()
return $result;
}
}
return $this->provider->getAccessToken(new RefreshToken(), ['refresh_token' => $this->accessToken->getRefreshToken()]);
return $this->provider->getAccessToken(new RefreshToken(), [
'refresh_token' => $this->accessToken->getRefreshToken(),
]);
} catch (BadResponseException $e) {
if (isset($this->onRefreshError)) {
$accessToken = call_user_func($this->onRefreshError, $e);
Expand All @@ -268,27 +241,4 @@ private function acquireAccessToken()

return $this->provider->getAccessToken($this->grant, $this->grantOptions);
}

/**
* Set the access token for the next request(s).
*
* @param AccessToken $token
*/
public function setAccessToken(AccessToken $token)
{
$this->accessToken = $token;
}

/**
* Set the access token for the next request(s), and save it to storage.
*
* @param AccessToken $token
*/
public function saveAccessToken(AccessToken $token)
{
$this->accessToken = $token;
if (is_callable($this->tokenSave)) {
call_user_func($this->tokenSave, $this->accessToken);
}
}
}
Loading

0 comments on commit db3d6db

Please sign in to comment.