Skip to content

Decouple error responses from exception #1113

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

Open
wants to merge 9 commits into
base: 9.0.0-WIP
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions examples/public/auth_code.php
Original file line number Diff line number Diff line change
@@ -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);
@@ -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());
@@ -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());
2 changes: 1 addition & 1 deletion examples/public/client_credentials.php
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion examples/public/implicit.php
Original file line number Diff line number Diff line change
@@ -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());
2 changes: 1 addition & 1 deletion examples/public/password.php
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion examples/public/refresh_token.php
Original file line number Diff line number Diff line change
@@ -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());

32 changes: 24 additions & 8 deletions src/AuthorizationServer.php
Original file line number Diff line number Diff line change
@@ -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;
@@ -27,7 +30,8 @@

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

/**
* @var GrantTypeInterface[]
@@ -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;
@@ -115,6 +126,11 @@ public function __construct(
}

$this->responseType = $responseType;

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

/**
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
@@ -10,7 +10,6 @@
namespace League\OAuth2\Server\Exception;

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

@@ -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.
*
@@ -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
@@ -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
9 changes: 4 additions & 5 deletions src/Middleware/ResourceServerMiddleware.php
Original file line number Diff line number Diff line change
@@ -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
Loading
Oops, something went wrong.