Converted MissingAuthenticationException into a problem details exception

This commit is contained in:
Alejandro Celaya 2019-11-26 21:43:29 +01:00
parent f502eb0195
commit 6f4e5175da
8 changed files with 48 additions and 57 deletions

View file

@ -4,9 +4,8 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\Rest\Authentication;
use Psr\Container;
use Psr\Http\Message\ServerRequestInterface;
use Shlinkio\Shlink\Rest\Exception\NoAuthenticationException;
use Shlinkio\Shlink\Rest\Exception\MissingAuthenticationException;
use function array_filter;
use function array_reduce;
@ -30,13 +29,12 @@ class RequestToHttpAuthPlugin implements RequestToHttpAuthPluginInterface
}
/**
* @throws Container\ContainerExceptionInterface
* @throws NoAuthenticationException
* @throws MissingAuthenticationException
*/
public function fromRequest(ServerRequestInterface $request): Plugin\AuthenticationPluginInterface
{
if (! $this->hasAnySupportedHeader($request)) {
throw NoAuthenticationException::fromExpectedTypes(self::SUPPORTED_AUTH_HEADERS);
throw MissingAuthenticationException::fromExpectedTypes(self::SUPPORTED_AUTH_HEADERS);
}
return $this->authPluginManager->get($this->getFirstAvailableHeader($request));

View file

@ -4,15 +4,13 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\Rest\Authentication;
use Psr\Container;
use Psr\Http\Message\ServerRequestInterface;
use Shlinkio\Shlink\Rest\Exception\NoAuthenticationException;
use Shlinkio\Shlink\Rest\Exception\MissingAuthenticationException;
interface RequestToHttpAuthPluginInterface
{
/**
* @throws Container\ContainerExceptionInterface
* @throws NoAuthenticationException
* @throws MissingAuthenticationException
*/
public function fromRequest(ServerRequestInterface $request): Plugin\AuthenticationPluginInterface;
}

View file

@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\Rest\Exception;
use Fig\Http\Message\StatusCodeInterface;
use Zend\ProblemDetails\Exception\CommonProblemDetailsExceptionTrait;
use Zend\ProblemDetails\Exception\ProblemDetailsExceptionInterface;
use function implode;
use function sprintf;
class MissingAuthenticationException extends RuntimeException implements ProblemDetailsExceptionInterface
{
use CommonProblemDetailsExceptionTrait;
private const TITLE = 'Invalid authorization';
public const TYPE = 'INVALID_AUTHORIZATION';
public static function fromExpectedTypes(array $expectedTypes): self
{
$e = new self(sprintf(
'Expected one of the following authentication headers, but none were provided, ["%s"]',
implode('", "', $expectedTypes)
));
$e->detail = $e->getMessage();
$e->title = self::TITLE;
$e->type = self::TYPE;
$e->status = StatusCodeInterface::STATUS_UNAUTHORIZED;
$e->additional = ['expectedTypes' => $expectedTypes];
return $e;
}
}

View file

@ -1,19 +0,0 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\Rest\Exception;
use function implode;
use function sprintf;
class NoAuthenticationException extends RuntimeException
{
public static function fromExpectedTypes(array $expectedTypes): self
{
return new self(sprintf(
'None of the valid authentication mechanisms where provided. Expected one of ["%s"]',
implode('", "', $expectedTypes)
));
}
}

View file

@ -6,24 +6,19 @@ namespace Shlinkio\Shlink\Rest\Middleware;
use Fig\Http\Message\RequestMethodInterface;
use Fig\Http\Message\StatusCodeInterface;
use Psr\Container\ContainerExceptionInterface;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Shlinkio\Shlink\Rest\Authentication\RequestToHttpAuthPlugin;
use Shlinkio\Shlink\Rest\Authentication\RequestToHttpAuthPluginInterface;
use Shlinkio\Shlink\Rest\Exception\NoAuthenticationException;
use Shlinkio\Shlink\Rest\Exception\VerifyAuthenticationException;
use Shlinkio\Shlink\Rest\Util\RestUtils;
use Zend\Diactoros\Response\JsonResponse;
use Zend\Expressive\Router\RouteResult;
use function Functional\contains;
use function implode;
use function sprintf;
class AuthenticationMiddleware implements MiddlewareInterface, StatusCodeInterface, RequestMethodInterface
{
@ -44,16 +39,6 @@ class AuthenticationMiddleware implements MiddlewareInterface, StatusCodeInterfa
$this->logger = $logger ?: new NullLogger();
}
/**
* Process an incoming server request and return a response, optionally delegating
* to the next middleware component to create the response.
*
* @param Request $request
* @param RequestHandlerInterface $handler
*
* @return Response
* @throws \InvalidArgumentException
*/
public function process(Request $request, RequestHandlerInterface $handler): Response
{
/** @var RouteResult|null $routeResult */
@ -67,15 +52,7 @@ class AuthenticationMiddleware implements MiddlewareInterface, StatusCodeInterfa
return $handler->handle($request);
}
try {
$plugin = $this->requestToAuthPlugin->fromRequest($request);
} catch (ContainerExceptionInterface | NoAuthenticationException $e) {
$this->logger->warning('Invalid or no authentication provided. {e}', ['e' => $e]);
return $this->createErrorResponse(sprintf(
'Expected one of the following authentication headers, but none were provided, ["%s"]',
implode('", "', RequestToHttpAuthPlugin::SUPPORTED_AUTH_HEADERS)
));
}
$plugin = $this->requestToAuthPlugin->fromRequest($request);
try {
$plugin->verify($request);

View file

@ -28,7 +28,8 @@ class RestUtils
public const INVALID_CREDENTIALS_ERROR = 'INVALID_CREDENTIALS';
public const INVALID_AUTH_TOKEN_ERROR = 'INVALID_AUTH_TOKEN';
public const INVALID_AUTHORIZATION_ERROR = 'INVALID_AUTHORIZATION';
/** @deprecated */
public const INVALID_AUTHORIZATION_ERROR = Rest\MissingAuthenticationException::TYPE;
public const INVALID_API_KEY_ERROR = 'INVALID_API_KEY';
/** @deprecated */

View file

@ -11,7 +11,7 @@ use Shlinkio\Shlink\Rest\Authentication\Plugin\ApiKeyHeaderPlugin;
use Shlinkio\Shlink\Rest\Authentication\Plugin\AuthenticationPluginInterface;
use Shlinkio\Shlink\Rest\Authentication\Plugin\AuthorizationHeaderPlugin;
use Shlinkio\Shlink\Rest\Authentication\RequestToHttpAuthPlugin;
use Shlinkio\Shlink\Rest\Exception\NoAuthenticationException;
use Shlinkio\Shlink\Rest\Exception\MissingAuthenticationException;
use Zend\Diactoros\ServerRequest;
use function implode;
@ -35,7 +35,7 @@ class RequestToAuthPluginTest extends TestCase
{
$request = new ServerRequest();
$this->expectException(NoAuthenticationException::class);
$this->expectException(MissingAuthenticationException::class);
$this->expectExceptionMessage(sprintf(
'None of the valid authentication mechanisms where provided. Expected one of ["%s"]',
implode('", "', RequestToHttpAuthPlugin::SUPPORTED_AUTH_HEADERS)

View file

@ -19,7 +19,7 @@ use Shlinkio\Shlink\Rest\Action\AuthenticateAction;
use Shlinkio\Shlink\Rest\Authentication\Plugin\AuthenticationPluginInterface;
use Shlinkio\Shlink\Rest\Authentication\RequestToHttpAuthPlugin;
use Shlinkio\Shlink\Rest\Authentication\RequestToHttpAuthPluginInterface;
use Shlinkio\Shlink\Rest\Exception\NoAuthenticationException;
use Shlinkio\Shlink\Rest\Exception\MissingAuthenticationException;
use Shlinkio\Shlink\Rest\Exception\VerifyAuthenticationException;
use Shlinkio\Shlink\Rest\Middleware\AuthenticationMiddleware;
use Shlinkio\Shlink\Rest\Util\RestUtils;
@ -128,7 +128,7 @@ class AuthenticationMiddlewareTest extends TestCase
};
yield 'container exception' => [$containerException];
yield 'authentication exception' => [NoAuthenticationException::fromExpectedTypes([])];
yield 'authentication exception' => [MissingAuthenticationException::fromExpectedTypes([])];
}
/** @test */