Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Decouple error responses from exception #1113

Open
wants to merge 9 commits into
base: 9.0.0-WIP
Choose a base branch
from
5 changes: 2 additions & 3 deletions examples/public/auth_code.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@
return $server;
},
]);

$app->get('/authorize', function (ServerRequestInterface $request, ResponseInterface $response) use ($app) {
/* @var \League\OAuth2\Server\AuthorizationServer $server */
$server = $app->getContainer()->get(AuthorizationServer::class);
Expand All @@ -79,7 +78,7 @@
// Return the HTTP redirect response
return $server->completeAuthorizationRequest($authRequest, $response);
} catch (OAuthServerException $exception) {
return $exception->generateHttpResponse($response);
return $server->generateHttpResponse($exception, $response);
} catch (\Exception $exception) {
$body = new Stream('php://temp', 'r+');
$body->write($exception->getMessage());
Expand All @@ -95,7 +94,7 @@
try {
return $server->respondToAccessTokenRequest($request, $response);
} catch (OAuthServerException $exception) {
return $exception->generateHttpResponse($response);
return $server->generateHttpResponse($exception, $response);
} catch (\Exception $exception) {
$body = new Stream('php://temp', 'r+');
$body->write($exception->getMessage());
Expand Down
2 changes: 1 addition & 1 deletion examples/public/client_credentials.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
} catch (OAuthServerException $exception) {

// All instances of OAuthServerException can be formatted into a HTTP response
return $exception->generateHttpResponse($response);
return $server->generateHttpResponse($exception, $response);
} catch (\Exception $exception) {

// Unknown exception
Expand Down
2 changes: 1 addition & 1 deletion examples/public/implicit.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
// Return the HTTP redirect response
return $server->completeAuthorizationRequest($authRequest, $response);
} catch (OAuthServerException $exception) {
return $exception->generateHttpResponse($response);
return $server->generateHttpResponse($exception, $response);
} catch (\Exception $exception) {
$body = new Stream('php://temp', 'r+');
$body->write($exception->getMessage());
Expand Down
2 changes: 1 addition & 1 deletion examples/public/password.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ function (ServerRequestInterface $request, ResponseInterface $response) use ($ap
} catch (OAuthServerException $exception) {

// All instances of OAuthServerException can be converted to a PSR-7 response
return $exception->generateHttpResponse($response);
return $server->generateHttpResponse($exception, $response);
} catch (\Exception $exception) {

// Catch unexpected exceptions
Expand Down
2 changes: 1 addition & 1 deletion examples/public/refresh_token.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
try {
return $server->respondToAccessTokenRequest($request, $response);
} catch (OAuthServerException $exception) {
return $exception->generateHttpResponse($response);
return $server->generateHttpResponse($exception, $response);
} catch (\Exception $exception) {
$response->getBody()->write($exception->getMessage());

Expand Down
32 changes: 24 additions & 8 deletions src/AuthorizationServer.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
use Defuse\Crypto\Key;
use League\Event\EmitterAwareInterface;
use League\Event\EmitterAwareTrait;
use League\OAuth2\Server\Exception\ExceptionResponseHandler;
use League\OAuth2\Server\Exception\ExceptionResponseHandlerInterface;
use League\OAuth2\Server\Exception\ExceptionResponseHandlerTrait;
use League\OAuth2\Server\Exception\OAuthServerException;
use League\OAuth2\Server\Grant\GrantTypeInterface;
use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface;
Expand All @@ -27,7 +30,8 @@

class AuthorizationServer implements EmitterAwareInterface
{
use EmitterAwareTrait;
use EmitterAwareTrait,
ExceptionResponseHandlerTrait;

/**
* @var GrantTypeInterface[]
Expand Down Expand Up @@ -79,23 +83,30 @@ class AuthorizationServer implements EmitterAwareInterface
*/
private $defaultScope = '';

/**
* @var ExceptionResponseHandlerInterface
*/
private $exceptionResponseHandler;

/**
* New server instance.
*
* @param ClientRepositoryInterface $clientRepository
* @param AccessTokenRepositoryInterface $accessTokenRepository
* @param ScopeRepositoryInterface $scopeRepository
* @param CryptKeyInterface|string $privateKey
* @param string|Key $encryptionKey
* @param null|ResponseTypeInterface $responseType
* @param ClientRepositoryInterface $clientRepository
* @param AccessTokenRepositoryInterface $accessTokenRepository
* @param ScopeRepositoryInterface $scopeRepository
* @param CryptKeyInterface|string $privateKey
* @param string|Key $encryptionKey
* @param null|ResponseTypeInterface $responseType
* @param null|ExceptionResponseHandlerInterface $exceptionResponseHandler
*/
public function __construct(
ClientRepositoryInterface $clientRepository,
AccessTokenRepositoryInterface $accessTokenRepository,
ScopeRepositoryInterface $scopeRepository,
$privateKey,
$encryptionKey,
ResponseTypeInterface $responseType = null
ResponseTypeInterface $responseType = null,
ExceptionResponseHandlerInterface $exceptionResponseHandler = null
) {
$this->clientRepository = $clientRepository;
$this->accessTokenRepository = $accessTokenRepository;
Expand All @@ -115,6 +126,11 @@ public function __construct(
}

$this->responseType = $responseType;

if ($exceptionResponseHandler === null) {
$exceptionResponseHandler = new ExceptionResponseHandler();
}
$this->exceptionResponseHandler = $exceptionResponseHandler;
}

/**
Expand Down
72 changes: 72 additions & 0 deletions src/Exception/ExceptionResponseHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

declare(strict_types=1);

namespace League\OAuth2\Server\Exception;

use Psr\Http\Message\ResponseInterface;

class ExceptionResponseHandler implements ExceptionResponseHandlerInterface
{
/**
* Generate a HTTP response from am OAuthServerException
*
* @param OAuthServerException $exception
* @param ResponseInterface $response
* @param bool $useFragment True if errors should be in the URI fragment instead of query string
* @param int $jsonOptions options passed to json_encode
*
* @return ResponseInterface
*/
public function generateHttpResponse(
OAuthServerException $exception,
ResponseInterface $response,
$useFragment = false,
$jsonOptions = 0
) {
$headers = $exception->getHttpHeaders();

$payload = $exception->getPayload();

$redirectUri = $exception->getRedirectUri();
if ($redirectUri !== null) {
return $this->generateRedirectResponse($redirectUri, $response, $payload, $useFragment);
}

foreach ($headers as $header => $content) {
$response = $response->withHeader($header, $content);
}

$responseBody = \json_encode($payload, $jsonOptions) ?: 'JSON encoding of payload failed';

$response->getBody()->write($responseBody);

return $response->withStatus($exception->getHttpStatusCode());
}

/**
* Generate a HTTP response from am OAuthServerException
*
* @param string $redirectUri
* @param ResponseInterface $response
* @param string[] $payload
* @param bool $useFragment
*
* @return ResponseInterface
*/
protected function generateRedirectResponse(
string $redirectUri,
ResponseInterface $response,
$payload,
$useFragment
): ResponseInterface {
if ($useFragment === true) {
$querySeparator = '#';
} else {
$querySeparator = '?';
}
$redirectUri .= (\strstr($redirectUri, '?') === false) ? $querySeparator : '&';

return $response->withStatus(302)->withHeader('Location', $redirectUri . \http_build_query($payload));
}
}
25 changes: 25 additions & 0 deletions src/Exception/ExceptionResponseHandlerInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php declare(strict_types=1);

namespace League\OAuth2\Server\Exception;

use Psr\Http\Message\ResponseInterface;

interface ExceptionResponseHandlerInterface
{
/**
* Generate a HTTP response from am OAuthServerException
*
* @param OAuthServerException $exception
* @param ResponseInterface $response
* @param bool $useFragment True if errors should be in the URI fragment instead of query string
* @param int $jsonOptions options passed to json_encode
*
* @return ResponseInterface
*/
public function generateHttpResponse(
OAuthServerException $exception,
ResponseInterface $response,
$useFragment = false,
$jsonOptions = 0
);
}
27 changes: 27 additions & 0 deletions src/Exception/ExceptionResponseHandlerTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php declare(strict_types=1);

namespace League\OAuth2\Server\Exception;

use Psr\Http\Message\ResponseInterface;

trait ExceptionResponseHandlerTrait
{
/**
* Generate a HTTP response from am OAuthServerException
*
* @param OAuthServerException $exception
* @param ResponseInterface $response
* @param bool $useFragment True if errors should be in the URI fragment instead of query string
* @param int $jsonOptions options passed to json_encode
*
* @return ResponseInterface
*/
public function generateHttpResponse(
OAuthServerException $exception,
ResponseInterface $response,
$useFragment = false,
$jsonOptions = 0
) {
return $this->exceptionResponseHandler->generateHttpResponse($exception, $response, $useFragment, $jsonOptions);
}
}
45 changes: 8 additions & 37 deletions src/Exception/OAuthServerException.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
namespace League\OAuth2\Server\Exception;

use Exception;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Throwable;

Expand Down Expand Up @@ -279,42 +278,6 @@ public function getErrorType()
return $this->errorType;
}

/**
* Generate a HTTP response.
*
* @param ResponseInterface $response
* @param bool $useFragment True if errors should be in the URI fragment instead of query string
* @param int $jsonOptions options passed to json_encode
*
* @return ResponseInterface
*/
public function generateHttpResponse(ResponseInterface $response, $useFragment = false, $jsonOptions = 0)
{
$headers = $this->getHttpHeaders();

$payload = $this->getPayload();

if ($this->redirectUri !== null) {
if ($useFragment === true) {
$this->redirectUri .= (\strstr($this->redirectUri, '#') === false) ? '#' : '&';
} else {
$this->redirectUri .= (\strstr($this->redirectUri, '?') === false) ? '?' : '&';
}

return $response->withStatus(302)->withHeader('Location', $this->redirectUri . \http_build_query($payload));
}

foreach ($headers as $header => $content) {
$response = $response->withHeader($header, $content);
}

$responseBody = \json_encode($payload, $jsonOptions) ?: 'JSON encoding of payload failed';

$response->getBody()->write($responseBody);

return $response->withStatus($this->getHttpStatusCode());
}

/**
* Get all headers that have to be send with the error response.
*
Expand Down Expand Up @@ -376,4 +339,12 @@ public function getHint()
{
return $this->hint;
}

/**
* @return null|string
*/
public function getRedirectUri()
{
return $this->redirectUri;
}
}
9 changes: 4 additions & 5 deletions src/Middleware/AuthorizationServerMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,11 @@ public function __invoke(ServerRequestInterface $request, ResponseInterface $res
try {
$response = $this->server->respondToAccessTokenRequest($request, $response);
} catch (OAuthServerException $exception) {
return $exception->generateHttpResponse($response);
// @codeCoverageIgnoreStart
return $this->server->generateHttpResponse($exception, $response);
} catch (Exception $exception) {
return (new OAuthServerException($exception->getMessage(), 0, 'unknown_error', 500))
->generateHttpResponse($response);
// @codeCoverageIgnoreEnd
$serverException = new OAuthServerException($exception->getMessage(), 0, 'unknown_error', 500);

return $this->server->generateHttpResponse($serverException, $response);
}

// Pass the request and response on to the next responder in the chain
Expand Down
9 changes: 4 additions & 5 deletions src/Middleware/ResourceServerMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,11 @@ public function __invoke(ServerRequestInterface $request, ResponseInterface $res
try {
$request = $this->server->validateAuthenticatedRequest($request);
} catch (OAuthServerException $exception) {
return $exception->generateHttpResponse($response);
// @codeCoverageIgnoreStart
return $this->server->generateHttpResponse($exception, $response);
} catch (Exception $exception) {
return (new OAuthServerException($exception->getMessage(), 0, 'unknown_error', 500))
->generateHttpResponse($response);
// @codeCoverageIgnoreEnd
$serverException = new OAuthServerException($exception->getMessage(), 0, 'unknown_error', 500);

return $this->server->generateHttpResponse($serverException, $response);
}

// Pass the request and response on to the next responder in the chain
Expand Down
Loading