WIP: Preparations for Symfony 6 upgrade #14

Draft
skobkin wants to merge 4 commits from fix_symfony6_preparations into master
10 changed files with 188 additions and 251 deletions

View file

@ -22,7 +22,7 @@
"composer/package-versions-deprecated": "1.11.99.5", "composer/package-versions-deprecated": "1.11.99.5",
"doctrine/annotations": "^1.0", "doctrine/annotations": "^1.0",
"doctrine/doctrine-bundle": "^2", "doctrine/doctrine-bundle": "^2",
"doctrine/doctrine-migrations-bundle": "^2", "doctrine/doctrine-migrations-bundle": "^3",
"doctrine/orm": "^2", "doctrine/orm": "^2",
"excelwebzone/recaptcha-bundle": "^1.5", "excelwebzone/recaptcha-bundle": "^1.5",
"pagerfanta/doctrine-orm-adapter": "^3.6", "pagerfanta/doctrine-orm-adapter": "^3.6",

173
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "bfa726a8284bf45a7ccbbb6600e02128", "content-hash": "d075bf85a61c962f3316ab9d71fa4650",
"packages": [ "packages": [
{ {
"name": "babdev/pagerfanta-bundle", "name": "babdev/pagerfanta-bundle",
@ -724,35 +724,38 @@
}, },
{ {
"name": "doctrine/dbal", "name": "doctrine/dbal",
"version": "2.13.9", "version": "3.3.7",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/doctrine/dbal.git", "url": "https://github.com/doctrine/dbal.git",
"reference": "c480849ca3ad6706a39c970cdfe6888fa8a058b8" "reference": "9f79d4650430b582f4598fe0954ef4d52fbc0a8a"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/doctrine/dbal/zipball/c480849ca3ad6706a39c970cdfe6888fa8a058b8", "url": "https://api.github.com/repos/doctrine/dbal/zipball/9f79d4650430b582f4598fe0954ef4d52fbc0a8a",
"reference": "c480849ca3ad6706a39c970cdfe6888fa8a058b8", "reference": "9f79d4650430b582f4598fe0954ef4d52fbc0a8a",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"doctrine/cache": "^1.0|^2.0", "composer-runtime-api": "^2",
"doctrine/cache": "^1.11|^2.0",
"doctrine/deprecations": "^0.5.3|^1", "doctrine/deprecations": "^0.5.3|^1",
"doctrine/event-manager": "^1.0", "doctrine/event-manager": "^1.0",
"ext-pdo": "*", "php": "^7.3 || ^8.0",
"php": "^7.1 || ^8" "psr/cache": "^1|^2|^3",
"psr/log": "^1|^2|^3"
}, },
"require-dev": { "require-dev": {
"doctrine/coding-standard": "9.0.0", "doctrine/coding-standard": "9.0.0",
"jetbrains/phpstorm-stubs": "2021.1", "jetbrains/phpstorm-stubs": "2022.1",
"phpstan/phpstan": "1.4.6", "phpstan/phpstan": "1.7.13",
"phpunit/phpunit": "^7.5.20|^8.5|9.5.16", "phpstan/phpstan-strict-rules": "^1.2",
"phpunit/phpunit": "9.5.20",
"psalm/plugin-phpunit": "0.16.1", "psalm/plugin-phpunit": "0.16.1",
"squizlabs/php_codesniffer": "3.6.2", "squizlabs/php_codesniffer": "3.7.0",
"symfony/cache": "^4.4", "symfony/cache": "^5.2|^6.0",
"symfony/console": "^2.0.5|^3.0|^4.0|^5.0", "symfony/console": "^2.7|^3.0|^4.0|^5.0|^6.0",
"vimeo/psalm": "4.22.0" "vimeo/psalm": "4.23.0"
}, },
"suggest": { "suggest": {
"symfony/console": "For helpful console commands such as SQL execution and import of files." "symfony/console": "For helpful console commands such as SQL execution and import of files."
@ -763,7 +766,7 @@
"type": "library", "type": "library",
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"Doctrine\\DBAL\\": "lib/Doctrine/DBAL" "Doctrine\\DBAL\\": "src"
} }
}, },
"notification-url": "https://packagist.org/downloads/", "notification-url": "https://packagist.org/downloads/",
@ -806,14 +809,13 @@
"queryobject", "queryobject",
"sasql", "sasql",
"sql", "sql",
"sqlanywhere",
"sqlite", "sqlite",
"sqlserver", "sqlserver",
"sqlsrv" "sqlsrv"
], ],
"support": { "support": {
"issues": "https://github.com/doctrine/dbal/issues", "issues": "https://github.com/doctrine/dbal/issues",
"source": "https://github.com/doctrine/dbal/tree/2.13.9" "source": "https://github.com/doctrine/dbal/tree/3.3.7"
}, },
"funding": [ "funding": [
{ {
@ -829,7 +831,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-05-02T20:28:55+00:00" "time": "2022-06-13T21:43:03+00:00"
}, },
{ {
"name": "doctrine/deprecations", "name": "doctrine/deprecations",
@ -990,30 +992,34 @@
}, },
{ {
"name": "doctrine/doctrine-migrations-bundle", "name": "doctrine/doctrine-migrations-bundle",
"version": "2.2.3", "version": "3.2.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/doctrine/DoctrineMigrationsBundle.git", "url": "https://github.com/doctrine/DoctrineMigrationsBundle.git",
"reference": "0a081b55a88259a887af7be654743a8c5f703e99" "reference": "3393f411ba25ade21969c33f2053220044854d01"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/doctrine/DoctrineMigrationsBundle/zipball/0a081b55a88259a887af7be654743a8c5f703e99", "url": "https://api.github.com/repos/doctrine/DoctrineMigrationsBundle/zipball/3393f411ba25ade21969c33f2053220044854d01",
"reference": "0a081b55a88259a887af7be654743a8c5f703e99", "reference": "3393f411ba25ade21969c33f2053220044854d01",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"doctrine/doctrine-bundle": "~1.0|~2.0", "doctrine/doctrine-bundle": "~1.0|~2.0",
"doctrine/migrations": "^2.2", "doctrine/migrations": "^3.2",
"php": "^7.1|^8.0", "php": "^7.2|^8.0",
"symfony/framework-bundle": "~3.4|~4.0|~5.0" "symfony/framework-bundle": "~3.4|~4.0|~5.0|~6.0"
}, },
"require-dev": { "require-dev": {
"doctrine/coding-standard": "^8.0", "doctrine/coding-standard": "^8.0",
"mikey179/vfsstream": "^1.6", "doctrine/orm": "^2.6",
"doctrine/persistence": "^1.3||^2.0",
"phpstan/phpstan": "^0.12", "phpstan/phpstan": "^0.12",
"phpstan/phpstan-deprecation-rules": "^0.12",
"phpstan/phpstan-phpunit": "^0.12",
"phpstan/phpstan-strict-rules": "^0.12", "phpstan/phpstan-strict-rules": "^0.12",
"phpunit/phpunit": "^7.0|^8.0|^9.0" "phpunit/phpunit": "^8.0|^9.0",
"vimeo/psalm": "^4.11"
}, },
"type": "symfony-bundle", "type": "symfony-bundle",
"autoload": { "autoload": {
@ -1035,11 +1041,11 @@
}, },
{ {
"name": "Doctrine Project", "name": "Doctrine Project",
"homepage": "http://www.doctrine-project.org" "homepage": "https://www.doctrine-project.org"
}, },
{ {
"name": "Symfony Community", "name": "Symfony Community",
"homepage": "http://symfony.com/contributors" "homepage": "https://symfony.com/contributors"
} }
], ],
"description": "Symfony DoctrineMigrationsBundle", "description": "Symfony DoctrineMigrationsBundle",
@ -1051,7 +1057,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/doctrine/DoctrineMigrationsBundle/issues", "issues": "https://github.com/doctrine/DoctrineMigrationsBundle/issues",
"source": "https://github.com/doctrine/DoctrineMigrationsBundle/tree/2.2.3" "source": "https://github.com/doctrine/DoctrineMigrationsBundle/tree/3.2.2"
}, },
"funding": [ "funding": [
{ {
@ -1067,7 +1073,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2021-03-18T20:55:50+00:00" "time": "2022-02-01T18:08:07+00:00"
}, },
{ {
"name": "doctrine/event-manager", "name": "doctrine/event-manager",
@ -1402,49 +1408,63 @@
}, },
{ {
"name": "doctrine/migrations", "name": "doctrine/migrations",
"version": "2.3.5", "version": "3.5.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/doctrine/migrations.git", "url": "https://github.com/doctrine/migrations.git",
"reference": "28d92a34348fee5daeb80879e56461b2e862fc05" "reference": "c0a01ddead0ccaf0282f3f4fcaa026d11918a481"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/doctrine/migrations/zipball/28d92a34348fee5daeb80879e56461b2e862fc05", "url": "https://api.github.com/repos/doctrine/migrations/zipball/c0a01ddead0ccaf0282f3f4fcaa026d11918a481",
"reference": "28d92a34348fee5daeb80879e56461b2e862fc05", "reference": "c0a01ddead0ccaf0282f3f4fcaa026d11918a481",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"composer/package-versions-deprecated": "^1.8", "composer-runtime-api": "^2",
"doctrine/dbal": "^2.9", "doctrine/dbal": "^3.3",
"doctrine/deprecations": "^0.5.3 || ^1",
"doctrine/event-manager": "^1.0",
"friendsofphp/proxy-manager-lts": "^1.0", "friendsofphp/proxy-manager-lts": "^1.0",
"php": "^7.1 || ^8.0", "php": "^7.4 || ^8.0",
"symfony/console": "^3.4||^4.4.16||^5.0", "psr/log": "^1.1.3 || ^2 || ^3",
"symfony/stopwatch": "^3.4||^4.0||^5.0" "symfony/console": "^4.4.16 || ^5.4 || ^6.0",
"symfony/stopwatch": "^4.4 || ^5.4 || ^6.0"
},
"conflict": {
"doctrine/orm": "<2.12"
}, },
"require-dev": { "require-dev": {
"doctrine/coding-standard": "^8.2", "doctrine/coding-standard": "^9",
"doctrine/orm": "^2.6", "doctrine/orm": "^2.12",
"doctrine/persistence": "^2 || ^3",
"doctrine/sql-formatter": "^1.0",
"ergebnis/composer-normalize": "^2.9",
"ext-pdo_sqlite": "*", "ext-pdo_sqlite": "*",
"jdorn/sql-formatter": "^1.1", "phpstan/phpstan": "^1.5",
"mikey179/vfsstream": "^1.6", "phpstan/phpstan-deprecation-rules": "^1",
"phpstan/phpstan": "^0.12", "phpstan/phpstan-phpunit": "^1.1",
"phpstan/phpstan-deprecation-rules": "^0.12", "phpstan/phpstan-strict-rules": "^1.1",
"phpstan/phpstan-phpunit": "^0.12", "phpstan/phpstan-symfony": "^1.1",
"phpstan/phpstan-strict-rules": "^0.12", "phpunit/phpunit": "^9.5",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.4", "symfony/cache": "^4.4 || ^5.4 || ^6.0",
"symfony/cache": "^4.4. || ^5.3", "symfony/process": "^4.4 || ^5.4 || ^6.0",
"symfony/process": "^3.4||^4.0||^5.0", "symfony/yaml": "^4.4 || ^5.4 || ^6.0"
"symfony/yaml": "^3.4||^4.0||^5.0"
}, },
"suggest": { "suggest": {
"jdorn/sql-formatter": "Allows to generate formatted SQL with the diff command.", "doctrine/sql-formatter": "Allows to generate formatted SQL with the diff command.",
"symfony/yaml": "Allows the use of yaml for migration configuration files." "symfony/yaml": "Allows the use of yaml for migration configuration files."
}, },
"bin": [ "bin": [
"bin/doctrine-migrations" "bin/doctrine-migrations"
], ],
"type": "library", "type": "library",
"extra": {
"composer-normalize": {
"indent-size": 4,
"indent-style": "space"
}
},
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"Doctrine\\Migrations\\": "lib/Doctrine/Migrations" "Doctrine\\Migrations\\": "lib/Doctrine/Migrations"
@ -1473,12 +1493,11 @@
"keywords": [ "keywords": [
"database", "database",
"dbal", "dbal",
"migrations", "migrations"
"php"
], ],
"support": { "support": {
"issues": "https://github.com/doctrine/migrations/issues", "issues": "https://github.com/doctrine/migrations/issues",
"source": "https://github.com/doctrine/migrations/tree/2.3.5" "source": "https://github.com/doctrine/migrations/tree/3.5.1"
}, },
"funding": [ "funding": [
{ {
@ -1494,7 +1513,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2021-10-19T19:55:20+00:00" "time": "2022-05-09T20:24:38+00:00"
}, },
{ {
"name": "doctrine/orm", "name": "doctrine/orm",
@ -2716,16 +2735,16 @@
}, },
{ {
"name": "php-http/discovery", "name": "php-http/discovery",
"version": "1.14.2", "version": "1.14.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/php-http/discovery.git", "url": "https://github.com/php-http/discovery.git",
"reference": "c8d48852fbc052454af42f6de27635ddd916b959" "reference": "31d8ee46d0215108df16a8527c7438e96a4d7735"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/php-http/discovery/zipball/c8d48852fbc052454af42f6de27635ddd916b959", "url": "https://api.github.com/repos/php-http/discovery/zipball/31d8ee46d0215108df16a8527c7438e96a4d7735",
"reference": "c8d48852fbc052454af42f6de27635ddd916b959", "reference": "31d8ee46d0215108df16a8527c7438e96a4d7735",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2777,9 +2796,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/php-http/discovery/issues", "issues": "https://github.com/php-http/discovery/issues",
"source": "https://github.com/php-http/discovery/tree/1.14.2" "source": "https://github.com/php-http/discovery/tree/1.14.3"
}, },
"time": "2022-05-25T07:26:05+00:00" "time": "2022-07-11T14:04:40+00:00"
}, },
{ {
"name": "php-http/httplug", "name": "php-http/httplug",
@ -3771,16 +3790,16 @@
}, },
{ {
"name": "sentry/sentry", "name": "sentry/sentry",
"version": "3.6.1", "version": "3.7.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/getsentry/sentry-php.git", "url": "https://github.com/getsentry/sentry-php.git",
"reference": "5b8f2934b0b20bb01da11c76985ceb5bd6c6af91" "reference": "877bca3f0f0ac0fc8ec0a218c6070cccea266795"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/getsentry/sentry-php/zipball/5b8f2934b0b20bb01da11c76985ceb5bd6c6af91", "url": "https://api.github.com/repos/getsentry/sentry-php/zipball/877bca3f0f0ac0fc8ec0a218c6070cccea266795",
"reference": "5b8f2934b0b20bb01da11c76985ceb5bd6c6af91", "reference": "877bca3f0f0ac0fc8ec0a218c6070cccea266795",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -3826,7 +3845,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "3.6.x-dev" "dev-master": "3.7.x-dev"
} }
}, },
"autoload": { "autoload": {
@ -3860,7 +3879,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/getsentry/sentry-php/issues", "issues": "https://github.com/getsentry/sentry-php/issues",
"source": "https://github.com/getsentry/sentry-php/tree/3.6.1" "source": "https://github.com/getsentry/sentry-php/tree/3.7.0"
}, },
"funding": [ "funding": [
{ {
@ -3872,7 +3891,7 @@
"type": "custom" "type": "custom"
} }
], ],
"time": "2022-06-27T07:58:00+00:00" "time": "2022-07-18T07:55:36+00:00"
}, },
{ {
"name": "sentry/sentry-symfony", "name": "sentry/sentry-symfony",
@ -4053,16 +4072,16 @@
}, },
{ {
"name": "spiral/roadrunner", "name": "spiral/roadrunner",
"version": "v2.10.5", "version": "v2.10.7",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/roadrunner-server/roadrunner.git", "url": "https://github.com/roadrunner-server/roadrunner.git",
"reference": "3996ba6d12f953f808408e276f69dfcf0950e906" "reference": "18a7a98bcb483a680b6ebe7da8bb61e95329daf4"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/roadrunner-server/roadrunner/zipball/3996ba6d12f953f808408e276f69dfcf0950e906", "url": "https://api.github.com/repos/roadrunner-server/roadrunner/zipball/18a7a98bcb483a680b6ebe7da8bb61e95329daf4",
"reference": "3996ba6d12f953f808408e276f69dfcf0950e906", "reference": "18a7a98bcb483a680b6ebe7da8bb61e95329daf4",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -4088,9 +4107,9 @@
"description": "RoadRunner: High-performance PHP application server, load-balancer and process manager written in Golang", "description": "RoadRunner: High-performance PHP application server, load-balancer and process manager written in Golang",
"support": { "support": {
"issues": "https://github.com/roadrunner-server/roadrunner/issues", "issues": "https://github.com/roadrunner-server/roadrunner/issues",
"source": "https://github.com/roadrunner-server/roadrunner/tree/v2.10.5" "source": "https://github.com/roadrunner-server/roadrunner/tree/v2.10.7"
}, },
"time": "2022-06-23T20:54:36+00:00" "time": "2022-07-14T09:00:44+00:00"
}, },
{ {
"name": "spiral/roadrunner-cli", "name": "spiral/roadrunner-cli",

View file

@ -1,5 +1,6 @@
doctrine_migrations: doctrine_migrations:
dir_name: '%kernel.project_dir%/src/Migrations' migrations_paths:
# namespace is arbitrary but should be different from App\Migrations 'DoctrineMigrations': '%kernel.project_dir%/src/Migrations'
# as migrations classes should NOT be autoloaded storage:
namespace: DoctrineMigrations table_storage:
table_name: 'migration_versions'

View file

@ -7,6 +7,9 @@ framework:
# https://symfony.com/doc/5.4/deployment/proxies.html#but-what-if-the-ip-of-my-reverse-proxy-changes-constantly # https://symfony.com/doc/5.4/deployment/proxies.html#but-what-if-the-ip-of-my-reverse-proxy-changes-constantly
trusted_proxies: '%env(TRUSTED_PROXIES)%' trusted_proxies: '%env(TRUSTED_PROXIES)%'
router:
utf8: true
# Enables session support. Note that the session will ONLY be started if you read or write from it. # Enables session support. Note that the session will ONLY be started if you read or write from it.
# Remove or comment this section to explicitly disable session support. # Remove or comment this section to explicitly disable session support.
session: session:

View file

@ -6,6 +6,7 @@ security:
class: App\Entity\User class: App\Entity\User
property: username property: username
manager_name: default manager_name: default
enable_authenticator_manager: true
password_hashers: password_hashers:
App\Entity\User: App\Entity\User:
algorithm: sodium algorithm: sodium
@ -15,14 +16,11 @@ security:
security: false security: false
api: api:
pattern: ^/api/ pattern: ^/api/
anonymous: ~
stateless: true stateless: true
guard: custom_authenticators:
authenticators: - App\Security\ApiTokenAuthenticator
- App\Security\ApiTokenAuthenticator
main: main:
pattern: ^/ pattern: ^/
anonymous: ~
provider: default_provider provider: default_provider
form_login: form_login:
login_path: user_auth_login login_path: user_auth_login
@ -40,10 +38,10 @@ security:
# Easy way to control access for large sections of your site # Easy way to control access for large sections of your site
# Note: Only the *first* access control that matches will be used # Note: Only the *first* access control that matches will be used
access_control: access_control:
- { path: ^/api/v1/login$, roles: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/api/v1/login$, roles: PUBLIC_ACCESS }
- { path: ^/api/, roles: ROLE_USER } - { path: ^/api/, roles: ROLE_USER }
- { path: ^/$, roles: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/$, roles: PUBLIC_ACCESS }
- { path: ^/auth/, roles: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/auth/, roles: PUBLIC_ACCESS }
- { path: ^/register/, roles: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/register/, roles: PUBLIC_ACCESS }
- { path: ^/magnet/, roles: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/magnet/, roles: PUBLIC_ACCESS }
- { path: ^/, roles: ROLE_USER } - { path: ^/, roles: ROLE_USER }

View file

@ -11,7 +11,7 @@ abstract class AbstractApiController extends AbstractController
{ {
protected const DEFAULT_SERIALIZER_GROUPS = ['api']; protected const DEFAULT_SERIALIZER_GROUPS = ['api'];
protected function createJsonResponse($data, array $groups = [], int $code = Response::HTTP_OK, string $message = null, string $status = ''): JsonResponse protected function createJsonResponse($data = null, array $groups = [], int $code = Response::HTTP_OK, string $message = null, string $status = ''): JsonResponse
{ {
return $this->json(new ApiResponse($data, $code, $message, $status), $code, [], [ return $this->json(new ApiResponse($data, $code, $message, $status), $code, [], [
'groups' => array_merge(self::DEFAULT_SERIALIZER_GROUPS,$groups), 'groups' => array_merge(self::DEFAULT_SERIALIZER_GROUPS,$groups),

View file

@ -5,11 +5,10 @@ namespace App\Api\V1\Controller;
use App\Entity\{ApiToken, User}; use App\Entity\{ApiToken, User};
use App\Repository\{ApiTokenRepository, UserRepository}; use App\Repository\{ApiTokenRepository, UserRepository};
use App\Security\Token\AuthenticatedApiToken; use App\Security\ApiTokenAuthenticator;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\{JsonResponse, Request}; use Symfony\Component\HttpFoundation\{JsonResponse, Request};
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
class SecurityController extends AbstractApiController class SecurityController extends AbstractApiController
{ {
@ -18,18 +17,18 @@ class SecurityController extends AbstractApiController
EntityManagerInterface $em, EntityManagerInterface $em,
UserRepository $userRepo, UserRepository $userRepo,
ApiTokenRepository $tokenRepo, ApiTokenRepository $tokenRepo,
UserPasswordEncoderInterface $passwordEncoder UserPasswordHasherInterface $passwordHasher,
): JsonResponse { ): JsonResponse {
$username = $request->request->get('username'); $username = $request->request->get('username');
$password = $request->request->get('password'); $password = $request->request->get('password');
/** @var User $user */ /** @var User $user */
if (null === $user = $userRepo->findOneBy(['username' => $username])) { if (null === $user = $userRepo->findOneBy(['username' => $username])) {
return $this->createJsonResponse(null, [], JsonResponse::HTTP_UNAUTHORIZED, 'User not found'); return $this->createJsonResponse(code: JsonResponse::HTTP_UNAUTHORIZED, message: 'User not found');
} }
if (!$passwordEncoder->isPasswordValid($user, $password)) { if (!$passwordHasher->isPasswordValid($user, $password)) {
return $this->createJsonResponse(null, [], JsonResponse::HTTP_UNAUTHORIZED, 'Invalid password'); return $this->createJsonResponse(code: JsonResponse::HTTP_UNAUTHORIZED, message: 'Invalid password');
} }
$apiToken = new ApiToken($user); $apiToken = new ApiToken($user);
@ -38,28 +37,19 @@ class SecurityController extends AbstractApiController
try { try {
$em->flush(); $em->flush();
} catch (\Exception $ex) { } catch (\Exception $ex) {
return $this->createJsonResponse(null, [], JsonResponse::HTTP_INTERNAL_SERVER_ERROR, 'Token persisting error'); return $this->createJsonResponse(code: JsonResponse::HTTP_INTERNAL_SERVER_ERROR, message: 'Token persisting error');
} }
return $this->createJsonResponse($apiToken->getKey()); return $this->createJsonResponse($apiToken->getKey());
} }
public function logout(TokenStorageInterface $tokenStorage, ApiTokenRepository $apiTokenRepo, EntityManagerInterface $em): JsonResponse public function logout(Request $request, ApiTokenRepository $apiTokenRepo, EntityManagerInterface $em): JsonResponse {
{ if (null === $tokenKey = ApiTokenAuthenticator::getTokenKeyFromRequest($request)) {
if (null === $token = $tokenStorage->getToken()) { return $this->createJsonResponse(null, code:JsonResponse::HTTP_INTERNAL_SERVER_ERROR, message: 'No API token provided.');
return $this->createJsonResponse(null,[],JsonResponse::HTTP_INTERNAL_SERVER_ERROR, 'Can\'t retrieve user token.');
}
if (!$token instanceof AuthenticatedApiToken) {
return $this->createJsonResponse(null, [], JsonResponse::HTTP_INTERNAL_SERVER_ERROR, 'Invalid session token type retrieved.');
}
if (null === $tokenKey = $token->getTokenKey()) {
return $this->createJsonResponse(null,[],JsonResponse::HTTP_INTERNAL_SERVER_ERROR, 'Can\'t retrieve token key from the session.');
} }
if (null === $apiToken = $apiTokenRepo->findOneBy(['key' => $tokenKey])) { if (null === $apiToken = $apiTokenRepo->findOneBy(['key' => $tokenKey])) {
return $this->createJsonResponse(null,[],JsonResponse::HTTP_INTERNAL_SERVER_ERROR, 'API token with such key not found in the database.'); return $this->createJsonResponse(null, code:JsonResponse::HTTP_INTERNAL_SERVER_ERROR, message: 'API token with such key not found in the database.');
} }
$em->remove($apiToken); $em->remove($apiToken);

View file

@ -24,7 +24,7 @@ class ApiResponse
#[Groups(['api'])] #[Groups(['api'])]
private ?string $message; private ?string $message;
/** @Response body. In case of 'error' or 'fail' contains cause or exception name. */ /** Response body. In case of 'error' or 'fail' contains cause or exception name. */
#[Groups(['api'])] #[Groups(['api'])]
private string|object|array|null $data; private string|object|array|null $data;

View file

@ -4,125 +4,82 @@ declare(strict_types=1);
namespace App\Security; namespace App\Security;
use App\Api\V1\DTO\ApiResponse; use App\Api\V1\DTO\ApiResponse;
use App\Entity\User;
use App\Repository\ApiTokenRepository; use App\Repository\ApiTokenRepository;
use App\Security\Token\AuthenticatedApiToken; use Symfony\Component\HttpFoundation\{JsonResponse, Request, Response};
use Symfony\Component\HttpFoundation\{JsonResponse, Request, RequestStack, Response};
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\Exception\{AuthenticationException, CustomUserMessageAuthenticationException};
use Symfony\Component\Security\Core\User\{UserInterface, UserProviderInterface}; use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
use Symfony\Component\Security\Guard\AbstractGuardAuthenticator; use Symfony\Component\Security\Http\Authenticator\Passport\{Badge\UserBadge, Passport, SelfValidatingPassport};
use Symfony\Component\Serializer\SerializerInterface; use Symfony\Component\Serializer\SerializerInterface;
/** class ApiTokenAuthenticator extends AbstractAuthenticator
* @deprecated Refactor to new Authenticators system @see https://gitlab.com/skobkin/magnetico-web/-/issues/26
*/
class ApiTokenAuthenticator extends AbstractGuardAuthenticator
{ {
public const TOKEN_HEADER = 'api-token'; public const TOKEN_HEADER = 'api-token';
/** @var ApiTokenRepository */ public function __construct(
private $tokenRepo; private readonly SerializerInterface $serializer,
private readonly ApiTokenRepository $tokenRepo,
) {
/** @var SerializerInterface */
private $serializer;
/** @var RequestStack */
private $requestStack;
public function __construct(SerializerInterface $serializer, ApiTokenRepository $tokenRepo, RequestStack $requestStack)
{
$this->serializer = $serializer;
$this->tokenRepo = $tokenRepo;
// Crutch for Guard simplified auth to retrieve 'api-token' header in the createAuthenticatedToken()
$this->requestStack = $requestStack;
} }
/**
* Called on every request to decide if this authenticator should be
* used for the request. Returning `false` will cause this authenticator
* to be skipped.
*
* @see https://symfony.com/doc/6.1/security/custom_authenticator.html
*/
public function supports(Request $request): bool public function supports(Request $request): bool
{
return static::requestHasToken($request);
}
public function authenticate(Request $request): Passport
{
$tokenKey = static::getTokenKeyFromRequest($request);
if (null === $tokenKey) {
throw new CustomUserMessageAuthenticationException('No API token provided');
}
return new SelfValidatingPassport(
new UserBadge($tokenKey, function (string $userIdentifier) {
return $this->tokenRepo->findUserByTokenKey($userIdentifier);
})
);
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
{
// No response object needed in token auth
return null;
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): JsonResponse
{
// @todo Decouple with App\Api\V1\DTO
$json = $this->serializer->serialize(
new ApiResponse(null, JsonResponse::HTTP_UNAUTHORIZED, $exception->getMessageKey()),
'json',
['groups' => ['api']]
);
return new JsonResponse($json, JsonResponse::HTTP_UNAUTHORIZED, json: true);
}
public static function getTokenKeyFromRequest(Request $request): ?string
{
$request?->headers?->get(self::TOKEN_HEADER) ?:
$request?->cookies?->get(self::TOKEN_HEADER) ?:
$request?->query?->get(self::TOKEN_HEADER);
}
public static function requestHasToken(Request $request): bool
{ {
// Let's also support cookies and query params for some cases like torrent clients. // Let's also support cookies and query params for some cases like torrent clients.
return $request->headers->has(self::TOKEN_HEADER) || return $request->headers->has(self::TOKEN_HEADER) ||
$request->cookies->has(self::TOKEN_HEADER) || $request->cookies->has(self::TOKEN_HEADER) ||
$request->query->has(self::TOKEN_HEADER); $request->query->has(self::TOKEN_HEADER);
} }
public function getCredentials(Request $request)
{
return [
'token' => $request->headers->get(self::TOKEN_HEADER) ?:
$request->cookies->get(self::TOKEN_HEADER) ?:
$request->query->get(self::TOKEN_HEADER),
];
}
public function getUser($credentials, UserProviderInterface $userProvider): ?User
{
if (null === $token = $credentials['token']) {
return null;
}
return $this->tokenRepo->findUserByTokenKey($token);
}
public function start(Request $request, AuthenticationException $authException = null)
{
$message = sprintf('You need to use \'%s\' in your request: %s', self::TOKEN_HEADER, $authException ? $authException->getMessage() : '');
$json = $this->serializer->serialize(
new ApiResponse(null, JsonResponse::HTTP_UNAUTHORIZED, $message),
'json',
['groups' => ['api']]
);
return new JsonResponse($json, Response::HTTP_UNAUTHORIZED,[], true);
}
public function checkCredentials($credentials, UserInterface $user)
{
// No credentials check needed in case of token auth
return true;
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
// No response object needed in token auth
return null;
}
public function supportsRememberMe()
{
// Remember me functionality don't needed in token auth
return false;
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): JsonResponse
{
// @todo Decouple with App\Api\V1\DTO
$json = $this->serializer->serialize(
new ApiResponse(null, JsonResponse::HTTP_UNAUTHORIZED, $exception->getMessage()),
'json',
['groups' => ['api']]
);
return new JsonResponse($json, JsonResponse::HTTP_UNAUTHORIZED,[], true);
}
/** @deprecated use AuthenticatorInterface::createToken() instead */
public function createAuthenticatedToken(UserInterface $user, $providerKey)
{
$request = $this->requestStack->getCurrentRequest();
$tokenKey = $request?->headers?->get(self::TOKEN_HEADER) ?:
$request?->cookies?->get(self::TOKEN_HEADER) ?:
$request?->query?->get(self::TOKEN_HEADER)
;
return new AuthenticatedApiToken(
$user,
$tokenKey,
$providerKey,
$user->getRoles()
);
}
} }

View file

@ -1,31 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Security\Token;
use App\Entity\User;
use Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken;
use Symfony\Component\Security\Guard\Token\GuardTokenInterface;
/**
* This token stores ApiToken key even after eraseCredentials() called
*
* @deprecated Refactor to new Authenticators system @see https://gitlab.com/skobkin/magnetico-web/-/issues/26
*/
class AuthenticatedApiToken extends PreAuthenticatedToken implements GuardTokenInterface
{
/** @var string|null This token is stored only for this request and will not be erased by eraseCredentials() or serialized */
private $tokenKey;
public function __construct(User $user, string $credentials, string $providerKey, array $roles = [])
{
parent::__construct($user, $credentials, $providerKey, $roles);
// @todo probably separate constructor argument needed
$this->tokenKey = $credentials;
}
public function getTokenKey(): ?string
{
return $this->tokenKey;
}
}