mirror of
https://github.com/shlinkio/shlink.git
synced 2025-03-14 04:00:57 +03:00
Removed several deprecated components
This commit is contained in:
parent
78b484e657
commit
434b56fa8c
41 changed files with 16 additions and 952 deletions
|
@ -2,14 +2,11 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
use function Shlinkio\Shlink\Common\env;
|
||||
|
||||
return [
|
||||
|
||||
'app_options' => [
|
||||
'name' => 'Shlink',
|
||||
'version' => '%SHLINK_VERSION%',
|
||||
'secret_key' => env('SECRET_KEY', ''),
|
||||
'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' => [
|
||||
'listeners' => [Logger\ErrorLogger::class],
|
||||
],
|
||||
|
|
|
@ -21,7 +21,6 @@ return [
|
|||
'path' => '/rest',
|
||||
'middleware' => [
|
||||
Rest\Middleware\CrossDomainMiddleware::class,
|
||||
Rest\Middleware\BackwardsCompatibleProblemDetailsMiddleware::class,
|
||||
ProblemDetails\ProblemDetailsMiddleware::class,
|
||||
],
|
||||
],
|
||||
|
@ -35,7 +34,6 @@ return [
|
|||
'path' => '/rest',
|
||||
'middleware' => [
|
||||
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.
|
||||
|
||||
* `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)).
|
||||
|
||||
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",
|
||||
"host": "something.rds.amazonaws.com",
|
||||
"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).
|
||||
|
||||
> 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:
|
||||
|
||||
```bash
|
||||
|
|
|
@ -8,19 +8,10 @@ use Monolog\Handler\StreamHandler;
|
|||
use Monolog\Logger;
|
||||
|
||||
use function explode;
|
||||
use function file_exists;
|
||||
use function file_get_contents;
|
||||
use function file_put_contents;
|
||||
use function Functional\contains;
|
||||
use function implode;
|
||||
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 {
|
||||
private const BASE62 = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
private const DB_DRIVERS_MAP = [
|
||||
'mysql' => 'pdo_mysql',
|
||||
'maria' => 'pdo_mysql',
|
||||
|
@ -32,40 +23,6 @@ $helper = new class {
|
|||
'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
|
||||
{
|
||||
$driver = env('DB_DRIVER');
|
||||
|
@ -94,7 +51,7 @@ $helper = new class {
|
|||
public function getNotFoundRedirectsConfig(): array
|
||||
{
|
||||
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'),
|
||||
'base_url' => env('BASE_URL_REDIRECT_TO'),
|
||||
];
|
||||
|
@ -112,7 +69,6 @@ return [
|
|||
'config_cache_enabled' => false,
|
||||
|
||||
'app_options' => [
|
||||
'secret_key' => $helper->getSecretKey(),
|
||||
'disable_track_param' => env('DISABLE_TRACK_PARAM'),
|
||||
],
|
||||
|
||||
|
|
|
@ -15,10 +15,6 @@ return [
|
|||
Command\ShortUrl\DeleteShortUrlCommand::NAME => Command\ShortUrl\DeleteShortUrlCommand::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\DisableKeyCommand::NAME => Command\Api\DisableKeyCommand::class,
|
||||
|
|
|
@ -36,10 +36,6 @@ return [
|
|||
Command\ShortUrl\DeleteShortUrlCommand::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\DisableKeyCommand::class => ConfigAbstractFactory::class,
|
||||
|
@ -70,7 +66,6 @@ return [
|
|||
LockFactory::class,
|
||||
GeolocationDbUpdater::class,
|
||||
],
|
||||
Command\Visit\UpdateDbCommand::class => [DbUpdater::class],
|
||||
|
||||
Command\Api\GenerateKeyCommand::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_host' => ['url_shortener', 'domain', 'hostname'],
|
||||
'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'],
|
||||
'regular_404_redirect_to' => ['not_found_redirects', 'regular_404'],
|
||||
'base_url_redirect_to' => ['not_found_redirects', 'base_path'],
|
||||
|
|
|
@ -90,9 +90,6 @@ class Visit extends AbstractEntity implements JsonSerializable
|
|||
'date' => $this->date->toAtomString(),
|
||||
'userAgent' => $this->userAgent,
|
||||
'visitLocation' => $this->visitLocation,
|
||||
|
||||
// Deprecated
|
||||
'remoteAddr' => null,
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -78,7 +78,7 @@ class NotifyVisitToWebHooks
|
|||
'User-Agent' => (string) $this->appOptions,
|
||||
],
|
||||
RequestOptions::JSON => [
|
||||
'shortUrl' => $this->transformer->transform($visit->getShortUrl(), false),
|
||||
'shortUrl' => $this->transformer->transform($visit->getShortUrl()),
|
||||
'visit' => $visit->jsonSerialize(),
|
||||
],
|
||||
];
|
||||
|
|
|
@ -15,8 +15,6 @@ class AppOptions extends AbstractOptions
|
|||
|
||||
private string $name = '';
|
||||
private string $version = '1.0';
|
||||
/** @deprecated */
|
||||
private string $secretKey = '';
|
||||
private ?string $disableTrackParam = null;
|
||||
|
||||
public function getName(): string
|
||||
|
@ -41,23 +39,6 @@ class AppOptions extends AbstractOptions
|
|||
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
|
||||
*/
|
||||
|
|
|
@ -22,11 +22,11 @@ class ShortUrlDataTransformer implements DataTransformerInterface
|
|||
/**
|
||||
* @param ShortUrl $shortUrl
|
||||
*/
|
||||
public function transform($shortUrl, bool $includeDeprecated = true): array
|
||||
public function transform($shortUrl): array
|
||||
{
|
||||
$longUrl = $shortUrl->getLongUrl();
|
||||
|
||||
$rawData = [
|
||||
return [
|
||||
'shortCode' => $shortUrl->getShortCode(),
|
||||
'shortUrl' => $shortUrl->toString($this->domainConfig),
|
||||
'longUrl' => $longUrl,
|
||||
|
@ -35,12 +35,6 @@ class ShortUrlDataTransformer implements DataTransformerInterface
|
|||
'tags' => invoke($shortUrl->getTags(), '__toString'),
|
||||
'meta' => $this->buildMeta($shortUrl),
|
||||
];
|
||||
|
||||
if ($includeDeprecated) {
|
||||
$rawData['originalUrl'] = $longUrl;
|
||||
}
|
||||
|
||||
return $rawData;
|
||||
}
|
||||
|
||||
private function buildMeta(ShortUrl $shortUrl): array
|
||||
|
|
|
@ -11,7 +11,7 @@ use function array_merge;
|
|||
|
||||
class SimplifiedConfigParserTest extends TestCase
|
||||
{
|
||||
private $postProcessor;
|
||||
private SimplifiedConfigParser $postProcessor;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
|
@ -40,7 +40,7 @@ class SimplifiedConfigParserTest extends TestCase
|
|||
'short_domain_host' => 'doma.in',
|
||||
'validate_url' => false,
|
||||
'delete_short_url_threshold' => 50,
|
||||
'not_found_redirect_to' => 'foobar.com',
|
||||
'invalid_short_url_redirect_to' => 'foobar.com',
|
||||
'redis_servers' => [
|
||||
'tcp://1.1.1.1:1111',
|
||||
'tcp://1.2.2.2:2222',
|
||||
|
@ -125,28 +125,4 @@ class SimplifiedConfigParserTest extends TestCase
|
|||
|
||||
$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(),
|
||||
'userAgent' => 'Chrome',
|
||||
'visitLocation' => null,
|
||||
|
||||
// Deprecated
|
||||
'remoteAddr' => null,
|
||||
], $visit->jsonSerialize());
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,6 @@ return [
|
|||
|
||||
'auth' => [
|
||||
'routes_whitelist' => [
|
||||
Action\AuthenticateAction::class,
|
||||
Action\HealthAction::class,
|
||||
Action\ShortUrl\SingleStepCreateShortUrlAction::class,
|
||||
],
|
||||
|
|
|
@ -20,7 +20,6 @@ return [
|
|||
Authentication\JWTService::class => ConfigAbstractFactory::class,
|
||||
ApiKeyService::class => ConfigAbstractFactory::class,
|
||||
|
||||
Action\AuthenticateAction::class => ConfigAbstractFactory::class,
|
||||
Action\HealthAction::class => ConfigAbstractFactory::class,
|
||||
Action\ShortUrl\CreateShortUrlAction::class => ConfigAbstractFactory::class,
|
||||
Action\ShortUrl\SingleStepCreateShortUrlAction::class => ConfigAbstractFactory::class,
|
||||
|
@ -39,9 +38,7 @@ return [
|
|||
Middleware\BodyParserMiddleware::class => InvokableFactory::class,
|
||||
Middleware\CrossDomainMiddleware::class => InvokableFactory::class,
|
||||
Middleware\PathVersionMiddleware::class => InvokableFactory::class,
|
||||
Middleware\BackwardsCompatibleProblemDetailsMiddleware::class => ConfigAbstractFactory::class,
|
||||
Middleware\ShortUrl\CreateShortUrlContentNegotiationMiddleware::class => InvokableFactory::class,
|
||||
Middleware\ShortUrl\ShortCodePathMiddleware::class => InvokableFactory::class,
|
||||
],
|
||||
],
|
||||
|
||||
|
@ -49,7 +46,6 @@ return [
|
|||
Authentication\JWTService::class => [AppOptions::class],
|
||||
ApiKeyService::class => ['em'],
|
||||
|
||||
Action\AuthenticateAction::class => [ApiKeyService::class, Authentication\JWTService::class, 'Logger_Shlink'],
|
||||
Action\HealthAction::class => [Connection::class, AppOptions::class, 'Logger_Shlink'],
|
||||
Action\ShortUrl\CreateShortUrlAction::class => [
|
||||
Service\UrlShortener::class,
|
||||
|
@ -76,10 +72,6 @@ return [
|
|||
Action\Tag\DeleteTagsAction::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],
|
||||
|
||||
Middleware\BackwardsCompatibleProblemDetailsMiddleware::class => [
|
||||
'config.backwards_compatible_problem_details.json_flags',
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
|
|
|
@ -7,7 +7,6 @@ namespace Shlinkio\Shlink\Rest;
|
|||
return [
|
||||
|
||||
'routes' => [
|
||||
Action\AuthenticateAction::getRouteDef(),
|
||||
Action\HealthAction::getRouteDef(),
|
||||
|
||||
// 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
|
||||
{
|
||||
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
|
||||
{
|
||||
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, $payload['status']);
|
||||
$this->assertEquals($detail, $payload['detail']);
|
||||
$this->assertEquals($detail, $payload['message']); // Deprecated
|
||||
$this->assertEquals('INVALID_SLUG', $payload['type']);
|
||||
$this->assertEquals('INVALID_SLUG', $payload['error']); // Deprecated
|
||||
$this->assertEquals('Invalid custom slug', $payload['title']);
|
||||
$this->assertEquals($slug, $payload['customSlug']);
|
||||
|
||||
|
@ -215,7 +213,7 @@ class CreateShortUrlActionTest extends ApiTestCase
|
|||
}
|
||||
|
||||
/** @test */
|
||||
public function failsToCreateShortUrlWithInvalidOriginalUrl(): void
|
||||
public function failsToCreateShortUrlWithInvalidLongUrl(): void
|
||||
{
|
||||
$url = 'https://this-has-to-be-invalid.com';
|
||||
$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, $payload['status']);
|
||||
$this->assertEquals('INVALID_URL', $payload['type']);
|
||||
$this->assertEquals('INVALID_URL', $payload['error']); // Deprecated
|
||||
$this->assertEquals($expectedDetail, $payload['detail']);
|
||||
$this->assertEquals($expectedDetail, $payload['message']); // Deprecated
|
||||
$this->assertEquals('Invalid URL', $payload['title']);
|
||||
$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, $payload['status']);
|
||||
$this->assertEquals('INVALID_SHORTCODE', $payload['type']);
|
||||
$this->assertEquals('INVALID_SHORTCODE', $payload['error']); // Deprecated
|
||||
$this->assertEquals($expectedDetail, $payload['detail']);
|
||||
$this->assertEquals($expectedDetail, $payload['message']); // Deprecated
|
||||
$this->assertEquals('Short URL not found', $payload['title']);
|
||||
$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, $payload['status']);
|
||||
$this->assertEquals('INVALID_SHORTCODE_DELETION', $payload['type']);
|
||||
$this->assertEquals('INVALID_SHORTCODE_DELETION', $payload['error']); // Deprecated
|
||||
$this->assertEquals($expectedDetail, $payload['detail']);
|
||||
$this->assertEquals($expectedDetail, $payload['message']); // Deprecated
|
||||
$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, $payload['status']);
|
||||
$this->assertEquals('INVALID_SHORTCODE', $payload['type']);
|
||||
$this->assertEquals('INVALID_SHORTCODE', $payload['error']); // Deprecated
|
||||
$this->assertEquals($expectedDetail, $payload['detail']);
|
||||
$this->assertEquals($expectedDetail, $payload['message']); // Deprecated
|
||||
$this->assertEquals('Short URL not found', $payload['title']);
|
||||
$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, $payload['status']);
|
||||
$this->assertEquals('INVALID_ARGUMENT', $payload['type']);
|
||||
$this->assertEquals('INVALID_ARGUMENT', $payload['error']); // Deprecated
|
||||
$this->assertEquals($expectedDetail, $payload['detail']);
|
||||
$this->assertEquals($expectedDetail, $payload['message']); // Deprecated
|
||||
$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, $payload['status']);
|
||||
$this->assertEquals('INVALID_ARGUMENT', $payload['type']);
|
||||
$this->assertEquals('INVALID_ARGUMENT', $payload['error']); // Deprecated
|
||||
$this->assertEquals($expectedDetail, $payload['detail']);
|
||||
$this->assertEquals($expectedDetail, $payload['message']); // Deprecated
|
||||
$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, $payload['status']);
|
||||
$this->assertEquals('INVALID_SHORTCODE', $payload['type']);
|
||||
$this->assertEquals('INVALID_SHORTCODE', $payload['error']); // Deprecated
|
||||
$this->assertEquals($expectedDetail, $payload['detail']);
|
||||
$this->assertEquals($expectedDetail, $payload['message']); // Deprecated
|
||||
$this->assertEquals('Short URL not found', $payload['title']);
|
||||
$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, $payload['status']);
|
||||
$this->assertEquals('INVALID_SHORTCODE', $payload['type']);
|
||||
$this->assertEquals('INVALID_SHORTCODE', $payload['error']); // Deprecated
|
||||
$this->assertEquals($expectedDetail, $payload['detail']);
|
||||
$this->assertEquals($expectedDetail, $payload['message']); // Deprecated
|
||||
$this->assertEquals('Short URL not found', $payload['title']);
|
||||
$this->assertEquals('invalid', $payload['shortCode']);
|
||||
}
|
||||
|
|
|
@ -24,7 +24,6 @@ class ListShortUrlsTest extends ApiTestCase
|
|||
'validUntil' => null,
|
||||
'maxVisits' => null,
|
||||
],
|
||||
'originalUrl' => 'https://shlink.io',
|
||||
];
|
||||
private const SHORT_URL_CUSTOM_SLUG_AND_DOMAIN = [
|
||||
'shortCode' => 'custom-with-domain',
|
||||
|
@ -38,7 +37,6 @@ class ListShortUrlsTest extends ApiTestCase
|
|||
'validUntil' => null,
|
||||
'maxVisits' => null,
|
||||
],
|
||||
'originalUrl' => 'https://google.com',
|
||||
];
|
||||
private const SHORT_URL_META = [
|
||||
'shortCode' => 'def456',
|
||||
|
@ -54,9 +52,6 @@ class ListShortUrlsTest extends ApiTestCase
|
|||
'validUntil' => 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 = [
|
||||
'shortCode' => 'custom',
|
||||
|
@ -70,7 +65,6 @@ class ListShortUrlsTest extends ApiTestCase
|
|||
'validUntil' => null,
|
||||
'maxVisits' => 2,
|
||||
],
|
||||
'originalUrl' => 'https://shlink.io',
|
||||
];
|
||||
private const SHORT_URL_CUSTOM_DOMAIN = [
|
||||
'shortCode' => 'ghi789',
|
||||
|
@ -86,9 +80,6 @@ class ListShortUrlsTest extends ApiTestCase
|
|||
'validUntil' => 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, $payload['status']);
|
||||
$this->assertEquals('INVALID_SHORTCODE', $payload['type']);
|
||||
$this->assertEquals('INVALID_SHORTCODE', $payload['error']); // Deprecated
|
||||
$this->assertEquals($expectedDetail, $payload['detail']);
|
||||
$this->assertEquals($expectedDetail, $payload['message']); // Deprecated
|
||||
$this->assertEquals('Short URL not found', $payload['title']);
|
||||
$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, $payload['status']);
|
||||
$this->assertEquals('INVALID_ARGUMENT', $payload['type']);
|
||||
$this->assertEquals('INVALID_ARGUMENT', $payload['error']); // Deprecated
|
||||
$this->assertEquals($expectedDetail, $payload['detail']);
|
||||
$this->assertEquals($expectedDetail, $payload['message']); // Deprecated
|
||||
$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, $payload['status']);
|
||||
$this->assertEquals('TAG_NOT_FOUND', $payload['type']);
|
||||
$this->assertEquals('TAG_NOT_FOUND', $payload['error']); // Deprecated
|
||||
$this->assertEquals($expectedDetail, $payload['detail']);
|
||||
$this->assertEquals($expectedDetail, $payload['message']); // Deprecated
|
||||
$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, $payload['status']);
|
||||
$this->assertEquals('TAG_CONFLICT', $payload['type']);
|
||||
$this->assertEquals('TAG_CONFLICT', $payload['error']); // Deprecated
|
||||
$this->assertEquals($expectedDetail, $payload['detail']);
|
||||
$this->assertEquals($expectedDetail, $payload['message']); // Deprecated
|
||||
$this->assertEquals('Tag conflict', $payload['title']);
|
||||
}
|
||||
|
||||
|
|
|
@ -21,15 +21,13 @@ class AuthenticationTest extends ApiTestCase
|
|||
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);
|
||||
|
||||
$this->assertEquals(self::STATUS_UNAUTHORIZED, $resp->getStatusCode());
|
||||
$this->assertEquals(self::STATUS_UNAUTHORIZED, $payload['status']);
|
||||
$this->assertEquals('INVALID_AUTHORIZATION', $payload['type']);
|
||||
$this->assertEquals('INVALID_AUTHORIZATION', $payload['error']); // Deprecated
|
||||
$this->assertEquals($expectedDetail, $payload['detail']);
|
||||
$this->assertEquals($expectedDetail, $payload['message']); // Deprecated
|
||||
$this->assertEquals('Invalid authorization', $payload['title']);
|
||||
}
|
||||
|
||||
|
@ -41,7 +39,7 @@ class AuthenticationTest extends ApiTestCase
|
|||
{
|
||||
$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' => [
|
||||
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, $payload['status']);
|
||||
$this->assertEquals('INVALID_API_KEY', $payload['type']);
|
||||
$this->assertEquals('INVALID_API_KEY', $payload['error']); // Deprecated
|
||||
$this->assertEquals($expectedDetail, $payload['detail']);
|
||||
$this->assertEquals($expectedDetail, $payload['message']); // Deprecated
|
||||
$this->assertEquals('Invalid API key', $payload['title']);
|
||||
}
|
||||
|
||||
|
@ -74,7 +70,7 @@ class AuthenticationTest extends ApiTestCase
|
|||
string $expectedType,
|
||||
string $expectedTitle
|
||||
): void {
|
||||
$resp = $this->callApi(self::METHOD_GET, '/short-codes', [
|
||||
$resp = $this->callApi(self::METHOD_GET, '/short-urls', [
|
||||
'headers' => [
|
||||
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, $payload['status']);
|
||||
$this->assertEquals($expectedType, $payload['type']);
|
||||
$this->assertEquals($expectedType, $payload['error']); // Deprecated
|
||||
$this->assertEquals($expectedDetail, $payload['detail']);
|
||||
$this->assertEquals($expectedDetail, $payload['message']); // Deprecated
|
||||
$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\RequestHandlerInterface;
|
||||
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\RequestToHttpAuthPluginInterface;
|
||||
use Shlinkio\Shlink\Rest\Middleware\AuthenticationMiddleware;
|
||||
|
@ -37,7 +37,7 @@ class AuthenticationMiddlewareTest extends TestCase
|
|||
|
||||
$this->middleware = new AuthenticationMiddleware(
|
||||
$this->requestToPlugin->reveal(),
|
||||
[AuthenticateAction::class],
|
||||
[HealthAction::class],
|
||||
$this->logger->reveal()
|
||||
);
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ class AuthenticationMiddlewareTest extends TestCase
|
|||
yield 'with whitelisted route' => [(new ServerRequest())->withAttribute(
|
||||
RouteResult::class,
|
||||
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(
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue