mirror of
https://github.com/shlinkio/shlink.git
synced 2025-02-25 18:45:27 -06:00
Removed several deprecated components
This commit is contained in:
@@ -2,14 +2,11 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use function Shlinkio\Shlink\Common\env;
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'app_options' => [
|
'app_options' => [
|
||||||
'name' => 'Shlink',
|
'name' => 'Shlink',
|
||||||
'version' => '%SHLINK_VERSION%',
|
'version' => '%SHLINK_VERSION%',
|
||||||
'secret_key' => env('SECRET_KEY', ''),
|
|
||||||
'disable_track_param' => null,
|
'disable_track_param' => null,
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@@ -15,10 +15,6 @@ return [
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
'backwards_compatible_problem_details' => [
|
|
||||||
'json_flags' => JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRESERVE_ZERO_FRACTION,
|
|
||||||
],
|
|
||||||
|
|
||||||
'error_handler' => [
|
'error_handler' => [
|
||||||
'listeners' => [Logger\ErrorLogger::class],
|
'listeners' => [Logger\ErrorLogger::class],
|
||||||
],
|
],
|
||||||
|
@@ -21,7 +21,6 @@ return [
|
|||||||
'path' => '/rest',
|
'path' => '/rest',
|
||||||
'middleware' => [
|
'middleware' => [
|
||||||
Rest\Middleware\CrossDomainMiddleware::class,
|
Rest\Middleware\CrossDomainMiddleware::class,
|
||||||
Rest\Middleware\BackwardsCompatibleProblemDetailsMiddleware::class,
|
|
||||||
ProblemDetails\ProblemDetailsMiddleware::class,
|
ProblemDetails\ProblemDetailsMiddleware::class,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
@@ -35,7 +34,6 @@ return [
|
|||||||
'path' => '/rest',
|
'path' => '/rest',
|
||||||
'middleware' => [
|
'middleware' => [
|
||||||
Rest\Middleware\PathVersionMiddleware::class,
|
Rest\Middleware\PathVersionMiddleware::class,
|
||||||
Rest\Middleware\ShortUrl\ShortCodePathMiddleware::class,
|
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@@ -119,7 +119,6 @@ This is the complete list of supported env vars:
|
|||||||
|
|
||||||
In the future, these redis servers could be used for other caching operations performed by shlink.
|
In the future, these redis servers could be used for other caching operations performed by shlink.
|
||||||
|
|
||||||
* `NOT_FOUND_REDIRECT_TO`: **Deprecated since v1.20 in favor of `INVALID_SHORT_URL_REDIRECT_TO`** If a URL is provided here, when a user tries to access an invalid short URL, he/she will be redirected to this value. If this env var is not provided, the user will see a generic `404 - not found` page.
|
|
||||||
* `SHORTCODE_CHARS`: **Ignored when using Shlink 1.20 or newer**. A charset to use when building short codes. Only needed when using more than one shlink instance ([Multi instance considerations](#multi-instance-considerations)).
|
* `SHORTCODE_CHARS`: **Ignored when using Shlink 1.20 or newer**. A charset to use when building short codes. Only needed when using more than one shlink instance ([Multi instance considerations](#multi-instance-considerations)).
|
||||||
|
|
||||||
An example using all env vars could look like this:
|
An example using all env vars could look like this:
|
||||||
@@ -186,15 +185,12 @@ The whole configuration should have this format, but it can be split into multip
|
|||||||
"password": "123abc",
|
"password": "123abc",
|
||||||
"host": "something.rds.amazonaws.com",
|
"host": "something.rds.amazonaws.com",
|
||||||
"port": "3306"
|
"port": "3306"
|
||||||
},
|
}
|
||||||
"not_found_redirect_to": "https://my-landing-page.com"
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
> This is internally parsed to how shlink expects the config. If you are using a version previous to 1.17.0, this parser is not present and you need to provide a config structure like the one [documented previously](https://github.com/shlinkio/shlink-docker-image/tree/v1.16.3#provide-config-via-volumes).
|
> This is internally parsed to how shlink expects the config. If you are using a version previous to 1.17.0, this parser is not present and you need to provide a config structure like the one [documented previously](https://github.com/shlinkio/shlink-docker-image/tree/v1.16.3#provide-config-via-volumes).
|
||||||
|
|
||||||
> The `not_found_redirect_to` option has been deprecated in v1.20. Use `invalid_short_url_redirect_to` instead (however, it will still work for backwards compatibility).
|
|
||||||
|
|
||||||
Once created just run shlink with the volume:
|
Once created just run shlink with the volume:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
@@ -8,19 +8,10 @@ use Monolog\Handler\StreamHandler;
|
|||||||
use Monolog\Logger;
|
use Monolog\Logger;
|
||||||
|
|
||||||
use function explode;
|
use function explode;
|
||||||
use function file_exists;
|
|
||||||
use function file_get_contents;
|
|
||||||
use function file_put_contents;
|
|
||||||
use function Functional\contains;
|
use function Functional\contains;
|
||||||
use function implode;
|
|
||||||
use function Shlinkio\Shlink\Common\env;
|
use function Shlinkio\Shlink\Common\env;
|
||||||
use function sprintf;
|
|
||||||
use function str_shuffle;
|
|
||||||
use function substr;
|
|
||||||
use function sys_get_temp_dir;
|
|
||||||
|
|
||||||
$helper = new class {
|
$helper = new class {
|
||||||
private const BASE62 = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
|
||||||
private const DB_DRIVERS_MAP = [
|
private const DB_DRIVERS_MAP = [
|
||||||
'mysql' => 'pdo_mysql',
|
'mysql' => 'pdo_mysql',
|
||||||
'maria' => 'pdo_mysql',
|
'maria' => 'pdo_mysql',
|
||||||
@@ -32,40 +23,6 @@ $helper = new class {
|
|||||||
'postgres' => '5432',
|
'postgres' => '5432',
|
||||||
];
|
];
|
||||||
|
|
||||||
/** @var string */
|
|
||||||
private $secretKey;
|
|
||||||
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
[, $this->secretKey] = $this->initShlinkSecretKey();
|
|
||||||
}
|
|
||||||
|
|
||||||
private function initShlinkSecretKey(): array
|
|
||||||
{
|
|
||||||
$keysFile = sprintf('%s/shlink.keys', sys_get_temp_dir());
|
|
||||||
if (file_exists($keysFile)) {
|
|
||||||
return explode(',', file_get_contents($keysFile));
|
|
||||||
}
|
|
||||||
|
|
||||||
$keys = [
|
|
||||||
'', // This was the SHORTCODE_CHARS. Kept as empty string for BC
|
|
||||||
env('SECRET_KEY', $this->generateSecretKey()), // Deprecated
|
|
||||||
];
|
|
||||||
|
|
||||||
file_put_contents($keysFile, implode(',', $keys));
|
|
||||||
return $keys;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function generateSecretKey(): string
|
|
||||||
{
|
|
||||||
return substr(str_shuffle(self::BASE62), 0, 32);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getSecretKey(): string
|
|
||||||
{
|
|
||||||
return $this->secretKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getDbConfig(): array
|
public function getDbConfig(): array
|
||||||
{
|
{
|
||||||
$driver = env('DB_DRIVER');
|
$driver = env('DB_DRIVER');
|
||||||
@@ -94,7 +51,7 @@ $helper = new class {
|
|||||||
public function getNotFoundRedirectsConfig(): array
|
public function getNotFoundRedirectsConfig(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'invalid_short_url' => env('INVALID_SHORT_URL_REDIRECT_TO', env('NOT_FOUND_REDIRECT_TO')),
|
'invalid_short_url' => env('INVALID_SHORT_URL_REDIRECT_TO'),
|
||||||
'regular_404' => env('REGULAR_404_REDIRECT_TO'),
|
'regular_404' => env('REGULAR_404_REDIRECT_TO'),
|
||||||
'base_url' => env('BASE_URL_REDIRECT_TO'),
|
'base_url' => env('BASE_URL_REDIRECT_TO'),
|
||||||
];
|
];
|
||||||
@@ -112,7 +69,6 @@ return [
|
|||||||
'config_cache_enabled' => false,
|
'config_cache_enabled' => false,
|
||||||
|
|
||||||
'app_options' => [
|
'app_options' => [
|
||||||
'secret_key' => $helper->getSecretKey(),
|
|
||||||
'disable_track_param' => env('DISABLE_TRACK_PARAM'),
|
'disable_track_param' => env('DISABLE_TRACK_PARAM'),
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@@ -15,10 +15,6 @@ return [
|
|||||||
Command\ShortUrl\DeleteShortUrlCommand::NAME => Command\ShortUrl\DeleteShortUrlCommand::class,
|
Command\ShortUrl\DeleteShortUrlCommand::NAME => Command\ShortUrl\DeleteShortUrlCommand::class,
|
||||||
|
|
||||||
Command\Visit\LocateVisitsCommand::NAME => Command\Visit\LocateVisitsCommand::class,
|
Command\Visit\LocateVisitsCommand::NAME => Command\Visit\LocateVisitsCommand::class,
|
||||||
Command\Visit\UpdateDbCommand::NAME => Command\Visit\UpdateDbCommand::class,
|
|
||||||
|
|
||||||
Command\Config\GenerateCharsetCommand::NAME => Command\Config\GenerateCharsetCommand::class,
|
|
||||||
Command\Config\GenerateSecretCommand::NAME => Command\Config\GenerateSecretCommand::class,
|
|
||||||
|
|
||||||
Command\Api\GenerateKeyCommand::NAME => Command\Api\GenerateKeyCommand::class,
|
Command\Api\GenerateKeyCommand::NAME => Command\Api\GenerateKeyCommand::class,
|
||||||
Command\Api\DisableKeyCommand::NAME => Command\Api\DisableKeyCommand::class,
|
Command\Api\DisableKeyCommand::NAME => Command\Api\DisableKeyCommand::class,
|
||||||
|
@@ -36,10 +36,6 @@ return [
|
|||||||
Command\ShortUrl\DeleteShortUrlCommand::class => ConfigAbstractFactory::class,
|
Command\ShortUrl\DeleteShortUrlCommand::class => ConfigAbstractFactory::class,
|
||||||
|
|
||||||
Command\Visit\LocateVisitsCommand::class => ConfigAbstractFactory::class,
|
Command\Visit\LocateVisitsCommand::class => ConfigAbstractFactory::class,
|
||||||
Command\Visit\UpdateDbCommand::class => ConfigAbstractFactory::class,
|
|
||||||
|
|
||||||
Command\Config\GenerateCharsetCommand::class => InvokableFactory::class,
|
|
||||||
Command\Config\GenerateSecretCommand::class => InvokableFactory::class,
|
|
||||||
|
|
||||||
Command\Api\GenerateKeyCommand::class => ConfigAbstractFactory::class,
|
Command\Api\GenerateKeyCommand::class => ConfigAbstractFactory::class,
|
||||||
Command\Api\DisableKeyCommand::class => ConfigAbstractFactory::class,
|
Command\Api\DisableKeyCommand::class => ConfigAbstractFactory::class,
|
||||||
@@ -70,7 +66,6 @@ return [
|
|||||||
LockFactory::class,
|
LockFactory::class,
|
||||||
GeolocationDbUpdater::class,
|
GeolocationDbUpdater::class,
|
||||||
],
|
],
|
||||||
Command\Visit\UpdateDbCommand::class => [DbUpdater::class],
|
|
||||||
|
|
||||||
Command\Api\GenerateKeyCommand::class => [ApiKeyService::class],
|
Command\Api\GenerateKeyCommand::class => [ApiKeyService::class],
|
||||||
Command\Api\DisableKeyCommand::class => [ApiKeyService::class],
|
Command\Api\DisableKeyCommand::class => [ApiKeyService::class],
|
||||||
|
@@ -1,40 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Shlinkio\Shlink\CLI\Command\Config;
|
|
||||||
|
|
||||||
use Shlinkio\Shlink\CLI\Util\ExitCodes;
|
|
||||||
use Symfony\Component\Console\Command\Command;
|
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
|
||||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
|
||||||
|
|
||||||
use function sprintf;
|
|
||||||
use function str_shuffle;
|
|
||||||
|
|
||||||
/** @deprecated */
|
|
||||||
class GenerateCharsetCommand extends Command
|
|
||||||
{
|
|
||||||
public const NAME = 'config:generate-charset';
|
|
||||||
private const DEFAULT_CHARS = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
|
||||||
|
|
||||||
protected function configure(): void
|
|
||||||
{
|
|
||||||
$this
|
|
||||||
->setName(self::NAME)
|
|
||||||
->setDescription(sprintf(
|
|
||||||
'[DEPRECATED] Generates a character set sample just by shuffling the default one, "%s". '
|
|
||||||
. 'Then it can be set in the SHORTCODE_CHARS environment variable',
|
|
||||||
self::DEFAULT_CHARS
|
|
||||||
))
|
|
||||||
->setHelp('<fg=red;options=bold>This command is deprecated. Better leave shlink generate the charset.</>');
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function execute(InputInterface $input, OutputInterface $output): ?int
|
|
||||||
{
|
|
||||||
$charSet = str_shuffle(self::DEFAULT_CHARS);
|
|
||||||
(new SymfonyStyle($input, $output))->success(sprintf('Character set: "%s"', $charSet));
|
|
||||||
return ExitCodes::EXIT_SUCCESS;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,39 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Shlinkio\Shlink\CLI\Command\Config;
|
|
||||||
|
|
||||||
use Shlinkio\Shlink\CLI\Util\ExitCodes;
|
|
||||||
use Shlinkio\Shlink\Common\Util\StringUtilsTrait;
|
|
||||||
use Symfony\Component\Console\Command\Command;
|
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
|
||||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
|
||||||
|
|
||||||
use function sprintf;
|
|
||||||
|
|
||||||
/** @deprecated */
|
|
||||||
class GenerateSecretCommand extends Command
|
|
||||||
{
|
|
||||||
use StringUtilsTrait;
|
|
||||||
|
|
||||||
public const NAME = 'config:generate-secret';
|
|
||||||
|
|
||||||
protected function configure(): void
|
|
||||||
{
|
|
||||||
$this
|
|
||||||
->setName(self::NAME)
|
|
||||||
->setDescription('[DEPRECATED] Generates a random secret string that can be used for JWT token encryption')
|
|
||||||
->setHelp(
|
|
||||||
'<fg=red;options=bold>This command is deprecated. Better leave shlink generate the secret key.</>'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function execute(InputInterface $input, OutputInterface $output): ?int
|
|
||||||
{
|
|
||||||
$secret = $this->generateRandomString(32);
|
|
||||||
(new SymfonyStyle($input, $output))->success(sprintf('Secret key: "%s"', $secret));
|
|
||||||
return ExitCodes::EXIT_SUCCESS;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,91 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Shlinkio\Shlink\CLI\Command\Visit;
|
|
||||||
|
|
||||||
use Shlinkio\Shlink\CLI\Util\ExitCodes;
|
|
||||||
use Shlinkio\Shlink\IpGeolocation\Exception\RuntimeException;
|
|
||||||
use Shlinkio\Shlink\IpGeolocation\GeoLite2\DbUpdaterInterface;
|
|
||||||
use Symfony\Component\Console\Command\Command;
|
|
||||||
use Symfony\Component\Console\Helper\ProgressBar;
|
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
|
||||||
use Symfony\Component\Console\Input\InputOption;
|
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
|
||||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
|
||||||
|
|
||||||
use function sprintf;
|
|
||||||
|
|
||||||
/** @deprecated */
|
|
||||||
class UpdateDbCommand extends Command
|
|
||||||
{
|
|
||||||
public const NAME = 'visit:update-db';
|
|
||||||
|
|
||||||
/** @var DbUpdaterInterface */
|
|
||||||
private $geoLiteDbUpdater;
|
|
||||||
|
|
||||||
public function __construct(DbUpdaterInterface $geoLiteDbUpdater)
|
|
||||||
{
|
|
||||||
parent::__construct();
|
|
||||||
$this->geoLiteDbUpdater = $geoLiteDbUpdater;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function configure(): void
|
|
||||||
{
|
|
||||||
$this
|
|
||||||
->setName(self::NAME)
|
|
||||||
->setDescription('[DEPRECATED] Updates the GeoLite2 database file used to geolocate IP addresses')
|
|
||||||
->setHelp(
|
|
||||||
'The GeoLite2 database is updated first Tuesday every month, so this command should be ideally run '
|
|
||||||
. 'every first Wednesday'
|
|
||||||
)
|
|
||||||
->addOption(
|
|
||||||
'ignoreErrors',
|
|
||||||
'i',
|
|
||||||
InputOption::VALUE_NONE,
|
|
||||||
'Makes the command success even iof the update fails.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function execute(InputInterface $input, OutputInterface $output): ?int
|
|
||||||
{
|
|
||||||
$io = new SymfonyStyle($input, $output);
|
|
||||||
$progressBar = new ProgressBar($output);
|
|
||||||
$progressBar->start();
|
|
||||||
|
|
||||||
try {
|
|
||||||
$this->geoLiteDbUpdater->downloadFreshCopy(function (int $total, int $downloaded) use ($progressBar) {
|
|
||||||
$progressBar->setMaxSteps($total);
|
|
||||||
$progressBar->setProgress($downloaded);
|
|
||||||
});
|
|
||||||
|
|
||||||
$progressBar->finish();
|
|
||||||
$io->newLine();
|
|
||||||
|
|
||||||
$io->success('GeoLite2 database properly updated');
|
|
||||||
return ExitCodes::EXIT_SUCCESS;
|
|
||||||
} catch (RuntimeException $e) {
|
|
||||||
$progressBar->finish();
|
|
||||||
$io->newLine();
|
|
||||||
|
|
||||||
return $this->handleError($e, $io, $input);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function handleError(RuntimeException $e, SymfonyStyle $io, InputInterface $input): int
|
|
||||||
{
|
|
||||||
$ignoreErrors = $input->getOption('ignoreErrors');
|
|
||||||
$baseErrorMsg = 'An error occurred while updating GeoLite2 database';
|
|
||||||
|
|
||||||
if ($ignoreErrors) {
|
|
||||||
$io->warning(sprintf('%s, but it was ignored', $baseErrorMsg));
|
|
||||||
return ExitCodes::EXIT_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
$io->error($baseErrorMsg);
|
|
||||||
if ($io->isVerbose()) {
|
|
||||||
$this->getApplication()->renderThrowable($e, $io);
|
|
||||||
}
|
|
||||||
return ExitCodes::EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,47 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace ShlinkioTest\Shlink\CLI\Command\Config;
|
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
|
||||||
use Shlinkio\Shlink\CLI\Command\Config\GenerateCharsetCommand;
|
|
||||||
use Symfony\Component\Console\Application;
|
|
||||||
use Symfony\Component\Console\Tester\CommandTester;
|
|
||||||
|
|
||||||
use function implode;
|
|
||||||
use function sort;
|
|
||||||
use function str_split;
|
|
||||||
|
|
||||||
class GenerateCharsetCommandTest extends TestCase
|
|
||||||
{
|
|
||||||
private CommandTester $commandTester;
|
|
||||||
|
|
||||||
public function setUp(): void
|
|
||||||
{
|
|
||||||
$command = new GenerateCharsetCommand();
|
|
||||||
$app = new Application();
|
|
||||||
$app->add($command);
|
|
||||||
|
|
||||||
$this->commandTester = new CommandTester($command);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function charactersAreGeneratedFromDefault()
|
|
||||||
{
|
|
||||||
$prefix = 'Character set: ';
|
|
||||||
|
|
||||||
$this->commandTester->execute([]);
|
|
||||||
$output = $this->commandTester->getDisplay();
|
|
||||||
|
|
||||||
// Both default character set and the new one should have the same length
|
|
||||||
$this->assertStringContainsString($prefix, $output);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function orderStringLetters($string)
|
|
||||||
{
|
|
||||||
$letters = str_split($string);
|
|
||||||
sort($letters);
|
|
||||||
return implode('', $letters);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,75 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace ShlinkioTest\Shlink\CLI\Command\Visit;
|
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
|
||||||
use Prophecy\Argument;
|
|
||||||
use Prophecy\Prophecy\ObjectProphecy;
|
|
||||||
use Shlinkio\Shlink\CLI\Command\Visit\UpdateDbCommand;
|
|
||||||
use Shlinkio\Shlink\CLI\Util\ExitCodes;
|
|
||||||
use Shlinkio\Shlink\IpGeolocation\Exception\RuntimeException;
|
|
||||||
use Shlinkio\Shlink\IpGeolocation\GeoLite2\DbUpdaterInterface;
|
|
||||||
use Symfony\Component\Console\Application;
|
|
||||||
use Symfony\Component\Console\Tester\CommandTester;
|
|
||||||
|
|
||||||
class UpdateDbCommandTest extends TestCase
|
|
||||||
{
|
|
||||||
private CommandTester $commandTester;
|
|
||||||
private ObjectProphecy $dbUpdater;
|
|
||||||
|
|
||||||
public function setUp(): void
|
|
||||||
{
|
|
||||||
$this->dbUpdater = $this->prophesize(DbUpdaterInterface::class);
|
|
||||||
|
|
||||||
$command = new UpdateDbCommand($this->dbUpdater->reveal());
|
|
||||||
$app = new Application();
|
|
||||||
$app->add($command);
|
|
||||||
|
|
||||||
$this->commandTester = new CommandTester($command);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function successMessageIsPrintedIfEverythingWorks(): void
|
|
||||||
{
|
|
||||||
$download = $this->dbUpdater->downloadFreshCopy(Argument::type('callable'))->will(function () {
|
|
||||||
});
|
|
||||||
|
|
||||||
$this->commandTester->execute([]);
|
|
||||||
$output = $this->commandTester->getDisplay();
|
|
||||||
$exitCode = $this->commandTester->getStatusCode();
|
|
||||||
|
|
||||||
$this->assertStringContainsString('GeoLite2 database properly updated', $output);
|
|
||||||
$this->assertEquals(ExitCodes::EXIT_SUCCESS, $exitCode);
|
|
||||||
$download->shouldHaveBeenCalledOnce();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function errorMessageIsPrintedIfAnExceptionIsThrown(): void
|
|
||||||
{
|
|
||||||
$download = $this->dbUpdater->downloadFreshCopy(Argument::type('callable'))->willThrow(RuntimeException::class);
|
|
||||||
|
|
||||||
$this->commandTester->execute([]);
|
|
||||||
$output = $this->commandTester->getDisplay();
|
|
||||||
$exitCode = $this->commandTester->getStatusCode();
|
|
||||||
|
|
||||||
$this->assertStringContainsString('An error occurred while updating GeoLite2 database', $output);
|
|
||||||
$this->assertEquals(ExitCodes::EXIT_FAILURE, $exitCode);
|
|
||||||
$download->shouldHaveBeenCalledOnce();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function warningMessageIsPrintedIfAnExceptionIsThrownAndErrorsAreIgnored(): void
|
|
||||||
{
|
|
||||||
$download = $this->dbUpdater->downloadFreshCopy(Argument::type('callable'))->willThrow(RuntimeException::class);
|
|
||||||
|
|
||||||
$this->commandTester->execute(['--ignoreErrors' => true]);
|
|
||||||
$output = $this->commandTester->getDisplay();
|
|
||||||
$exitCode = $this->commandTester->getStatusCode();
|
|
||||||
|
|
||||||
$this->assertStringContainsString('ignored', $output);
|
|
||||||
$this->assertEquals(ExitCodes::EXIT_SUCCESS, $exitCode);
|
|
||||||
$download->shouldHaveBeenCalledOnce();
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,9 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
return [
|
|
||||||
|
|
||||||
'app_options' => [],
|
|
||||||
|
|
||||||
];
|
|
@@ -22,7 +22,6 @@ class SimplifiedConfigParser
|
|||||||
'short_domain_schema' => ['url_shortener', 'domain', 'schema'],
|
'short_domain_schema' => ['url_shortener', 'domain', 'schema'],
|
||||||
'short_domain_host' => ['url_shortener', 'domain', 'hostname'],
|
'short_domain_host' => ['url_shortener', 'domain', 'hostname'],
|
||||||
'validate_url' => ['url_shortener', 'validate_url'],
|
'validate_url' => ['url_shortener', 'validate_url'],
|
||||||
'not_found_redirect_to' => ['not_found_redirects', 'invalid_short_url'], // Deprecated
|
|
||||||
'invalid_short_url_redirect_to' => ['not_found_redirects', 'invalid_short_url'],
|
'invalid_short_url_redirect_to' => ['not_found_redirects', 'invalid_short_url'],
|
||||||
'regular_404_redirect_to' => ['not_found_redirects', 'regular_404'],
|
'regular_404_redirect_to' => ['not_found_redirects', 'regular_404'],
|
||||||
'base_url_redirect_to' => ['not_found_redirects', 'base_path'],
|
'base_url_redirect_to' => ['not_found_redirects', 'base_path'],
|
||||||
|
@@ -90,9 +90,6 @@ class Visit extends AbstractEntity implements JsonSerializable
|
|||||||
'date' => $this->date->toAtomString(),
|
'date' => $this->date->toAtomString(),
|
||||||
'userAgent' => $this->userAgent,
|
'userAgent' => $this->userAgent,
|
||||||
'visitLocation' => $this->visitLocation,
|
'visitLocation' => $this->visitLocation,
|
||||||
|
|
||||||
// Deprecated
|
|
||||||
'remoteAddr' => null,
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -78,7 +78,7 @@ class NotifyVisitToWebHooks
|
|||||||
'User-Agent' => (string) $this->appOptions,
|
'User-Agent' => (string) $this->appOptions,
|
||||||
],
|
],
|
||||||
RequestOptions::JSON => [
|
RequestOptions::JSON => [
|
||||||
'shortUrl' => $this->transformer->transform($visit->getShortUrl(), false),
|
'shortUrl' => $this->transformer->transform($visit->getShortUrl()),
|
||||||
'visit' => $visit->jsonSerialize(),
|
'visit' => $visit->jsonSerialize(),
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
@@ -15,8 +15,6 @@ class AppOptions extends AbstractOptions
|
|||||||
|
|
||||||
private string $name = '';
|
private string $name = '';
|
||||||
private string $version = '1.0';
|
private string $version = '1.0';
|
||||||
/** @deprecated */
|
|
||||||
private string $secretKey = '';
|
|
||||||
private ?string $disableTrackParam = null;
|
private ?string $disableTrackParam = null;
|
||||||
|
|
||||||
public function getName(): string
|
public function getName(): string
|
||||||
@@ -41,23 +39,6 @@ class AppOptions extends AbstractOptions
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated
|
|
||||||
*/
|
|
||||||
public function getSecretKey(): string
|
|
||||||
{
|
|
||||||
return $this->secretKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated
|
|
||||||
*/
|
|
||||||
protected function setSecretKey(string $secretKey): self
|
|
||||||
{
|
|
||||||
$this->secretKey = $secretKey;
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return string|null
|
* @return string|null
|
||||||
*/
|
*/
|
||||||
|
@@ -22,11 +22,11 @@ class ShortUrlDataTransformer implements DataTransformerInterface
|
|||||||
/**
|
/**
|
||||||
* @param ShortUrl $shortUrl
|
* @param ShortUrl $shortUrl
|
||||||
*/
|
*/
|
||||||
public function transform($shortUrl, bool $includeDeprecated = true): array
|
public function transform($shortUrl): array
|
||||||
{
|
{
|
||||||
$longUrl = $shortUrl->getLongUrl();
|
$longUrl = $shortUrl->getLongUrl();
|
||||||
|
|
||||||
$rawData = [
|
return [
|
||||||
'shortCode' => $shortUrl->getShortCode(),
|
'shortCode' => $shortUrl->getShortCode(),
|
||||||
'shortUrl' => $shortUrl->toString($this->domainConfig),
|
'shortUrl' => $shortUrl->toString($this->domainConfig),
|
||||||
'longUrl' => $longUrl,
|
'longUrl' => $longUrl,
|
||||||
@@ -35,12 +35,6 @@ class ShortUrlDataTransformer implements DataTransformerInterface
|
|||||||
'tags' => invoke($shortUrl->getTags(), '__toString'),
|
'tags' => invoke($shortUrl->getTags(), '__toString'),
|
||||||
'meta' => $this->buildMeta($shortUrl),
|
'meta' => $this->buildMeta($shortUrl),
|
||||||
];
|
];
|
||||||
|
|
||||||
if ($includeDeprecated) {
|
|
||||||
$rawData['originalUrl'] = $longUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $rawData;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function buildMeta(ShortUrl $shortUrl): array
|
private function buildMeta(ShortUrl $shortUrl): array
|
||||||
|
@@ -11,7 +11,7 @@ use function array_merge;
|
|||||||
|
|
||||||
class SimplifiedConfigParserTest extends TestCase
|
class SimplifiedConfigParserTest extends TestCase
|
||||||
{
|
{
|
||||||
private $postProcessor;
|
private SimplifiedConfigParser $postProcessor;
|
||||||
|
|
||||||
public function setUp(): void
|
public function setUp(): void
|
||||||
{
|
{
|
||||||
@@ -40,7 +40,7 @@ class SimplifiedConfigParserTest extends TestCase
|
|||||||
'short_domain_host' => 'doma.in',
|
'short_domain_host' => 'doma.in',
|
||||||
'validate_url' => false,
|
'validate_url' => false,
|
||||||
'delete_short_url_threshold' => 50,
|
'delete_short_url_threshold' => 50,
|
||||||
'not_found_redirect_to' => 'foobar.com',
|
'invalid_short_url_redirect_to' => 'foobar.com',
|
||||||
'redis_servers' => [
|
'redis_servers' => [
|
||||||
'tcp://1.1.1.1:1111',
|
'tcp://1.1.1.1:1111',
|
||||||
'tcp://1.2.2.2:2222',
|
'tcp://1.2.2.2:2222',
|
||||||
@@ -125,28 +125,4 @@ class SimplifiedConfigParserTest extends TestCase
|
|||||||
|
|
||||||
$this->assertEquals(array_merge($expected, $simplified), $result);
|
$this->assertEquals(array_merge($expected, $simplified), $result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @test
|
|
||||||
* @dataProvider provideConfigWithDeprecates
|
|
||||||
*/
|
|
||||||
public function properlyMapsDeprecatedConfigs(array $config, string $expected): void
|
|
||||||
{
|
|
||||||
$result = ($this->postProcessor)($config);
|
|
||||||
$this->assertEquals($expected, $result['not_found_redirects']['invalid_short_url']);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function provideConfigWithDeprecates(): iterable
|
|
||||||
{
|
|
||||||
yield 'only deprecated config' => [['not_found_redirect_to' => 'old_value'], 'old_value'];
|
|
||||||
yield 'only new config' => [['invalid_short_url_redirect_to' => 'new_value'], 'new_value'];
|
|
||||||
yield 'both configs, new first' => [
|
|
||||||
['invalid_short_url_redirect_to' => 'new_value', 'not_found_redirect_to' => 'old_value'],
|
|
||||||
'new_value',
|
|
||||||
];
|
|
||||||
yield 'both configs, deprecated first' => [
|
|
||||||
['not_found_redirect_to' => 'old_value', 'invalid_short_url_redirect_to' => 'new_value'],
|
|
||||||
'new_value',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -25,9 +25,6 @@ class VisitTest extends TestCase
|
|||||||
'date' => ($date ?? $visit->getDate())->toAtomString(),
|
'date' => ($date ?? $visit->getDate())->toAtomString(),
|
||||||
'userAgent' => 'Chrome',
|
'userAgent' => 'Chrome',
|
||||||
'visitLocation' => null,
|
'visitLocation' => null,
|
||||||
|
|
||||||
// Deprecated
|
|
||||||
'remoteAddr' => null,
|
|
||||||
], $visit->jsonSerialize());
|
], $visit->jsonSerialize());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -10,7 +10,6 @@ return [
|
|||||||
|
|
||||||
'auth' => [
|
'auth' => [
|
||||||
'routes_whitelist' => [
|
'routes_whitelist' => [
|
||||||
Action\AuthenticateAction::class,
|
|
||||||
Action\HealthAction::class,
|
Action\HealthAction::class,
|
||||||
Action\ShortUrl\SingleStepCreateShortUrlAction::class,
|
Action\ShortUrl\SingleStepCreateShortUrlAction::class,
|
||||||
],
|
],
|
||||||
|
@@ -20,7 +20,6 @@ return [
|
|||||||
Authentication\JWTService::class => ConfigAbstractFactory::class,
|
Authentication\JWTService::class => ConfigAbstractFactory::class,
|
||||||
ApiKeyService::class => ConfigAbstractFactory::class,
|
ApiKeyService::class => ConfigAbstractFactory::class,
|
||||||
|
|
||||||
Action\AuthenticateAction::class => ConfigAbstractFactory::class,
|
|
||||||
Action\HealthAction::class => ConfigAbstractFactory::class,
|
Action\HealthAction::class => ConfigAbstractFactory::class,
|
||||||
Action\ShortUrl\CreateShortUrlAction::class => ConfigAbstractFactory::class,
|
Action\ShortUrl\CreateShortUrlAction::class => ConfigAbstractFactory::class,
|
||||||
Action\ShortUrl\SingleStepCreateShortUrlAction::class => ConfigAbstractFactory::class,
|
Action\ShortUrl\SingleStepCreateShortUrlAction::class => ConfigAbstractFactory::class,
|
||||||
@@ -39,9 +38,7 @@ return [
|
|||||||
Middleware\BodyParserMiddleware::class => InvokableFactory::class,
|
Middleware\BodyParserMiddleware::class => InvokableFactory::class,
|
||||||
Middleware\CrossDomainMiddleware::class => InvokableFactory::class,
|
Middleware\CrossDomainMiddleware::class => InvokableFactory::class,
|
||||||
Middleware\PathVersionMiddleware::class => InvokableFactory::class,
|
Middleware\PathVersionMiddleware::class => InvokableFactory::class,
|
||||||
Middleware\BackwardsCompatibleProblemDetailsMiddleware::class => ConfigAbstractFactory::class,
|
|
||||||
Middleware\ShortUrl\CreateShortUrlContentNegotiationMiddleware::class => InvokableFactory::class,
|
Middleware\ShortUrl\CreateShortUrlContentNegotiationMiddleware::class => InvokableFactory::class,
|
||||||
Middleware\ShortUrl\ShortCodePathMiddleware::class => InvokableFactory::class,
|
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
@@ -49,7 +46,6 @@ return [
|
|||||||
Authentication\JWTService::class => [AppOptions::class],
|
Authentication\JWTService::class => [AppOptions::class],
|
||||||
ApiKeyService::class => ['em'],
|
ApiKeyService::class => ['em'],
|
||||||
|
|
||||||
Action\AuthenticateAction::class => [ApiKeyService::class, Authentication\JWTService::class, 'Logger_Shlink'],
|
|
||||||
Action\HealthAction::class => [Connection::class, AppOptions::class, 'Logger_Shlink'],
|
Action\HealthAction::class => [Connection::class, AppOptions::class, 'Logger_Shlink'],
|
||||||
Action\ShortUrl\CreateShortUrlAction::class => [
|
Action\ShortUrl\CreateShortUrlAction::class => [
|
||||||
Service\UrlShortener::class,
|
Service\UrlShortener::class,
|
||||||
@@ -76,10 +72,6 @@ return [
|
|||||||
Action\Tag\DeleteTagsAction::class => [Service\Tag\TagService::class, LoggerInterface::class],
|
Action\Tag\DeleteTagsAction::class => [Service\Tag\TagService::class, LoggerInterface::class],
|
||||||
Action\Tag\CreateTagsAction::class => [Service\Tag\TagService::class, LoggerInterface::class],
|
Action\Tag\CreateTagsAction::class => [Service\Tag\TagService::class, LoggerInterface::class],
|
||||||
Action\Tag\UpdateTagAction::class => [Service\Tag\TagService::class, LoggerInterface::class],
|
Action\Tag\UpdateTagAction::class => [Service\Tag\TagService::class, LoggerInterface::class],
|
||||||
|
|
||||||
Middleware\BackwardsCompatibleProblemDetailsMiddleware::class => [
|
|
||||||
'config.backwards_compatible_problem_details.json_flags',
|
|
||||||
],
|
|
||||||
],
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
@@ -7,7 +7,6 @@ namespace Shlinkio\Shlink\Rest;
|
|||||||
return [
|
return [
|
||||||
|
|
||||||
'routes' => [
|
'routes' => [
|
||||||
Action\AuthenticateAction::getRouteDef(),
|
|
||||||
Action\HealthAction::getRouteDef(),
|
Action\HealthAction::getRouteDef(),
|
||||||
|
|
||||||
// Short codes
|
// Short codes
|
||||||
|
@@ -1,64 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Shlinkio\Shlink\Rest\Action;
|
|
||||||
|
|
||||||
use Psr\Http\Message\ResponseInterface as Response;
|
|
||||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
|
||||||
use Psr\Log\LoggerInterface;
|
|
||||||
use Shlinkio\Shlink\Rest\Authentication\JWTServiceInterface;
|
|
||||||
use Shlinkio\Shlink\Rest\Service\ApiKeyService;
|
|
||||||
use Shlinkio\Shlink\Rest\Service\ApiKeyServiceInterface;
|
|
||||||
use Zend\Diactoros\Response\JsonResponse;
|
|
||||||
|
|
||||||
/** @deprecated */
|
|
||||||
class AuthenticateAction extends AbstractRestAction
|
|
||||||
{
|
|
||||||
protected const ROUTE_PATH = '/authenticate';
|
|
||||||
protected const ROUTE_ALLOWED_METHODS = [self::METHOD_POST];
|
|
||||||
|
|
||||||
/** @var ApiKeyService|ApiKeyServiceInterface */
|
|
||||||
private $apiKeyService;
|
|
||||||
/** @var JWTServiceInterface */
|
|
||||||
private $jwtService;
|
|
||||||
|
|
||||||
public function __construct(
|
|
||||||
ApiKeyServiceInterface $apiKeyService,
|
|
||||||
JWTServiceInterface $jwtService,
|
|
||||||
?LoggerInterface $logger = null
|
|
||||||
) {
|
|
||||||
parent::__construct($logger);
|
|
||||||
$this->apiKeyService = $apiKeyService;
|
|
||||||
$this->jwtService = $jwtService;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Request $request
|
|
||||||
* @return Response
|
|
||||||
* @throws \InvalidArgumentException
|
|
||||||
*/
|
|
||||||
public function handle(Request $request): Response
|
|
||||||
{
|
|
||||||
$authData = $request->getParsedBody();
|
|
||||||
if (! isset($authData['apiKey'])) {
|
|
||||||
return new JsonResponse([
|
|
||||||
'error' => 'INVALID_ARGUMENT',
|
|
||||||
'message' => 'You have to provide a valid API key under the "apiKey" param name.',
|
|
||||||
], self::STATUS_BAD_REQUEST);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Authenticate using provided API key
|
|
||||||
$apiKey = $this->apiKeyService->getByKey($authData['apiKey']);
|
|
||||||
if ($apiKey === null || ! $apiKey->isValid()) {
|
|
||||||
return new JsonResponse([
|
|
||||||
'error' => 'INVALID_API_KEY',
|
|
||||||
'message' => 'Provided API key does not exist or is invalid.',
|
|
||||||
], self::STATUS_UNAUTHORIZED);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate a JSON Web Token that will be used for authorization in next requests
|
|
||||||
$token = $this->jwtService->create($apiKey);
|
|
||||||
return new JsonResponse(['token' => $token]);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -97,7 +97,7 @@ class JWTService implements JWTServiceInterface
|
|||||||
*/
|
*/
|
||||||
private function encode(array $data): string
|
private function encode(array $data): string
|
||||||
{
|
{
|
||||||
return JWT::encode($data, $this->appOptions->getSecretKey(), self::DEFAULT_ENCRYPTION_ALG);
|
return JWT::encode($data, '', self::DEFAULT_ENCRYPTION_ALG);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -106,6 +106,6 @@ class JWTService implements JWTServiceInterface
|
|||||||
*/
|
*/
|
||||||
private function decode(string $jwt): array
|
private function decode(string $jwt): array
|
||||||
{
|
{
|
||||||
return (array) JWT::decode($jwt, $this->appOptions->getSecretKey(), [self::DEFAULT_ENCRYPTION_ALG]);
|
return (array) JWT::decode($jwt, '', [self::DEFAULT_ENCRYPTION_ALG]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,63 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Shlinkio\Shlink\Rest\Middleware;
|
|
||||||
|
|
||||||
use Psr\Http\Message\ResponseInterface;
|
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
|
||||||
use Psr\Http\Server\MiddlewareInterface;
|
|
||||||
use Psr\Http\Server\RequestHandlerInterface;
|
|
||||||
use Throwable;
|
|
||||||
use Zend\Diactoros\Response\JsonResponse;
|
|
||||||
|
|
||||||
use function Functional\reduce_left;
|
|
||||||
use function Shlinkio\Shlink\Common\json_decode;
|
|
||||||
use function strpos;
|
|
||||||
|
|
||||||
/** @deprecated */
|
|
||||||
class BackwardsCompatibleProblemDetailsMiddleware implements MiddlewareInterface
|
|
||||||
{
|
|
||||||
private const BACKWARDS_COMPATIBLE_FIELDS = [
|
|
||||||
'error' => 'type',
|
|
||||||
'message' => 'detail',
|
|
||||||
];
|
|
||||||
|
|
||||||
private int $jsonFlags;
|
|
||||||
|
|
||||||
public function __construct(int $jsonFlags)
|
|
||||||
{
|
|
||||||
$this->jsonFlags = $jsonFlags;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
|
||||||
{
|
|
||||||
$resp = $handler->handle($request);
|
|
||||||
if ($resp->getHeaderLine('Content-type') !== 'application/problem+json' || ! $this->isVersionOne($request)) {
|
|
||||||
return $resp;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$body = (string) $resp->getBody();
|
|
||||||
$payload = $this->makePayloadBackwardsCompatible(json_decode($body));
|
|
||||||
} catch (Throwable $e) {
|
|
||||||
return $resp;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new JsonResponse($payload, $resp->getStatusCode(), $resp->getHeaders(), $this->jsonFlags);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function isVersionOne(ServerRequestInterface $request): bool
|
|
||||||
{
|
|
||||||
$path = $request->getUri()->getPath();
|
|
||||||
return strpos($path, '/v') === false || strpos($path, '/v1') === 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function makePayloadBackwardsCompatible(array $payload): array
|
|
||||||
{
|
|
||||||
return reduce_left(self::BACKWARDS_COMPATIBLE_FIELDS, function (string $newKey, string $oldKey, $c, $acc) {
|
|
||||||
$acc[$oldKey] = $acc[$newKey];
|
|
||||||
return $acc;
|
|
||||||
}, $payload);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,34 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Shlinkio\Shlink\Rest\Middleware\ShortUrl;
|
|
||||||
|
|
||||||
use Psr\Http\Message\ResponseInterface;
|
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
|
||||||
use Psr\Http\Server\MiddlewareInterface;
|
|
||||||
use Psr\Http\Server\RequestHandlerInterface;
|
|
||||||
|
|
||||||
use function str_replace;
|
|
||||||
|
|
||||||
/** @deprecated */
|
|
||||||
class ShortCodePathMiddleware implements MiddlewareInterface
|
|
||||||
{
|
|
||||||
private const OLD_PATH_PREFIX = '/short-codes'; // Old path is deprecated. Remove this middleware on v2
|
|
||||||
private const NEW_PATH_PREFIX = '/short-urls';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Process an incoming server request and return a response, optionally delegating
|
|
||||||
* response creation to a handler.
|
|
||||||
*/
|
|
||||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
|
||||||
{
|
|
||||||
$uri = $request->getUri();
|
|
||||||
$path = $uri->getPath();
|
|
||||||
|
|
||||||
// If the path starts with the old prefix, replace it by the new one
|
|
||||||
return $handler->handle(
|
|
||||||
$request->withUri($uri->withPath(str_replace(self::OLD_PATH_PREFIX, self::NEW_PATH_PREFIX, $path)))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -49,9 +49,7 @@ class CreateShortUrlActionTest extends ApiTestCase
|
|||||||
$this->assertEquals(self::STATUS_BAD_REQUEST, $statusCode);
|
$this->assertEquals(self::STATUS_BAD_REQUEST, $statusCode);
|
||||||
$this->assertEquals(self::STATUS_BAD_REQUEST, $payload['status']);
|
$this->assertEquals(self::STATUS_BAD_REQUEST, $payload['status']);
|
||||||
$this->assertEquals($detail, $payload['detail']);
|
$this->assertEquals($detail, $payload['detail']);
|
||||||
$this->assertEquals($detail, $payload['message']); // Deprecated
|
|
||||||
$this->assertEquals('INVALID_SLUG', $payload['type']);
|
$this->assertEquals('INVALID_SLUG', $payload['type']);
|
||||||
$this->assertEquals('INVALID_SLUG', $payload['error']); // Deprecated
|
|
||||||
$this->assertEquals('Invalid custom slug', $payload['title']);
|
$this->assertEquals('Invalid custom slug', $payload['title']);
|
||||||
$this->assertEquals($slug, $payload['customSlug']);
|
$this->assertEquals($slug, $payload['customSlug']);
|
||||||
|
|
||||||
@@ -215,7 +213,7 @@ class CreateShortUrlActionTest extends ApiTestCase
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @test */
|
/** @test */
|
||||||
public function failsToCreateShortUrlWithInvalidOriginalUrl(): void
|
public function failsToCreateShortUrlWithInvalidLongUrl(): void
|
||||||
{
|
{
|
||||||
$url = 'https://this-has-to-be-invalid.com';
|
$url = 'https://this-has-to-be-invalid.com';
|
||||||
$expectedDetail = sprintf('Provided URL %s is invalid. Try with a different one.', $url);
|
$expectedDetail = sprintf('Provided URL %s is invalid. Try with a different one.', $url);
|
||||||
@@ -225,9 +223,7 @@ class CreateShortUrlActionTest extends ApiTestCase
|
|||||||
$this->assertEquals(self::STATUS_BAD_REQUEST, $statusCode);
|
$this->assertEquals(self::STATUS_BAD_REQUEST, $statusCode);
|
||||||
$this->assertEquals(self::STATUS_BAD_REQUEST, $payload['status']);
|
$this->assertEquals(self::STATUS_BAD_REQUEST, $payload['status']);
|
||||||
$this->assertEquals('INVALID_URL', $payload['type']);
|
$this->assertEquals('INVALID_URL', $payload['type']);
|
||||||
$this->assertEquals('INVALID_URL', $payload['error']); // Deprecated
|
|
||||||
$this->assertEquals($expectedDetail, $payload['detail']);
|
$this->assertEquals($expectedDetail, $payload['detail']);
|
||||||
$this->assertEquals($expectedDetail, $payload['message']); // Deprecated
|
|
||||||
$this->assertEquals('Invalid URL', $payload['title']);
|
$this->assertEquals('Invalid URL', $payload['title']);
|
||||||
$this->assertEquals($url, $payload['url']);
|
$this->assertEquals($url, $payload['url']);
|
||||||
}
|
}
|
||||||
|
@@ -19,9 +19,7 @@ class DeleteShortUrlActionTest extends ApiTestCase
|
|||||||
$this->assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode());
|
$this->assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode());
|
||||||
$this->assertEquals(self::STATUS_NOT_FOUND, $payload['status']);
|
$this->assertEquals(self::STATUS_NOT_FOUND, $payload['status']);
|
||||||
$this->assertEquals('INVALID_SHORTCODE', $payload['type']);
|
$this->assertEquals('INVALID_SHORTCODE', $payload['type']);
|
||||||
$this->assertEquals('INVALID_SHORTCODE', $payload['error']); // Deprecated
|
|
||||||
$this->assertEquals($expectedDetail, $payload['detail']);
|
$this->assertEquals($expectedDetail, $payload['detail']);
|
||||||
$this->assertEquals($expectedDetail, $payload['message']); // Deprecated
|
|
||||||
$this->assertEquals('Short URL not found', $payload['title']);
|
$this->assertEquals('Short URL not found', $payload['title']);
|
||||||
$this->assertEquals('invalid', $payload['shortCode']);
|
$this->assertEquals('invalid', $payload['shortCode']);
|
||||||
}
|
}
|
||||||
@@ -41,9 +39,7 @@ class DeleteShortUrlActionTest extends ApiTestCase
|
|||||||
$this->assertEquals(self::STATUS_UNPROCESSABLE_ENTITY, $resp->getStatusCode());
|
$this->assertEquals(self::STATUS_UNPROCESSABLE_ENTITY, $resp->getStatusCode());
|
||||||
$this->assertEquals(self::STATUS_UNPROCESSABLE_ENTITY, $payload['status']);
|
$this->assertEquals(self::STATUS_UNPROCESSABLE_ENTITY, $payload['status']);
|
||||||
$this->assertEquals('INVALID_SHORTCODE_DELETION', $payload['type']);
|
$this->assertEquals('INVALID_SHORTCODE_DELETION', $payload['type']);
|
||||||
$this->assertEquals('INVALID_SHORTCODE_DELETION', $payload['error']); // Deprecated
|
|
||||||
$this->assertEquals($expectedDetail, $payload['detail']);
|
$this->assertEquals($expectedDetail, $payload['detail']);
|
||||||
$this->assertEquals($expectedDetail, $payload['message']); // Deprecated
|
|
||||||
$this->assertEquals('Cannot delete short URL', $payload['title']);
|
$this->assertEquals('Cannot delete short URL', $payload['title']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -20,9 +20,7 @@ class EditShortUrlActionTest extends ApiTestCase
|
|||||||
$this->assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode());
|
$this->assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode());
|
||||||
$this->assertEquals(self::STATUS_NOT_FOUND, $payload['status']);
|
$this->assertEquals(self::STATUS_NOT_FOUND, $payload['status']);
|
||||||
$this->assertEquals('INVALID_SHORTCODE', $payload['type']);
|
$this->assertEquals('INVALID_SHORTCODE', $payload['type']);
|
||||||
$this->assertEquals('INVALID_SHORTCODE', $payload['error']); // Deprecated
|
|
||||||
$this->assertEquals($expectedDetail, $payload['detail']);
|
$this->assertEquals($expectedDetail, $payload['detail']);
|
||||||
$this->assertEquals($expectedDetail, $payload['message']); // Deprecated
|
|
||||||
$this->assertEquals('Short URL not found', $payload['title']);
|
$this->assertEquals('Short URL not found', $payload['title']);
|
||||||
$this->assertEquals('invalid', $payload['shortCode']);
|
$this->assertEquals('invalid', $payload['shortCode']);
|
||||||
}
|
}
|
||||||
@@ -40,9 +38,7 @@ class EditShortUrlActionTest extends ApiTestCase
|
|||||||
$this->assertEquals(self::STATUS_BAD_REQUEST, $resp->getStatusCode());
|
$this->assertEquals(self::STATUS_BAD_REQUEST, $resp->getStatusCode());
|
||||||
$this->assertEquals(self::STATUS_BAD_REQUEST, $payload['status']);
|
$this->assertEquals(self::STATUS_BAD_REQUEST, $payload['status']);
|
||||||
$this->assertEquals('INVALID_ARGUMENT', $payload['type']);
|
$this->assertEquals('INVALID_ARGUMENT', $payload['type']);
|
||||||
$this->assertEquals('INVALID_ARGUMENT', $payload['error']); // Deprecated
|
|
||||||
$this->assertEquals($expectedDetail, $payload['detail']);
|
$this->assertEquals($expectedDetail, $payload['detail']);
|
||||||
$this->assertEquals($expectedDetail, $payload['message']); // Deprecated
|
|
||||||
$this->assertEquals('Invalid data', $payload['title']);
|
$this->assertEquals('Invalid data', $payload['title']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -20,9 +20,7 @@ class EditShortUrlTagsActionTest extends ApiTestCase
|
|||||||
$this->assertEquals(self::STATUS_BAD_REQUEST, $resp->getStatusCode());
|
$this->assertEquals(self::STATUS_BAD_REQUEST, $resp->getStatusCode());
|
||||||
$this->assertEquals(self::STATUS_BAD_REQUEST, $payload['status']);
|
$this->assertEquals(self::STATUS_BAD_REQUEST, $payload['status']);
|
||||||
$this->assertEquals('INVALID_ARGUMENT', $payload['type']);
|
$this->assertEquals('INVALID_ARGUMENT', $payload['type']);
|
||||||
$this->assertEquals('INVALID_ARGUMENT', $payload['error']); // Deprecated
|
|
||||||
$this->assertEquals($expectedDetail, $payload['detail']);
|
$this->assertEquals($expectedDetail, $payload['detail']);
|
||||||
$this->assertEquals($expectedDetail, $payload['message']); // Deprecated
|
|
||||||
$this->assertEquals('Invalid data', $payload['title']);
|
$this->assertEquals('Invalid data', $payload['title']);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,9 +37,7 @@ class EditShortUrlTagsActionTest extends ApiTestCase
|
|||||||
$this->assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode());
|
$this->assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode());
|
||||||
$this->assertEquals(self::STATUS_NOT_FOUND, $payload['status']);
|
$this->assertEquals(self::STATUS_NOT_FOUND, $payload['status']);
|
||||||
$this->assertEquals('INVALID_SHORTCODE', $payload['type']);
|
$this->assertEquals('INVALID_SHORTCODE', $payload['type']);
|
||||||
$this->assertEquals('INVALID_SHORTCODE', $payload['error']); // Deprecated
|
|
||||||
$this->assertEquals($expectedDetail, $payload['detail']);
|
$this->assertEquals($expectedDetail, $payload['detail']);
|
||||||
$this->assertEquals($expectedDetail, $payload['message']); // Deprecated
|
|
||||||
$this->assertEquals('Short URL not found', $payload['title']);
|
$this->assertEquals('Short URL not found', $payload['title']);
|
||||||
$this->assertEquals('invalid', $payload['shortCode']);
|
$this->assertEquals('invalid', $payload['shortCode']);
|
||||||
}
|
}
|
||||||
|
@@ -19,9 +19,7 @@ class GetVisitsActionTest extends ApiTestCase
|
|||||||
$this->assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode());
|
$this->assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode());
|
||||||
$this->assertEquals(self::STATUS_NOT_FOUND, $payload['status']);
|
$this->assertEquals(self::STATUS_NOT_FOUND, $payload['status']);
|
||||||
$this->assertEquals('INVALID_SHORTCODE', $payload['type']);
|
$this->assertEquals('INVALID_SHORTCODE', $payload['type']);
|
||||||
$this->assertEquals('INVALID_SHORTCODE', $payload['error']); // Deprecated
|
|
||||||
$this->assertEquals($expectedDetail, $payload['detail']);
|
$this->assertEquals($expectedDetail, $payload['detail']);
|
||||||
$this->assertEquals($expectedDetail, $payload['message']); // Deprecated
|
|
||||||
$this->assertEquals('Short URL not found', $payload['title']);
|
$this->assertEquals('Short URL not found', $payload['title']);
|
||||||
$this->assertEquals('invalid', $payload['shortCode']);
|
$this->assertEquals('invalid', $payload['shortCode']);
|
||||||
}
|
}
|
||||||
|
@@ -24,7 +24,6 @@ class ListShortUrlsTest extends ApiTestCase
|
|||||||
'validUntil' => null,
|
'validUntil' => null,
|
||||||
'maxVisits' => null,
|
'maxVisits' => null,
|
||||||
],
|
],
|
||||||
'originalUrl' => 'https://shlink.io',
|
|
||||||
];
|
];
|
||||||
private const SHORT_URL_CUSTOM_SLUG_AND_DOMAIN = [
|
private const SHORT_URL_CUSTOM_SLUG_AND_DOMAIN = [
|
||||||
'shortCode' => 'custom-with-domain',
|
'shortCode' => 'custom-with-domain',
|
||||||
@@ -38,7 +37,6 @@ class ListShortUrlsTest extends ApiTestCase
|
|||||||
'validUntil' => null,
|
'validUntil' => null,
|
||||||
'maxVisits' => null,
|
'maxVisits' => null,
|
||||||
],
|
],
|
||||||
'originalUrl' => 'https://google.com',
|
|
||||||
];
|
];
|
||||||
private const SHORT_URL_META = [
|
private const SHORT_URL_META = [
|
||||||
'shortCode' => 'def456',
|
'shortCode' => 'def456',
|
||||||
@@ -54,9 +52,6 @@ class ListShortUrlsTest extends ApiTestCase
|
|||||||
'validUntil' => null,
|
'validUntil' => null,
|
||||||
'maxVisits' => null,
|
'maxVisits' => null,
|
||||||
],
|
],
|
||||||
'originalUrl' =>
|
|
||||||
'https://blog.alejandrocelaya.com/2017/12/09'
|
|
||||||
. '/acmailer-7-0-the-most-important-release-in-a-long-time/',
|
|
||||||
];
|
];
|
||||||
private const SHORT_URL_CUSTOM_SLUG = [
|
private const SHORT_URL_CUSTOM_SLUG = [
|
||||||
'shortCode' => 'custom',
|
'shortCode' => 'custom',
|
||||||
@@ -70,7 +65,6 @@ class ListShortUrlsTest extends ApiTestCase
|
|||||||
'validUntil' => null,
|
'validUntil' => null,
|
||||||
'maxVisits' => 2,
|
'maxVisits' => 2,
|
||||||
],
|
],
|
||||||
'originalUrl' => 'https://shlink.io',
|
|
||||||
];
|
];
|
||||||
private const SHORT_URL_CUSTOM_DOMAIN = [
|
private const SHORT_URL_CUSTOM_DOMAIN = [
|
||||||
'shortCode' => 'ghi789',
|
'shortCode' => 'ghi789',
|
||||||
@@ -86,9 +80,6 @@ class ListShortUrlsTest extends ApiTestCase
|
|||||||
'validUntil' => null,
|
'validUntil' => null,
|
||||||
'maxVisits' => null,
|
'maxVisits' => null,
|
||||||
],
|
],
|
||||||
'originalUrl' =>
|
|
||||||
'https://blog.alejandrocelaya.com/2019/04/27'
|
|
||||||
. '/considerations-to-properly-use-open-source-software-projects/',
|
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -19,9 +19,7 @@ class ResolveShortUrlActionTest extends ApiTestCase
|
|||||||
$this->assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode());
|
$this->assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode());
|
||||||
$this->assertEquals(self::STATUS_NOT_FOUND, $payload['status']);
|
$this->assertEquals(self::STATUS_NOT_FOUND, $payload['status']);
|
||||||
$this->assertEquals('INVALID_SHORTCODE', $payload['type']);
|
$this->assertEquals('INVALID_SHORTCODE', $payload['type']);
|
||||||
$this->assertEquals('INVALID_SHORTCODE', $payload['error']); // Deprecated
|
|
||||||
$this->assertEquals($expectedDetail, $payload['detail']);
|
$this->assertEquals($expectedDetail, $payload['detail']);
|
||||||
$this->assertEquals($expectedDetail, $payload['message']); // Deprecated
|
|
||||||
$this->assertEquals('Short URL not found', $payload['title']);
|
$this->assertEquals('Short URL not found', $payload['title']);
|
||||||
$this->assertEquals('invalid', $payload['shortCode']);
|
$this->assertEquals('invalid', $payload['shortCode']);
|
||||||
}
|
}
|
||||||
|
@@ -23,9 +23,7 @@ class UpdateTagActionTest extends ApiTestCase
|
|||||||
$this->assertEquals(self::STATUS_BAD_REQUEST, $resp->getStatusCode());
|
$this->assertEquals(self::STATUS_BAD_REQUEST, $resp->getStatusCode());
|
||||||
$this->assertEquals(self::STATUS_BAD_REQUEST, $payload['status']);
|
$this->assertEquals(self::STATUS_BAD_REQUEST, $payload['status']);
|
||||||
$this->assertEquals('INVALID_ARGUMENT', $payload['type']);
|
$this->assertEquals('INVALID_ARGUMENT', $payload['type']);
|
||||||
$this->assertEquals('INVALID_ARGUMENT', $payload['error']); // Deprecated
|
|
||||||
$this->assertEquals($expectedDetail, $payload['detail']);
|
$this->assertEquals($expectedDetail, $payload['detail']);
|
||||||
$this->assertEquals($expectedDetail, $payload['message']); // Deprecated
|
|
||||||
$this->assertEquals('Invalid data', $payload['title']);
|
$this->assertEquals('Invalid data', $payload['title']);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,9 +48,7 @@ class UpdateTagActionTest extends ApiTestCase
|
|||||||
$this->assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode());
|
$this->assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode());
|
||||||
$this->assertEquals(self::STATUS_NOT_FOUND, $payload['status']);
|
$this->assertEquals(self::STATUS_NOT_FOUND, $payload['status']);
|
||||||
$this->assertEquals('TAG_NOT_FOUND', $payload['type']);
|
$this->assertEquals('TAG_NOT_FOUND', $payload['type']);
|
||||||
$this->assertEquals('TAG_NOT_FOUND', $payload['error']); // Deprecated
|
|
||||||
$this->assertEquals($expectedDetail, $payload['detail']);
|
$this->assertEquals($expectedDetail, $payload['detail']);
|
||||||
$this->assertEquals($expectedDetail, $payload['message']); // Deprecated
|
|
||||||
$this->assertEquals('Tag not found', $payload['title']);
|
$this->assertEquals('Tag not found', $payload['title']);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,9 +66,7 @@ class UpdateTagActionTest extends ApiTestCase
|
|||||||
$this->assertEquals(self::STATUS_CONFLICT, $resp->getStatusCode());
|
$this->assertEquals(self::STATUS_CONFLICT, $resp->getStatusCode());
|
||||||
$this->assertEquals(self::STATUS_CONFLICT, $payload['status']);
|
$this->assertEquals(self::STATUS_CONFLICT, $payload['status']);
|
||||||
$this->assertEquals('TAG_CONFLICT', $payload['type']);
|
$this->assertEquals('TAG_CONFLICT', $payload['type']);
|
||||||
$this->assertEquals('TAG_CONFLICT', $payload['error']); // Deprecated
|
|
||||||
$this->assertEquals($expectedDetail, $payload['detail']);
|
$this->assertEquals($expectedDetail, $payload['detail']);
|
||||||
$this->assertEquals($expectedDetail, $payload['message']); // Deprecated
|
|
||||||
$this->assertEquals('Tag conflict', $payload['title']);
|
$this->assertEquals('Tag conflict', $payload['title']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -21,15 +21,13 @@ class AuthenticationTest extends ApiTestCase
|
|||||||
implode('", "', RequestToHttpAuthPlugin::SUPPORTED_AUTH_HEADERS)
|
implode('", "', RequestToHttpAuthPlugin::SUPPORTED_AUTH_HEADERS)
|
||||||
);
|
);
|
||||||
|
|
||||||
$resp = $this->callApi(self::METHOD_GET, '/short-codes');
|
$resp = $this->callApi(self::METHOD_GET, '/short-urls');
|
||||||
$payload = $this->getJsonResponsePayload($resp);
|
$payload = $this->getJsonResponsePayload($resp);
|
||||||
|
|
||||||
$this->assertEquals(self::STATUS_UNAUTHORIZED, $resp->getStatusCode());
|
$this->assertEquals(self::STATUS_UNAUTHORIZED, $resp->getStatusCode());
|
||||||
$this->assertEquals(self::STATUS_UNAUTHORIZED, $payload['status']);
|
$this->assertEquals(self::STATUS_UNAUTHORIZED, $payload['status']);
|
||||||
$this->assertEquals('INVALID_AUTHORIZATION', $payload['type']);
|
$this->assertEquals('INVALID_AUTHORIZATION', $payload['type']);
|
||||||
$this->assertEquals('INVALID_AUTHORIZATION', $payload['error']); // Deprecated
|
|
||||||
$this->assertEquals($expectedDetail, $payload['detail']);
|
$this->assertEquals($expectedDetail, $payload['detail']);
|
||||||
$this->assertEquals($expectedDetail, $payload['message']); // Deprecated
|
|
||||||
$this->assertEquals('Invalid authorization', $payload['title']);
|
$this->assertEquals('Invalid authorization', $payload['title']);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,7 +39,7 @@ class AuthenticationTest extends ApiTestCase
|
|||||||
{
|
{
|
||||||
$expectedDetail = 'Provided API key does not exist or is invalid.';
|
$expectedDetail = 'Provided API key does not exist or is invalid.';
|
||||||
|
|
||||||
$resp = $this->callApi(self::METHOD_GET, '/short-codes', [
|
$resp = $this->callApi(self::METHOD_GET, '/short-urls', [
|
||||||
'headers' => [
|
'headers' => [
|
||||||
Plugin\ApiKeyHeaderPlugin::HEADER_NAME => $apiKey,
|
Plugin\ApiKeyHeaderPlugin::HEADER_NAME => $apiKey,
|
||||||
],
|
],
|
||||||
@@ -51,9 +49,7 @@ class AuthenticationTest extends ApiTestCase
|
|||||||
$this->assertEquals(self::STATUS_UNAUTHORIZED, $resp->getStatusCode());
|
$this->assertEquals(self::STATUS_UNAUTHORIZED, $resp->getStatusCode());
|
||||||
$this->assertEquals(self::STATUS_UNAUTHORIZED, $payload['status']);
|
$this->assertEquals(self::STATUS_UNAUTHORIZED, $payload['status']);
|
||||||
$this->assertEquals('INVALID_API_KEY', $payload['type']);
|
$this->assertEquals('INVALID_API_KEY', $payload['type']);
|
||||||
$this->assertEquals('INVALID_API_KEY', $payload['error']); // Deprecated
|
|
||||||
$this->assertEquals($expectedDetail, $payload['detail']);
|
$this->assertEquals($expectedDetail, $payload['detail']);
|
||||||
$this->assertEquals($expectedDetail, $payload['message']); // Deprecated
|
|
||||||
$this->assertEquals('Invalid API key', $payload['title']);
|
$this->assertEquals('Invalid API key', $payload['title']);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +70,7 @@ class AuthenticationTest extends ApiTestCase
|
|||||||
string $expectedType,
|
string $expectedType,
|
||||||
string $expectedTitle
|
string $expectedTitle
|
||||||
): void {
|
): void {
|
||||||
$resp = $this->callApi(self::METHOD_GET, '/short-codes', [
|
$resp = $this->callApi(self::METHOD_GET, '/short-urls', [
|
||||||
'headers' => [
|
'headers' => [
|
||||||
Plugin\AuthorizationHeaderPlugin::HEADER_NAME => $authValue,
|
Plugin\AuthorizationHeaderPlugin::HEADER_NAME => $authValue,
|
||||||
],
|
],
|
||||||
@@ -84,9 +80,7 @@ class AuthenticationTest extends ApiTestCase
|
|||||||
$this->assertEquals(self::STATUS_UNAUTHORIZED, $resp->getStatusCode());
|
$this->assertEquals(self::STATUS_UNAUTHORIZED, $resp->getStatusCode());
|
||||||
$this->assertEquals(self::STATUS_UNAUTHORIZED, $payload['status']);
|
$this->assertEquals(self::STATUS_UNAUTHORIZED, $payload['status']);
|
||||||
$this->assertEquals($expectedType, $payload['type']);
|
$this->assertEquals($expectedType, $payload['type']);
|
||||||
$this->assertEquals($expectedType, $payload['error']); // Deprecated
|
|
||||||
$this->assertEquals($expectedDetail, $payload['detail']);
|
$this->assertEquals($expectedDetail, $payload['detail']);
|
||||||
$this->assertEquals($expectedDetail, $payload['message']); // Deprecated
|
|
||||||
$this->assertEquals($expectedTitle, $payload['title']);
|
$this->assertEquals($expectedTitle, $payload['title']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,72 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace ShlinkioTest\Shlink\Rest\Action;
|
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
|
||||||
use Prophecy\Argument;
|
|
||||||
use Prophecy\Prophecy\ObjectProphecy;
|
|
||||||
use Shlinkio\Shlink\Rest\Action\AuthenticateAction;
|
|
||||||
use Shlinkio\Shlink\Rest\Authentication\JWTService;
|
|
||||||
use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
|
||||||
use Shlinkio\Shlink\Rest\Service\ApiKeyService;
|
|
||||||
use Zend\Diactoros\ServerRequest;
|
|
||||||
|
|
||||||
use function strpos;
|
|
||||||
|
|
||||||
/** @deprecated */
|
|
||||||
class AuthenticateActionTest extends TestCase
|
|
||||||
{
|
|
||||||
/** @var AuthenticateAction */
|
|
||||||
private $action;
|
|
||||||
/** @var ObjectProphecy */
|
|
||||||
private $apiKeyService;
|
|
||||||
/** @var ObjectProphecy */
|
|
||||||
private $jwtService;
|
|
||||||
|
|
||||||
public function setUp(): void
|
|
||||||
{
|
|
||||||
$this->apiKeyService = $this->prophesize(ApiKeyService::class);
|
|
||||||
$this->jwtService = $this->prophesize(JWTService::class);
|
|
||||||
$this->jwtService->create(Argument::cetera())->willReturn('');
|
|
||||||
|
|
||||||
$this->action = new AuthenticateAction($this->apiKeyService->reveal(), $this->jwtService->reveal());
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function notProvidingAuthDataReturnsError()
|
|
||||||
{
|
|
||||||
$resp = $this->action->handle(new ServerRequest());
|
|
||||||
$this->assertEquals(400, $resp->getStatusCode());
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function properApiKeyReturnsTokenInResponse()
|
|
||||||
{
|
|
||||||
$this->apiKeyService->getByKey('foo')->willReturn((new ApiKey())->setId('5'))
|
|
||||||
->shouldBeCalledOnce();
|
|
||||||
|
|
||||||
$request = (new ServerRequest())->withParsedBody([
|
|
||||||
'apiKey' => 'foo',
|
|
||||||
]);
|
|
||||||
$response = $this->action->handle($request);
|
|
||||||
$this->assertEquals(200, $response->getStatusCode());
|
|
||||||
|
|
||||||
$response->getBody()->rewind();
|
|
||||||
$this->assertTrue(strpos($response->getBody()->getContents(), '"token"') > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function invalidApiKeyReturnsErrorResponse()
|
|
||||||
{
|
|
||||||
$this->apiKeyService->getByKey('foo')->willReturn((new ApiKey())->disable())
|
|
||||||
->shouldBeCalledOnce();
|
|
||||||
|
|
||||||
$request = (new ServerRequest())->withParsedBody([
|
|
||||||
'apiKey' => 'foo',
|
|
||||||
]);
|
|
||||||
$response = $this->action->handle($request);
|
|
||||||
$this->assertEquals(401, $response->getStatusCode());
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,86 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace ShlinkioTest\Shlink\Rest\Authentication;
|
|
||||||
|
|
||||||
use Firebase\JWT\JWT;
|
|
||||||
use PHPUnit\Framework\TestCase;
|
|
||||||
use Shlinkio\Shlink\Core\Options\AppOptions;
|
|
||||||
use Shlinkio\Shlink\Rest\Authentication\JWTService;
|
|
||||||
use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
|
||||||
use Shlinkio\Shlink\Rest\Exception\AuthenticationException;
|
|
||||||
|
|
||||||
use function time;
|
|
||||||
|
|
||||||
/** @deprecated */
|
|
||||||
class JWTServiceTest extends TestCase
|
|
||||||
{
|
|
||||||
/** @var JWTService */
|
|
||||||
private $service;
|
|
||||||
|
|
||||||
public function setUp(): void
|
|
||||||
{
|
|
||||||
$this->service = new JWTService(new AppOptions([
|
|
||||||
'name' => 'ShlinkTest',
|
|
||||||
'version' => '10000.3.1',
|
|
||||||
'secret_key' => 'foo',
|
|
||||||
]));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function tokenIsProperlyCreated()
|
|
||||||
{
|
|
||||||
$id = '34';
|
|
||||||
$token = $this->service->create((new ApiKey())->setId($id));
|
|
||||||
$payload = (array) JWT::decode($token, 'foo', [JWTService::DEFAULT_ENCRYPTION_ALG]);
|
|
||||||
$this->assertGreaterThanOrEqual($payload['iat'], time());
|
|
||||||
$this->assertGreaterThan(time(), $payload['exp']);
|
|
||||||
$this->assertEquals($id, $payload['key']);
|
|
||||||
$this->assertEquals('auth', $payload['sub']);
|
|
||||||
$this->assertEquals('ShlinkTest:v10000.3.1', $payload['iss']);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function refreshIncreasesExpiration()
|
|
||||||
{
|
|
||||||
$originalLifetime = 10;
|
|
||||||
$newLifetime = 30;
|
|
||||||
$originalPayload = ['exp' => time() + $originalLifetime];
|
|
||||||
$token = JWT::encode($originalPayload, 'foo');
|
|
||||||
$newToken = $this->service->refresh($token, $newLifetime);
|
|
||||||
$newPayload = (array) JWT::decode($newToken, 'foo', [JWTService::DEFAULT_ENCRYPTION_ALG]);
|
|
||||||
|
|
||||||
$this->assertGreaterThan($originalPayload['exp'], $newPayload['exp']);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function verifyReturnsTrueWhenTheTokenIsCorrect()
|
|
||||||
{
|
|
||||||
$this->assertTrue($this->service->verify(JWT::encode([], 'foo')));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function verifyReturnsFalseWhenTheTokenIsCorrect()
|
|
||||||
{
|
|
||||||
$this->assertFalse($this->service->verify('invalidToken'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function getPayloadWorksWithCorrectTokens()
|
|
||||||
{
|
|
||||||
$originalPayload = [
|
|
||||||
'exp' => time() + 10,
|
|
||||||
'sub' => 'testing',
|
|
||||||
];
|
|
||||||
$token = JWT::encode($originalPayload, 'foo');
|
|
||||||
$this->assertEquals($originalPayload, $this->service->getPayload($token));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function getPayloadThrowsExceptionWithIncorrectTokens()
|
|
||||||
{
|
|
||||||
$this->expectException(AuthenticationException::class);
|
|
||||||
$this->service->getPayload('invalidToken');
|
|
||||||
}
|
|
||||||
}
|
|
@@ -13,7 +13,7 @@ use Psr\Http\Message\ServerRequestInterface;
|
|||||||
use Psr\Http\Server\MiddlewareInterface;
|
use Psr\Http\Server\MiddlewareInterface;
|
||||||
use Psr\Http\Server\RequestHandlerInterface;
|
use Psr\Http\Server\RequestHandlerInterface;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Shlinkio\Shlink\Rest\Action\AuthenticateAction;
|
use Shlinkio\Shlink\Rest\Action\HealthAction;
|
||||||
use Shlinkio\Shlink\Rest\Authentication\Plugin\AuthenticationPluginInterface;
|
use Shlinkio\Shlink\Rest\Authentication\Plugin\AuthenticationPluginInterface;
|
||||||
use Shlinkio\Shlink\Rest\Authentication\RequestToHttpAuthPluginInterface;
|
use Shlinkio\Shlink\Rest\Authentication\RequestToHttpAuthPluginInterface;
|
||||||
use Shlinkio\Shlink\Rest\Middleware\AuthenticationMiddleware;
|
use Shlinkio\Shlink\Rest\Middleware\AuthenticationMiddleware;
|
||||||
@@ -37,7 +37,7 @@ class AuthenticationMiddlewareTest extends TestCase
|
|||||||
|
|
||||||
$this->middleware = new AuthenticationMiddleware(
|
$this->middleware = new AuthenticationMiddleware(
|
||||||
$this->requestToPlugin->reveal(),
|
$this->requestToPlugin->reveal(),
|
||||||
[AuthenticateAction::class],
|
[HealthAction::class],
|
||||||
$this->logger->reveal()
|
$this->logger->reveal()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -72,7 +72,7 @@ class AuthenticationMiddlewareTest extends TestCase
|
|||||||
yield 'with whitelisted route' => [(new ServerRequest())->withAttribute(
|
yield 'with whitelisted route' => [(new ServerRequest())->withAttribute(
|
||||||
RouteResult::class,
|
RouteResult::class,
|
||||||
RouteResult::fromRoute(
|
RouteResult::fromRoute(
|
||||||
new Route('foo', $dummyMiddleware, Route::HTTP_METHOD_ANY, AuthenticateAction::class)
|
new Route('foo', $dummyMiddleware, Route::HTTP_METHOD_ANY, HealthAction::class)
|
||||||
)
|
)
|
||||||
)];
|
)];
|
||||||
yield 'with OPTIONS method' => [(new ServerRequest())->withAttribute(
|
yield 'with OPTIONS method' => [(new ServerRequest())->withAttribute(
|
||||||
|
@@ -1,94 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace ShlinkioTest\Shlink\Rest\Middleware;
|
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
|
||||||
use Prophecy\Prophecy\ObjectProphecy;
|
|
||||||
use Psr\Http\Server\RequestHandlerInterface;
|
|
||||||
use Shlinkio\Shlink\Rest\Middleware\BackwardsCompatibleProblemDetailsMiddleware;
|
|
||||||
use Zend\Diactoros\Response;
|
|
||||||
use Zend\Diactoros\ServerRequest;
|
|
||||||
use Zend\Diactoros\ServerRequestFactory;
|
|
||||||
use Zend\Diactoros\Uri;
|
|
||||||
|
|
||||||
class BackwardsCompatibleProblemDetailsMiddlewareTest extends TestCase
|
|
||||||
{
|
|
||||||
private BackwardsCompatibleProblemDetailsMiddleware $middleware;
|
|
||||||
private ObjectProphecy $handler;
|
|
||||||
|
|
||||||
public function setUp(): void
|
|
||||||
{
|
|
||||||
$this->handler = $this->prophesize(RequestHandlerInterface::class);
|
|
||||||
$this->middleware = new BackwardsCompatibleProblemDetailsMiddleware(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @test
|
|
||||||
* @dataProvider provideNonProcessableResponses
|
|
||||||
*/
|
|
||||||
public function nonProblemDetailsOrInvalidResponsesAreReturnedAsTheyAre(
|
|
||||||
Response $response,
|
|
||||||
?ServerRequest $request = null
|
|
||||||
): void {
|
|
||||||
$request = $request ?? ServerRequestFactory::fromGlobals();
|
|
||||||
$handle = $this->handler->handle($request)->willReturn($response);
|
|
||||||
|
|
||||||
$result = $this->middleware->process($request, $this->handler->reveal());
|
|
||||||
|
|
||||||
$this->assertSame($response, $result);
|
|
||||||
$handle->shouldHaveBeenCalledOnce();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function provideNonProcessableResponses(): iterable
|
|
||||||
{
|
|
||||||
yield 'no problem details' => [new Response()];
|
|
||||||
yield 'invalid JSON' => [(new Response('data://text/plain,{invalid-json'))->withHeader(
|
|
||||||
'Content-Type',
|
|
||||||
'application/problem+json'
|
|
||||||
)];
|
|
||||||
yield 'version 2' => [
|
|
||||||
(new Response())->withHeader('Content-type', 'application/problem+json'),
|
|
||||||
ServerRequestFactory::fromGlobals()->withUri(new Uri('/v2/something')),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @test
|
|
||||||
* @dataProvider provideRequestPath
|
|
||||||
*/
|
|
||||||
public function mapsDeprecatedPropertiesWhenRequestIsPerformedToVersionOne(string $requestPath): void
|
|
||||||
{
|
|
||||||
$request = ServerRequestFactory::fromGlobals()->withUri(new Uri($requestPath));
|
|
||||||
$response = $this->jsonResponse([
|
|
||||||
'type' => 'the_type',
|
|
||||||
'detail' => 'the_detail',
|
|
||||||
]);
|
|
||||||
$handle = $this->handler->handle($request)->willReturn($response);
|
|
||||||
|
|
||||||
/** @var Response\JsonResponse $result */
|
|
||||||
$result = $this->middleware->process($request, $this->handler->reveal());
|
|
||||||
$payload = $result->getPayload();
|
|
||||||
|
|
||||||
$this->assertEquals([
|
|
||||||
'type' => 'the_type',
|
|
||||||
'detail' => 'the_detail',
|
|
||||||
'error' => 'the_type',
|
|
||||||
'message' => 'the_detail',
|
|
||||||
], $payload);
|
|
||||||
$handle->shouldHaveBeenCalledOnce();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function provideRequestPath(): iterable
|
|
||||||
{
|
|
||||||
yield 'no version' => ['/foo'];
|
|
||||||
yield 'version one' => ['/v1/foo'];
|
|
||||||
}
|
|
||||||
|
|
||||||
private function jsonResponse(array $payload, int $status = 200): Response\JsonResponse
|
|
||||||
{
|
|
||||||
$headers = ['Content-Type' => 'application/problem+json'];
|
|
||||||
return new Response\JsonResponse($payload, $status, $headers);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,49 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace ShlinkioTest\Shlink\Rest\Middleware\ShortUrl;
|
|
||||||
|
|
||||||
use PHPUnit\Framework\Assert;
|
|
||||||
use PHPUnit\Framework\TestCase;
|
|
||||||
use Prophecy\Argument;
|
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
|
||||||
use Psr\Http\Message\UriInterface;
|
|
||||||
use Psr\Http\Server\RequestHandlerInterface;
|
|
||||||
use Shlinkio\Shlink\Rest\Middleware\ShortUrl\ShortCodePathMiddleware;
|
|
||||||
use Zend\Diactoros\Response;
|
|
||||||
use Zend\Diactoros\Uri;
|
|
||||||
|
|
||||||
class ShortCodePathMiddlewareTest extends TestCase
|
|
||||||
{
|
|
||||||
private $middleware;
|
|
||||||
private $requestHandler;
|
|
||||||
|
|
||||||
public function setUp(): void
|
|
||||||
{
|
|
||||||
$this->middleware = new ShortCodePathMiddleware();
|
|
||||||
$this->requestHandler = $this->prophesize(RequestHandlerInterface::class);
|
|
||||||
$this->requestHandler->handle(Argument::type(ServerRequestInterface::class))->willReturn(new Response());
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function properlyReplacesTheOldPathByTheNewOne()
|
|
||||||
{
|
|
||||||
$uri = new Uri('/short-codes/foo');
|
|
||||||
|
|
||||||
$request = $this->prophesize(ServerRequestInterface::class);
|
|
||||||
$request->getUri()->willReturn($uri);
|
|
||||||
$withUri = $request->withUri(Argument::that(function (UriInterface $uri) {
|
|
||||||
$path = $uri->getPath();
|
|
||||||
|
|
||||||
Assert::assertStringContainsString('/short-urls', $path);
|
|
||||||
Assert::assertStringNotContainsString('/short-codes', $path);
|
|
||||||
|
|
||||||
return $uri;
|
|
||||||
}))->willReturn($request->reveal());
|
|
||||||
|
|
||||||
$this->middleware->process($request->reveal(), $this->requestHandler->reveal());
|
|
||||||
|
|
||||||
$withUri->shouldHaveBeenCalledOnce();
|
|
||||||
}
|
|
||||||
}
|
|
Reference in New Issue
Block a user