mirror of
https://github.com/shlinkio/shlink.git
synced 2025-02-16 23:39:54 +03:00
Removed remaining deprecated elements
This commit is contained in:
parent
434b56fa8c
commit
5c90a7c7a7
25 changed files with 15 additions and 765 deletions
|
@ -17,16 +17,6 @@
|
|||
"status": {
|
||||
"type": "number",
|
||||
"description": "HTTP response status code"
|
||||
},
|
||||
"code": {
|
||||
"type": "string",
|
||||
"description": "**[Deprecated] Use type instead. Not returned for v2 of the REST API** A machine unique code",
|
||||
"deprecated": true
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"description": "**[Deprecated] Use detail instead. Not returned for v2 of the REST API** A human-friendly error message",
|
||||
"deprecated": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,11 +31,6 @@
|
|||
},
|
||||
"meta": {
|
||||
"$ref": "./ShortUrlMeta.json"
|
||||
},
|
||||
"originalUrl": {
|
||||
"deprecated": true,
|
||||
"type": "string",
|
||||
"description": "The original long URL. [DEPRECATED. Use longUrl instead]"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,11 +16,6 @@
|
|||
},
|
||||
"visitLocation": {
|
||||
"$ref": "./VisitLocation.json"
|
||||
},
|
||||
"remoteAddr": {
|
||||
"type": "string",
|
||||
"description": "This value is deprecated and will always be null",
|
||||
"deprecated": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,84 +0,0 @@
|
|||
{
|
||||
"post": {
|
||||
"deprecated": true,
|
||||
"operationId": "authenticate",
|
||||
"tags": [
|
||||
"Authentication"
|
||||
],
|
||||
"summary": "[Deprecated] Perform authentication",
|
||||
"description": "**This endpoint is deprecated, since the authentication can be performed via API key now**. Performs an authentication.",
|
||||
"requestBody": {
|
||||
"description": "Request body.",
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"apiKey"
|
||||
],
|
||||
"properties": {
|
||||
"apiKey": {
|
||||
"description": "The API key to authenticate with",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The authentication worked.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"token": {
|
||||
"type": "string",
|
||||
"description": "The authentication token that needs to be sent in the Authorization header"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"examples": {
|
||||
"application/json": {
|
||||
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "An API key was not provided.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "The API key is incorrect, is disabled or has expired.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Unexpected error.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,7 +5,7 @@
|
|||
"Short URLs"
|
||||
],
|
||||
"summary": "List short URLs",
|
||||
"description": "Returns the list of short URLs.<br><br>**Important note**: Before shlink v1.13, this endpoint used to use the `/short-codes` path instead of `/short-urls`. Both of them will keep working, while the first one is considered deprecated.",
|
||||
"description": "Returns the list of short URLs.",
|
||||
"parameters": [
|
||||
{
|
||||
"$ref": "../parameters/version.json"
|
||||
|
@ -77,9 +77,6 @@
|
|||
"security": [
|
||||
{
|
||||
"ApiKey": []
|
||||
},
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
|
@ -187,13 +184,10 @@
|
|||
"Short URLs"
|
||||
],
|
||||
"summary": "Create short URL",
|
||||
"description": "Creates a new short URL.<br><br>**Important note**: Before shlink v1.13, this endpoint used to use the `/short-codes` path instead of `/short-urls`. Both of them will keep working, while the first one is considered deprecated.<br></br>**Param findIfExists:**: Starting with v1.16, this new param allows to force shlink to return existing short URLs when found based on provided params, instead of creating a new one. However, it might add complexity and have unexpected outputs.\n\nThese are the use cases:\n* Only the long URL is provided: It will return the newest match or create a new short URL if none is found.\n* Long url and custom slug are provided: It will return the short URL when both params match, return an error when the slug is in use for another long URL, or create a new short URL otherwise.\n* Any of the above but including other params (tags, validSince, validUntil, maxVisits): It will behave the same as the previous two cases, but it will try to exactly match existing results using all the params. If any of them does not match, it will try to create a new short URL.",
|
||||
"description": "Creates a new short URL.<br></br>**Param findIfExists:**: Starting with v1.16, this new param allows to force shlink to return existing short URLs when found based on provided params, instead of creating a new one. However, it might add complexity and have unexpected outputs.\n\nThese are the use cases:\n* Only the long URL is provided: It will return the newest match or create a new short URL if none is found.\n* Long url and custom slug are provided: It will return the short URL when both params match, return an error when the slug is in use for another long URL, or create a new short URL otherwise.\n* Any of the above but including other params (tags, validSince, validUntil, maxVisits): It will behave the same as the previous two cases, but it will try to exactly match existing results using all the params. If any of them does not match, it will try to create a new short URL.",
|
||||
"security": [
|
||||
{
|
||||
"ApiKey": []
|
||||
},
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"parameters": [
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"Short URLs"
|
||||
],
|
||||
"summary": "Create a short URL",
|
||||
"description": "Creates a short URL in a single API call. Useful for third party integrations.<br><br>**Important note**: Before shlink v1.13, this endpoint used to use the `/short-codes` path instead of `/short-urls`. Both of them will keep working, while the first one is considered deprecated.",
|
||||
"description": "Creates a short URL in a single API call. Useful for third party integrations.",
|
||||
"parameters": [
|
||||
{
|
||||
"$ref": "../parameters/version.json"
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"Short URLs"
|
||||
],
|
||||
"summary": "Parse short code",
|
||||
"description": "Get the long URL behind a short URL's short code.<br><br>**Important note**: Before shlink v1.13, this endpoint used to use the `/short-codes` path instead of `/short-urls`. Both of them will keep working, while the first one is considered deprecated.",
|
||||
"description": "Get the long URL behind a short URL's short code.",
|
||||
"parameters": [
|
||||
{
|
||||
"$ref": "../parameters/version.json"
|
||||
|
@ -32,9 +32,6 @@
|
|||
"security": [
|
||||
{
|
||||
"ApiKey": []
|
||||
},
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
|
@ -94,7 +91,7 @@
|
|||
"Short URLs"
|
||||
],
|
||||
"summary": "Edit short URL",
|
||||
"description": "Update certain meta arguments from an existing short URL.<br><br>**Important note**: Before shlink v1.13, this endpoint used to use the `/short-codes` path instead of `/short-urls`. Both of them will keep working, while the first one is considered deprecated.",
|
||||
"description": "Update certain meta arguments from an existing short URL.",
|
||||
"parameters": [
|
||||
{
|
||||
"$ref": "../parameters/version.json"
|
||||
|
@ -137,9 +134,6 @@
|
|||
"security": [
|
||||
{
|
||||
"ApiKey": []
|
||||
},
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
|
@ -201,105 +195,13 @@
|
|||
}
|
||||
},
|
||||
|
||||
"put": {
|
||||
"deprecated": true,
|
||||
"operationId": "editShortUrlPut",
|
||||
"tags": [
|
||||
"Short URLs"
|
||||
],
|
||||
"summary": "[DEPRECATED] Edit short URL",
|
||||
"description": "**[DEPRECATED]** Use [editShortUrl](#/Short_URLs/getShortUrl) instead",
|
||||
"parameters": [
|
||||
{
|
||||
"$ref": "../parameters/version.json"
|
||||
},
|
||||
{
|
||||
"name": "shortCode",
|
||||
"in": "path",
|
||||
"description": "The short code to edit.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"description": "Request body.",
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"validSince": {
|
||||
"description": "The date (in ISO-8601 format) from which this short code will be valid",
|
||||
"type": "string"
|
||||
},
|
||||
"validUntil": {
|
||||
"description": "The date (in ISO-8601 format) until which this short code will be valid",
|
||||
"type": "string"
|
||||
},
|
||||
"maxVisits": {
|
||||
"description": "The maximum number of allowed visits for this short code",
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"ApiKey": []
|
||||
},
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "The short code has been properly updated."
|
||||
},
|
||||
"400": {
|
||||
"description": "Provided meta arguments are invalid.",
|
||||
"content": {
|
||||
"application/problem+json": {
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "No short URL was found for provided short code.",
|
||||
"content": {
|
||||
"application/problem+json": {
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Unexpected error.",
|
||||
"content": {
|
||||
"application/problem+json": {
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"delete": {
|
||||
"operationId": "deleteShortUrl",
|
||||
"tags": [
|
||||
"Short URLs"
|
||||
],
|
||||
"summary": "Delete short URL",
|
||||
"description": "Deletes the short URL for provided short code.<br><br>**Important note**: Before shlink v1.13, this endpoint used to use the `/short-codes` path instead of `/short-urls`. Both of them will keep working, while the first one is considered deprecated.",
|
||||
"description": "Deletes the short URL for provided short code.",
|
||||
"parameters": [
|
||||
{
|
||||
"$ref": "../parameters/version.json"
|
||||
|
@ -317,9 +219,6 @@
|
|||
"security": [
|
||||
{
|
||||
"ApiKey": []
|
||||
},
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"Short URLs"
|
||||
],
|
||||
"summary": "Edit tags on short URL",
|
||||
"description": "Edit the tags on URL identified by provided short code.<br><br>**Important note**: Before shlink v1.13, this endpoint used to use the `/short-codes` path instead of `/short-urls`. Both of them will keep working, while the first one is considered deprecated.",
|
||||
"description": "Edit the tags on URL identified by provided short code.",
|
||||
"parameters": [
|
||||
{
|
||||
"$ref": "../parameters/version.json"
|
||||
|
@ -46,9 +46,6 @@
|
|||
"security": [
|
||||
{
|
||||
"ApiKey": []
|
||||
},
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"Visits"
|
||||
],
|
||||
"summary": "List visits for short URL",
|
||||
"description": "Get the list of visits on the short URL behind provided short code.<br><br>**Important note**: Before shlink v1.13, this endpoint used to use the `/short-codes` path instead of `/short-urls`. Both of them will keep working, while the first one is considered deprecated.",
|
||||
"description": "Get the list of visits on the short URL behind provided short code.",
|
||||
"parameters": [
|
||||
{
|
||||
"$ref": "../parameters/version.json"
|
||||
|
@ -59,9 +59,6 @@
|
|||
"security": [
|
||||
{
|
||||
"ApiKey": []
|
||||
},
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
|
|
|
@ -9,9 +9,6 @@
|
|||
"security": [
|
||||
{
|
||||
"ApiKey": []
|
||||
},
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"parameters": [
|
||||
|
@ -78,9 +75,6 @@
|
|||
"security": [
|
||||
{
|
||||
"ApiKey": []
|
||||
},
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"parameters": [
|
||||
|
@ -170,9 +164,6 @@
|
|||
"security": [
|
||||
{
|
||||
"ApiKey": []
|
||||
},
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"parameters": [
|
||||
|
@ -279,9 +270,6 @@
|
|||
"security": [
|
||||
{
|
||||
"ApiKey": []
|
||||
},
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
|
|
|
@ -33,12 +33,6 @@
|
|||
"type": "apiKey",
|
||||
"in": "header",
|
||||
"name": "X-Api-Key"
|
||||
},
|
||||
"Bearer": {
|
||||
"description": "**[DEPRECATED]** The JWT identifying a previously authenticated API key",
|
||||
"type": "http",
|
||||
"scheme": "bearer",
|
||||
"bearerFormat": "JWT"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -63,10 +57,6 @@
|
|||
{
|
||||
"name": "URL Shortener",
|
||||
"description": "Non-rest endpoints, used to be publicly exposed"
|
||||
},
|
||||
{
|
||||
"name": "Authentication",
|
||||
"description": "**[DEPRECATED]** Authentication-related endpoints"
|
||||
}
|
||||
],
|
||||
|
||||
|
@ -104,10 +94,6 @@
|
|||
},
|
||||
"/{shortCode}/qr-code": {
|
||||
"$ref": "paths/{shortCode}_qr-code.json"
|
||||
},
|
||||
|
||||
"/rest/v1/authenticate": {
|
||||
"$ref": "paths/v1_authenticate.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,13 +17,10 @@ return [
|
|||
'plugins' => [
|
||||
'factories' => [
|
||||
Authentication\Plugin\ApiKeyHeaderPlugin::class => ConfigAbstractFactory::class,
|
||||
Authentication\Plugin\AuthorizationHeaderPlugin::class => ConfigAbstractFactory::class,
|
||||
],
|
||||
'aliases' => [
|
||||
Authentication\Plugin\ApiKeyHeaderPlugin::HEADER_NAME =>
|
||||
Authentication\Plugin\ApiKeyHeaderPlugin::class,
|
||||
Authentication\Plugin\AuthorizationHeaderPlugin::HEADER_NAME =>
|
||||
Authentication\Plugin\AuthorizationHeaderPlugin::class,
|
||||
],
|
||||
],
|
||||
],
|
||||
|
@ -39,7 +36,6 @@ return [
|
|||
],
|
||||
|
||||
ConfigAbstractFactory::class => [
|
||||
Authentication\Plugin\AuthorizationHeaderPlugin::class => [Authentication\JWTService::class],
|
||||
Authentication\Plugin\ApiKeyHeaderPlugin::class => [Service\ApiKeyService::class],
|
||||
|
||||
Authentication\RequestToHttpAuthPlugin::class => [Authentication\AuthenticationPluginManager::class],
|
||||
|
|
|
@ -17,7 +17,6 @@ return [
|
|||
|
||||
'dependencies' => [
|
||||
'factories' => [
|
||||
Authentication\JWTService::class => ConfigAbstractFactory::class,
|
||||
ApiKeyService::class => ConfigAbstractFactory::class,
|
||||
|
||||
Action\HealthAction::class => ConfigAbstractFactory::class,
|
||||
|
@ -43,7 +42,6 @@ return [
|
|||
],
|
||||
|
||||
ConfigAbstractFactory::class => [
|
||||
Authentication\JWTService::class => [AppOptions::class],
|
||||
ApiKeyService::class => ['em'],
|
||||
|
||||
Action\HealthAction::class => [Connection::class, AppOptions::class, 'Logger_Shlink'],
|
||||
|
|
|
@ -1,111 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Rest\Authentication;
|
||||
|
||||
use Firebase\JWT\JWT;
|
||||
use Shlinkio\Shlink\Core\Options\AppOptions;
|
||||
use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
||||
use Shlinkio\Shlink\Rest\Exception\AuthenticationException;
|
||||
use UnexpectedValueException;
|
||||
|
||||
use function time;
|
||||
|
||||
/** @deprecated */
|
||||
class JWTService implements JWTServiceInterface
|
||||
{
|
||||
/** @var AppOptions */
|
||||
private $appOptions;
|
||||
|
||||
public function __construct(AppOptions $appOptions)
|
||||
{
|
||||
$this->appOptions = $appOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new JSON web token por provided API key
|
||||
*
|
||||
* @param ApiKey $apiKey
|
||||
* @param int $lifetime
|
||||
* @return string
|
||||
*/
|
||||
public function create(ApiKey $apiKey, $lifetime = self::DEFAULT_LIFETIME): string
|
||||
{
|
||||
$currentTimestamp = time();
|
||||
|
||||
return $this->encode([
|
||||
'iss' => (string) $this->appOptions,
|
||||
'iat' => $currentTimestamp,
|
||||
'exp' => $currentTimestamp + $lifetime,
|
||||
'sub' => 'auth',
|
||||
'key' => $apiKey->getId(), // The ID is opaque. Returning the key would be insecure
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes a token and returns it with the new expiration
|
||||
*
|
||||
* @param string $jwt
|
||||
* @param int $lifetime
|
||||
* @return string
|
||||
* @throws AuthenticationException If the token has expired
|
||||
*/
|
||||
public function refresh(string $jwt, $lifetime = self::DEFAULT_LIFETIME): string
|
||||
{
|
||||
$payload = $this->getPayload($jwt);
|
||||
$payload['exp'] = time() + $lifetime;
|
||||
return $this->encode($payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that certain JWT is valid
|
||||
*
|
||||
* @param string $jwt
|
||||
* @return bool
|
||||
*/
|
||||
public function verify(string $jwt): bool
|
||||
{
|
||||
try {
|
||||
// If no exception is thrown while decoding the token, it is considered valid
|
||||
$this->decode($jwt);
|
||||
return true;
|
||||
} catch (UnexpectedValueException $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes certain token and returns the payload
|
||||
*
|
||||
* @param string $jwt
|
||||
* @return array
|
||||
* @throws AuthenticationException If the token has expired
|
||||
*/
|
||||
public function getPayload(string $jwt): array
|
||||
{
|
||||
try {
|
||||
return $this->decode($jwt);
|
||||
} catch (UnexpectedValueException $e) {
|
||||
throw AuthenticationException::expiredJWT($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
* @return string
|
||||
*/
|
||||
private function encode(array $data): string
|
||||
{
|
||||
return JWT::encode($data, '', self::DEFAULT_ENCRYPTION_ALG);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $jwt
|
||||
* @return array
|
||||
*/
|
||||
private function decode(string $jwt): array
|
||||
{
|
||||
return (array) JWT::decode($jwt, '', [self::DEFAULT_ENCRYPTION_ALG]);
|
||||
}
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Rest\Authentication;
|
||||
|
||||
use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
||||
use Shlinkio\Shlink\Rest\Exception\AuthenticationException;
|
||||
|
||||
interface JWTServiceInterface
|
||||
{
|
||||
public const DEFAULT_LIFETIME = 604800; // 1 week
|
||||
public const DEFAULT_ENCRYPTION_ALG = 'HS256';
|
||||
|
||||
/**
|
||||
* Creates a new JSON web token por provided API key
|
||||
*
|
||||
* @param ApiKey $apiKey
|
||||
* @param int $lifetime
|
||||
* @return string
|
||||
*/
|
||||
public function create(ApiKey $apiKey, $lifetime = self::DEFAULT_LIFETIME): string;
|
||||
|
||||
/**
|
||||
* Refreshes a token and returns it with the new expiration
|
||||
*
|
||||
* @param string $jwt
|
||||
* @param int $lifetime
|
||||
* @return string
|
||||
* @throws AuthenticationException If the token has expired
|
||||
*/
|
||||
public function refresh(string $jwt, $lifetime = self::DEFAULT_LIFETIME): string;
|
||||
|
||||
/**
|
||||
* Verifies that certain JWT is valid
|
||||
*
|
||||
* @param string $jwt
|
||||
* @return bool
|
||||
*/
|
||||
public function verify(string $jwt): bool;
|
||||
|
||||
/**
|
||||
* Decodes certain token and returns the payload
|
||||
*
|
||||
* @param string $jwt
|
||||
* @return array
|
||||
* @throws AuthenticationException If the token has expired
|
||||
*/
|
||||
public function getPayload(string $jwt): array;
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Rest\Authentication\Plugin;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Shlinkio\Shlink\Rest\Authentication\JWTServiceInterface;
|
||||
use Shlinkio\Shlink\Rest\Exception\VerifyAuthenticationException;
|
||||
use Throwable;
|
||||
|
||||
use function count;
|
||||
use function explode;
|
||||
use function sprintf;
|
||||
use function strtolower;
|
||||
|
||||
/** @deprecated */
|
||||
class AuthorizationHeaderPlugin implements AuthenticationPluginInterface
|
||||
{
|
||||
public const HEADER_NAME = 'Authorization';
|
||||
|
||||
/** @var JWTServiceInterface */
|
||||
private $jwtService;
|
||||
|
||||
public function __construct(JWTServiceInterface $jwtService)
|
||||
{
|
||||
$this->jwtService = $jwtService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws VerifyAuthenticationException
|
||||
*/
|
||||
public function verify(ServerRequestInterface $request): void
|
||||
{
|
||||
// Get token making sure the an authorization type is provided
|
||||
$authToken = $request->getHeaderLine(self::HEADER_NAME);
|
||||
$authTokenParts = explode(' ', $authToken);
|
||||
if (count($authTokenParts) === 1) {
|
||||
throw VerifyAuthenticationException::forMissingAuthType();
|
||||
}
|
||||
|
||||
// Make sure the authorization type is Bearer
|
||||
[$authType, $jwt] = $authTokenParts;
|
||||
if (strtolower($authType) !== 'bearer') {
|
||||
throw VerifyAuthenticationException::forInvalidAuthType($authType);
|
||||
}
|
||||
|
||||
try {
|
||||
if (! $this->jwtService->verify($jwt)) {
|
||||
throw $this->createInvalidTokenError();
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
throw $this->createInvalidTokenError();
|
||||
}
|
||||
}
|
||||
|
||||
private function createInvalidTokenError(): VerifyAuthenticationException
|
||||
{
|
||||
return VerifyAuthenticationException::forInvalidAuthToken();
|
||||
}
|
||||
|
||||
public function update(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
|
||||
{
|
||||
$authToken = $request->getHeaderLine(self::HEADER_NAME);
|
||||
[, $jwt] = explode(' ', $authToken);
|
||||
$jwt = $this->jwtService->refresh($jwt);
|
||||
|
||||
return $response->withHeader(self::HEADER_NAME, sprintf('Bearer %s', $jwt));
|
||||
}
|
||||
}
|
|
@ -17,7 +17,6 @@ class RequestToHttpAuthPlugin implements RequestToHttpAuthPluginInterface
|
|||
// When more than one is matched, the first one to be found will take precedence.
|
||||
public const SUPPORTED_AUTH_HEADERS = [
|
||||
Plugin\ApiKeyHeaderPlugin::HEADER_NAME,
|
||||
Plugin\AuthorizationHeaderPlugin::HEADER_NAME,
|
||||
];
|
||||
|
||||
private AuthenticationPluginManagerInterface $authPluginManager;
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Rest\Exception;
|
||||
|
||||
use Throwable;
|
||||
|
||||
/** @deprecated */
|
||||
class AuthenticationException extends RuntimeException
|
||||
{
|
||||
public static function expiredJWT(?Throwable $prev = null): self
|
||||
{
|
||||
return new self('The token has expired.', -1, $prev);
|
||||
}
|
||||
}
|
|
@ -8,8 +8,6 @@ use Fig\Http\Message\StatusCodeInterface;
|
|||
use Zend\ProblemDetails\Exception\CommonProblemDetailsExceptionTrait;
|
||||
use Zend\ProblemDetails\Exception\ProblemDetailsExceptionInterface;
|
||||
|
||||
use function sprintf;
|
||||
|
||||
class VerifyAuthenticationException extends RuntimeException implements ProblemDetailsExceptionInterface
|
||||
{
|
||||
use CommonProblemDetailsExceptionTrait;
|
||||
|
@ -25,46 +23,4 @@ class VerifyAuthenticationException extends RuntimeException implements ProblemD
|
|||
|
||||
return $e;
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
public static function forInvalidAuthToken(): self
|
||||
{
|
||||
$e = new self(
|
||||
'Missing or invalid auth token provided. Perform a new authentication request and send provided '
|
||||
. 'token on every new request on the Authorization header'
|
||||
);
|
||||
|
||||
$e->detail = $e->getMessage();
|
||||
$e->title = 'Invalid auth token';
|
||||
$e->type = 'INVALID_AUTH_TOKEN';
|
||||
$e->status = StatusCodeInterface::STATUS_UNAUTHORIZED;
|
||||
|
||||
return $e;
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
public static function forMissingAuthType(): self
|
||||
{
|
||||
$e = new self('You need to provide the Bearer type in the Authorization header.');
|
||||
|
||||
$e->detail = $e->getMessage();
|
||||
$e->title = 'Invalid authorization';
|
||||
$e->type = 'INVALID_AUTHORIZATION';
|
||||
$e->status = StatusCodeInterface::STATUS_UNAUTHORIZED;
|
||||
|
||||
return $e;
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
public static function forInvalidAuthType(string $providedType): self
|
||||
{
|
||||
$e = new self(sprintf('Provided authorization type %s is not supported. Use Bearer instead.', $providedType));
|
||||
|
||||
$e->detail = $e->getMessage();
|
||||
$e->title = 'Invalid authorization';
|
||||
$e->type = 'INVALID_AUTHORIZATION';
|
||||
$e->status = StatusCodeInterface::STATUS_UNAUTHORIZED;
|
||||
|
||||
return $e;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,6 @@ class CrossDomainMiddleware implements MiddlewareInterface, RequestMethodInterfa
|
|||
$response = $response->withHeader('Access-Control-Allow-Origin', $request->getHeader('Origin'))
|
||||
->withHeader('Access-Control-Expose-Headers', implode(', ', [
|
||||
Authentication\Plugin\ApiKeyHeaderPlugin::HEADER_NAME,
|
||||
Authentication\Plugin\AuthorizationHeaderPlugin::HEADER_NAME,
|
||||
]));
|
||||
if ($request->getMethod() !== self::METHOD_OPTIONS) {
|
||||
return $response;
|
||||
|
|
|
@ -59,51 +59,4 @@ class AuthenticationTest extends ApiTestCase
|
|||
yield 'key which is expired' => ['expired_api_key'];
|
||||
yield 'key which is disabled' => ['disabled_api_key'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @dataProvider provideInvalidAuthorizations
|
||||
*/
|
||||
public function authorizationErrorIsReturnedIfInvalidDataIsProvided(
|
||||
string $authValue,
|
||||
string $expectedDetail,
|
||||
string $expectedType,
|
||||
string $expectedTitle
|
||||
): void {
|
||||
$resp = $this->callApi(self::METHOD_GET, '/short-urls', [
|
||||
'headers' => [
|
||||
Plugin\AuthorizationHeaderPlugin::HEADER_NAME => $authValue,
|
||||
],
|
||||
]);
|
||||
$payload = $this->getJsonResponsePayload($resp);
|
||||
|
||||
$this->assertEquals(self::STATUS_UNAUTHORIZED, $resp->getStatusCode());
|
||||
$this->assertEquals(self::STATUS_UNAUTHORIZED, $payload['status']);
|
||||
$this->assertEquals($expectedType, $payload['type']);
|
||||
$this->assertEquals($expectedDetail, $payload['detail']);
|
||||
$this->assertEquals($expectedTitle, $payload['title']);
|
||||
}
|
||||
|
||||
public function provideInvalidAuthorizations(): iterable
|
||||
{
|
||||
yield 'no type' => [
|
||||
'invalid',
|
||||
'You need to provide the Bearer type in the Authorization header.',
|
||||
'INVALID_AUTHORIZATION',
|
||||
'Invalid authorization',
|
||||
];
|
||||
yield 'invalid type' => [
|
||||
'Basic invalid',
|
||||
'Provided authorization type Basic is not supported. Use Bearer instead.',
|
||||
'INVALID_AUTHORIZATION',
|
||||
'Invalid authorization',
|
||||
];
|
||||
yield 'invalid JWT' => [
|
||||
'Bearer invalid',
|
||||
'Missing or invalid auth token provided. Perform a new authentication request and send provided '
|
||||
. 'token on every new request on the Authorization header',
|
||||
'INVALID_AUTH_TOKEN',
|
||||
'Invalid auth token',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,119 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Rest\Authentication\Plugin;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\Rest\Authentication\JWTServiceInterface;
|
||||
use Shlinkio\Shlink\Rest\Authentication\Plugin\AuthorizationHeaderPlugin;
|
||||
use Shlinkio\Shlink\Rest\Exception\VerifyAuthenticationException;
|
||||
use Zend\Diactoros\Response;
|
||||
use Zend\Diactoros\ServerRequest;
|
||||
|
||||
use function sprintf;
|
||||
|
||||
/** @deprecated */
|
||||
class AuthorizationHeaderPluginTest extends TestCase
|
||||
{
|
||||
/** @var AuthorizationHeaderPlugin */
|
||||
private $plugin;
|
||||
/** @var ObjectProphecy */
|
||||
private $jwtService;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->jwtService = $this->prophesize(JWTServiceInterface::class);
|
||||
$this->plugin = new AuthorizationHeaderPlugin($this->jwtService->reveal());
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function verifyAnAuthorizationWithoutBearerTypeThrowsException()
|
||||
{
|
||||
$authToken = 'ABC-abc';
|
||||
$request = (new ServerRequest())->withHeader(
|
||||
AuthorizationHeaderPlugin::HEADER_NAME,
|
||||
$authToken
|
||||
);
|
||||
|
||||
$this->expectException(VerifyAuthenticationException::class);
|
||||
$this->expectExceptionMessage(sprintf(
|
||||
'You need to provide the Bearer type in the %s header.',
|
||||
AuthorizationHeaderPlugin::HEADER_NAME
|
||||
));
|
||||
|
||||
$this->plugin->verify($request);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function verifyAnAuthorizationWithWrongTypeThrowsException()
|
||||
{
|
||||
$authToken = 'Basic ABC-abc';
|
||||
$request = (new ServerRequest())->withHeader(
|
||||
AuthorizationHeaderPlugin::HEADER_NAME,
|
||||
$authToken
|
||||
);
|
||||
|
||||
$this->expectException(VerifyAuthenticationException::class);
|
||||
$this->expectExceptionMessage(
|
||||
'Provided authorization type Basic is not supported. Use Bearer instead.'
|
||||
);
|
||||
|
||||
$this->plugin->verify($request);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function verifyAnExpiredTokenThrowsException()
|
||||
{
|
||||
$authToken = 'Bearer ABC-abc';
|
||||
$request = (new ServerRequest())->withHeader(
|
||||
AuthorizationHeaderPlugin::HEADER_NAME,
|
||||
$authToken
|
||||
);
|
||||
$jwtVerify = $this->jwtService->verify('ABC-abc')->willReturn(false);
|
||||
|
||||
$this->expectException(VerifyAuthenticationException::class);
|
||||
$this->expectExceptionMessage(sprintf(
|
||||
'Missing or invalid auth token provided. Perform a new authentication request and send provided '
|
||||
. 'token on every new request on the %s header',
|
||||
AuthorizationHeaderPlugin::HEADER_NAME
|
||||
));
|
||||
|
||||
$this->plugin->verify($request);
|
||||
|
||||
$jwtVerify->shouldHaveBeenCalledOnce();
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function verifyValidTokenDoesNotThrowException()
|
||||
{
|
||||
$authToken = 'Bearer ABC-abc';
|
||||
$request = (new ServerRequest())->withHeader(
|
||||
AuthorizationHeaderPlugin::HEADER_NAME,
|
||||
$authToken
|
||||
);
|
||||
$jwtVerify = $this->jwtService->verify('ABC-abc')->willReturn(true);
|
||||
|
||||
$this->plugin->verify($request);
|
||||
|
||||
$jwtVerify->shouldHaveBeenCalledOnce();
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function updateReturnsAnUpdatedResponseWithNewJwt()
|
||||
{
|
||||
$authToken = 'Bearer ABC-abc';
|
||||
$request = (new ServerRequest())->withHeader(
|
||||
AuthorizationHeaderPlugin::HEADER_NAME,
|
||||
$authToken
|
||||
);
|
||||
$jwtRefresh = $this->jwtService->refresh('ABC-abc')->willReturn('DEF-def');
|
||||
|
||||
$response = $this->plugin->update($request, new Response());
|
||||
|
||||
$this->assertTrue($response->hasHeader(AuthorizationHeaderPlugin::HEADER_NAME));
|
||||
$this->assertEquals('Bearer DEF-def', $response->getHeaderLine(AuthorizationHeaderPlugin::HEADER_NAME));
|
||||
$jwtRefresh->shouldHaveBeenCalledOnce();
|
||||
}
|
||||
}
|
|
@ -9,7 +9,6 @@ use Prophecy\Prophecy\ObjectProphecy;
|
|||
use Shlinkio\Shlink\Rest\Authentication\AuthenticationPluginManagerInterface;
|
||||
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\MissingAuthenticationException;
|
||||
use Zend\Diactoros\ServerRequest;
|
||||
|
@ -63,14 +62,7 @@ class RequestToAuthPluginTest extends TestCase
|
|||
|
||||
public function provideHeaders(): iterable
|
||||
{
|
||||
yield 'API key header only' => [[
|
||||
ApiKeyHeaderPlugin::HEADER_NAME => 'foobar',
|
||||
], ApiKeyHeaderPlugin::HEADER_NAME];
|
||||
yield 'Authorization header only' => [[
|
||||
AuthorizationHeaderPlugin::HEADER_NAME => 'foobar',
|
||||
], AuthorizationHeaderPlugin::HEADER_NAME];
|
||||
yield 'Both headers' => [[
|
||||
AuthorizationHeaderPlugin::HEADER_NAME => 'foobar',
|
||||
yield 'API key header' => [[
|
||||
ApiKeyHeaderPlugin::HEADER_NAME => 'foobar',
|
||||
], ApiKeyHeaderPlugin::HEADER_NAME];
|
||||
}
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Rest\Exception;
|
||||
|
||||
use Exception;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\Rest\Exception\AuthenticationException;
|
||||
use Throwable;
|
||||
|
||||
class AuthenticationExceptionTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @test
|
||||
* @dataProvider providePrev
|
||||
*/
|
||||
public function expiredJWTCreatesExpectedException(?Throwable $prev): void
|
||||
{
|
||||
$e = AuthenticationException::expiredJWT($prev);
|
||||
|
||||
$this->assertEquals($prev, $e->getPrevious());
|
||||
$this->assertEquals(-1, $e->getCode());
|
||||
$this->assertEquals('The token has expired.', $e->getMessage());
|
||||
}
|
||||
|
||||
public function providePrev(): iterable
|
||||
{
|
||||
yield 'with previous exception' => [new Exception('Prev')];
|
||||
yield 'without previous exception' => [null];
|
||||
}
|
||||
}
|
|
@ -15,7 +15,6 @@ use Zend\Diactoros\ServerRequest;
|
|||
use Zend\Expressive\Router\Route;
|
||||
use Zend\Expressive\Router\RouteResult;
|
||||
|
||||
use function implode;
|
||||
use function Zend\Stratigility\middleware;
|
||||
|
||||
class CrossDomainMiddlewareTest extends TestCase
|
||||
|
@ -62,10 +61,10 @@ class CrossDomainMiddlewareTest extends TestCase
|
|||
$headers = $response->getHeaders();
|
||||
|
||||
$this->assertEquals('local', $response->getHeaderLine('Access-Control-Allow-Origin'));
|
||||
$this->assertEquals(implode(', ', [
|
||||
$this->assertEquals(
|
||||
Authentication\Plugin\ApiKeyHeaderPlugin::HEADER_NAME,
|
||||
Authentication\Plugin\AuthorizationHeaderPlugin::HEADER_NAME,
|
||||
]), $response->getHeaderLine('Access-Control-Expose-Headers'));
|
||||
$response->getHeaderLine('Access-Control-Expose-Headers')
|
||||
);
|
||||
$this->assertArrayNotHasKey('Access-Control-Allow-Methods', $headers);
|
||||
$this->assertArrayNotHasKey('Access-Control-Max-Age', $headers);
|
||||
$this->assertArrayNotHasKey('Access-Control-Allow-Headers', $headers);
|
||||
|
@ -87,10 +86,10 @@ class CrossDomainMiddlewareTest extends TestCase
|
|||
$headers = $response->getHeaders();
|
||||
|
||||
$this->assertEquals('local', $response->getHeaderLine('Access-Control-Allow-Origin'));
|
||||
$this->assertEquals(implode(', ', [
|
||||
$this->assertEquals(
|
||||
Authentication\Plugin\ApiKeyHeaderPlugin::HEADER_NAME,
|
||||
Authentication\Plugin\AuthorizationHeaderPlugin::HEADER_NAME,
|
||||
]), $response->getHeaderLine('Access-Control-Expose-Headers'));
|
||||
$response->getHeaderLine('Access-Control-Expose-Headers')
|
||||
);
|
||||
$this->assertArrayHasKey('Access-Control-Allow-Methods', $headers);
|
||||
$this->assertEquals('1000', $response->getHeaderLine('Access-Control-Max-Age'));
|
||||
$this->assertEquals('foo, bar, baz', $response->getHeaderLine('Access-Control-Allow-Headers'));
|
||||
|
|
Loading…
Add table
Reference in a new issue