From c418ae63995967a79736a26e1c6492eecb0e8412 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Fromme?= Date: Thu, 24 Apr 2025 17:08:44 +0200 Subject: [PATCH] feat: minimal locally stored user entity --- .env | 4 +- config/packages/security.yaml | 10 +- deploy.php | 4 +- migrations/Version20250322141523.php | 40 ------ migrations/Version20250424143458.php | 41 ++++++ src/BusProNet/Security/User.php | 51 ------- src/BusProNet/Security/UserProvider.php | 60 --------- src/Controller/Booking/DownloadController.php | 4 +- src/Controller/Booking/EditController.php | 4 +- src/Controller/Booking/IndexController.php | 5 +- src/Controller/PersonalDataController.php | 5 +- src/Controller/Traits/CredentialsTrait.php | 16 --- src/Entity/User.php | 126 ++++++++++++++++++ .../BpnAuthenticator.php} | 47 +++++-- 14 files changed, 217 insertions(+), 200 deletions(-) delete mode 100644 migrations/Version20250322141523.php create mode 100644 migrations/Version20250424143458.php delete mode 100644 src/BusProNet/Security/User.php delete mode 100644 src/BusProNet/Security/UserProvider.php delete mode 100644 src/Controller/Traits/CredentialsTrait.php create mode 100644 src/Entity/User.php rename src/{BusProNet/Security/Authenticator.php => Security/BpnAuthenticator.php} (71%) diff --git a/.env b/.env index a531507..c2ae58d 100644 --- a/.env +++ b/.env @@ -23,9 +23,9 @@ APP_SECRET=642ead390b8a38f0a017ec84d5362129 # Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url # IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml # -DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db" +# DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db" # DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=8.0.32&charset=utf8mb4" -# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=10.11.2-MariaDB&charset=utf8mb4" +DATABASE_URL="mysql://db:db@db:3306/db?serverVersion=10.11.11-MariaDB&charset=utf8mb4" # DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=16&charset=utf8" ###< doctrine/doctrine-bundle ### diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 2d5e81a..6d41b9e 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -4,8 +4,10 @@ security: Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto' # https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider providers: - bpn_user_provider: - id: App\BusProNet\Security\UserProvider + app_user_provider: + entity: + class: App\Entity\User + property: email firewalls: dev: pattern: ^/(_(profiler|wdt)|css|images|js)/ @@ -18,9 +20,9 @@ security: - App\Security\ApiKeyAuthenticator main: lazy: true - provider: bpn_user_provider + provider: app_user_provider custom_authenticator: - App\BusProNet\Security\Authenticator + App\Security\BpnAuthenticator logout: path: app_logout target: app_login diff --git a/deploy.php b/deploy.php index 24527e2..3711c8b 100644 --- a/deploy.php +++ b/deploy.php @@ -103,10 +103,10 @@ task('deploy', [ 'rsync', 'deploy:shared', 'deploy:writable', -// 'database:migrate', + 'database:migrate', 'deploy:publish', 'cachetool:clear:opcache', -// 'deploy:stop-workers', + 'deploy:stop-workers', ]); task('deploy:stop-workers', function () { diff --git a/migrations/Version20250322141523.php b/migrations/Version20250322141523.php deleted file mode 100644 index 44bb0e7..0000000 --- a/migrations/Version20250322141523.php +++ /dev/null @@ -1,40 +0,0 @@ -addSql('CREATE TABLE messenger_messages (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, body CLOB NOT NULL, headers CLOB NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at DATETIME NOT NULL --(DC2Type:datetime_immutable) - , available_at DATETIME NOT NULL --(DC2Type:datetime_immutable) - , delivered_at DATETIME DEFAULT NULL --(DC2Type:datetime_immutable) - )'); - $this->addSql('CREATE INDEX IDX_75EA56E0FB7336F0 ON messenger_messages (queue_name)'); - $this->addSql('CREATE INDEX IDX_75EA56E0E3BD61CE ON messenger_messages (available_at)'); - $this->addSql('CREATE INDEX IDX_75EA56E016BA31DB ON messenger_messages (delivered_at)'); - $this->addSql('CREATE TABLE rememberme_token (series VARCHAR(88) NOT NULL, value VARCHAR(88) NOT NULL, lastUsed DATETIME NOT NULL --(DC2Type:datetime_immutable) - , class VARCHAR(100) NOT NULL, username VARCHAR(200) NOT NULL, PRIMARY KEY(series))'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('DROP TABLE messenger_messages'); - $this->addSql('DROP TABLE rememberme_token'); - } -} diff --git a/migrations/Version20250424143458.php b/migrations/Version20250424143458.php new file mode 100644 index 0000000..54ccdce --- /dev/null +++ b/migrations/Version20250424143458.php @@ -0,0 +1,41 @@ +addSql(<<<'SQL' + CREATE TABLE user (id INT AUTO_INCREMENT NOT NULL, email VARCHAR(180) NOT NULL, password LONGTEXT NOT NULL, person_id INT DEFAULT NULL, address_id INT DEFAULT NULL, roles JSON NOT NULL COMMENT '(DC2Type:json)', last_login_at DATETIME DEFAULT NULL COMMENT '(DC2Type:datetime_immutable)', UNIQUE INDEX UNIQ_8D93D649E7927C74 (email), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB + SQL); + $this->addSql(<<<'SQL' + CREATE TABLE messenger_messages (id BIGINT AUTO_INCREMENT NOT NULL, body LONGTEXT NOT NULL, headers LONGTEXT NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at DATETIME NOT NULL COMMENT '(DC2Type:datetime_immutable)', available_at DATETIME NOT NULL COMMENT '(DC2Type:datetime_immutable)', delivered_at DATETIME DEFAULT NULL COMMENT '(DC2Type:datetime_immutable)', INDEX IDX_75EA56E0FB7336F0 (queue_name), INDEX IDX_75EA56E0E3BD61CE (available_at), INDEX IDX_75EA56E016BA31DB (delivered_at), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB + SQL); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql(<<<'SQL' + DROP TABLE user + SQL); + $this->addSql(<<<'SQL' + DROP TABLE messenger_messages + SQL); + } +} diff --git a/src/BusProNet/Security/User.php b/src/BusProNet/Security/User.php deleted file mode 100644 index 0f59d51..0000000 --- a/src/BusProNet/Security/User.php +++ /dev/null @@ -1,51 +0,0 @@ -email; - } - - public function getPassword(): ?string - { - return $this->password; - } - - public function getPersonId(): ?int - { - return $this->personId; - } - - public function getAddressId(): int - { - return $this->addressId; - } - - public function getRoles(): array - { - return ['ROLE_USER', ...$this->roles]; - } - - public function eraseCredentials(): void - { - } - - public function getUserIdentifier(): string - { - return $this->email; - } -} diff --git a/src/BusProNet/Security/UserProvider.php b/src/BusProNet/Security/UserProvider.php deleted file mode 100644 index f2862f5..0000000 --- a/src/BusProNet/Security/UserProvider.php +++ /dev/null @@ -1,60 +0,0 @@ -requestStack->getSession()->get('bpn_user')) { - return $activeUser; - } - - return $this->loadUserByIdentifier($user->getUserIdentifier()); - } - - public function supportsClass(string $class): bool - { - return User::class === $class; - } - - public function loadUserByIdentifier(string $identifier): UserInterface - { - if (null === $activeUser = $this->requestStack->getSession()->get('bpn_user')) { - throw new UserNotFoundException(); - } - - try { - $email = $activeUser->getEmail(); - $password = $this->crypt->decrypt($activeUser->getPassword()); - $response = $this->apiClient->getPersonalData($email, $password); - } catch (ApiClientException $e) { - throw new UserNotFoundException(); - } - - if (false === $response instanceof PersonalData) { - throw new UserNotFoundException(); - } - - return $activeUser; - } -} diff --git a/src/Controller/Booking/DownloadController.php b/src/Controller/Booking/DownloadController.php index 9353b98..aec3037 100644 --- a/src/Controller/Booking/DownloadController.php +++ b/src/Controller/Booking/DownloadController.php @@ -5,9 +5,8 @@ namespace App\Controller\Booking; use App\BusProNet\ApiClient; use App\BusProNet\Exception\ApiClientException; use App\BusProNet\Model\Notification; -use App\BusProNet\Security\User; use App\Controller\Traits\BookingDataTrait; -use App\Controller\Traits\CredentialsTrait; +use App\Entity\User; use App\Security\Crypt; use Psr\Log\LoggerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; @@ -21,7 +20,6 @@ use function Symfony\Component\String\u; class DownloadController extends AbstractController { - use CredentialsTrait; use BookingDataTrait; public function __construct( diff --git a/src/Controller/Booking/EditController.php b/src/Controller/Booking/EditController.php index 1526cd0..7d760e8 100644 --- a/src/Controller/Booking/EditController.php +++ b/src/Controller/Booking/EditController.php @@ -5,11 +5,10 @@ namespace App\Controller\Booking; use App\BusProNet\ApiClient; use App\BusProNet\Exception\ApiClientException; use App\BusProNet\Model\Notification; -use App\BusProNet\Security\User; use App\BusProNet\XmlLoader\PickupLoader; use App\BusProNet\XmlLoader\TravelLoader; use App\Controller\Traits\BookingDataTrait; -use App\Controller\Traits\CredentialsTrait; +use App\Entity\User; use App\Form\BookingType; use App\Form\Model\BookingData; use App\Security\Crypt; @@ -25,7 +24,6 @@ use Symfony\Contracts\Cache\CacheInterface; class EditController extends AbstractController { - use CredentialsTrait; use BookingDataTrait; public function __construct( diff --git a/src/Controller/Booking/IndexController.php b/src/Controller/Booking/IndexController.php index 180ee6c..712c1a9 100644 --- a/src/Controller/Booking/IndexController.php +++ b/src/Controller/Booking/IndexController.php @@ -6,9 +6,8 @@ use App\BusProNet\ApiClient; use App\BusProNet\Exception\ApiClientException; use App\BusProNet\Model\BaseData; use App\BusProNet\Model\Notification; -use App\BusProNet\Security\User; use App\BusProNet\XmlLoader\TravelLoader; -use App\Controller\Traits\CredentialsTrait; +use App\Entity\User; use App\Security\Crypt; use Psr\Log\LoggerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; @@ -19,8 +18,6 @@ use Symfony\Component\Security\Http\Attribute\IsGranted; class IndexController extends AbstractController { - use CredentialsTrait; - public function __construct( private readonly ApiClient $apiClient, private readonly TravelLoader $travelDataLoader, diff --git a/src/Controller/PersonalDataController.php b/src/Controller/PersonalDataController.php index 8a4366d..609cd6b 100644 --- a/src/Controller/PersonalDataController.php +++ b/src/Controller/PersonalDataController.php @@ -6,8 +6,7 @@ use App\BusProNet\ApiClient; use App\BusProNet\Exception\ApiClientException; use App\BusProNet\Model\Notification; use App\BusProNet\Model\PersonalData; -use App\BusProNet\Security\User; -use App\Controller\Traits\CredentialsTrait; +use App\Entity\User; use App\Form\PersonalDataType; use App\Security\Crypt; use Psr\Log\LoggerInterface; @@ -19,8 +18,6 @@ use Symfony\Component\Security\Http\Attribute\IsGranted; class PersonalDataController extends AbstractController { - use CredentialsTrait; - public function __construct( private readonly ApiClient $apiClient, private readonly Crypt $crypt, diff --git a/src/Controller/Traits/CredentialsTrait.php b/src/Controller/Traits/CredentialsTrait.php deleted file mode 100644 index e9d2458..0000000 --- a/src/Controller/Traits/CredentialsTrait.php +++ /dev/null @@ -1,16 +0,0 @@ -decrypt($bpnUser->getPassword()); - } -} diff --git a/src/Entity/User.php b/src/Entity/User.php new file mode 100644 index 0000000..bb712b2 --- /dev/null +++ b/src/Entity/User.php @@ -0,0 +1,126 @@ +email = $email; + $this->password = base64_encode($password); + } + + public function getId(): ?int + { + return $this->id; + } + + public function getEmail(): ?string + { + return $this->email; + } + + public function setEmail(?string $email): static + { + $this->email = $email; + + return $this; + } + + public function getPersonId(): ?int + { + return $this->personId; + } + + public function setPersonId(?int $personId): static + { + $this->personId = $personId; + + return $this; + } + + public function getAddressId(): ?int + { + return $this->addressId; + } + + public function setAddressId(?int $addressId): static + { + $this->addressId = $addressId; + + return $this; + } + + public function getPassword(): ?string + { + return base64_decode($this->password); + } + + public function setPassword(?string $password): static + { + $this->password = base64_encode($password); + + return $this; + } + + public function getRoles(): array + { + return ['ROLE_USER', ...$this->roles]; + } + + public function setRoles(array $roles): static + { + $this->roles = $roles; + + return $this; + } + + public function getLastLoginAt(): ?\DateTimeImmutable + { + return $this->lastLoginAt; + } + + public function setLastLoginAt(?\DateTimeImmutable $lastLoginAt): static + { + $this->lastLoginAt = $lastLoginAt; + + return $this; + } + + public function eraseCredentials(): void + { + } + + public function getUserIdentifier(): string + { + return $this->email; + } +} diff --git a/src/BusProNet/Security/Authenticator.php b/src/Security/BpnAuthenticator.php similarity index 71% rename from src/BusProNet/Security/Authenticator.php rename to src/Security/BpnAuthenticator.php index 20bb7c4..b3c996e 100644 --- a/src/BusProNet/Security/Authenticator.php +++ b/src/Security/BpnAuthenticator.php @@ -1,12 +1,13 @@ apiClient->getCrmAttributes($email, $password); - $roles = $this->collectRoles($crmAttributes); - $encryptedPassword = $this->crypt->encrypt($password); - $user = new User($email, $response->personId, $response->addressId, $encryptedPassword, $roles); - - $request->getSession()->set('bpn_user', $user); - - return $user; + return $this->createOrUpdateLocalUser($email, $password, $response->personId, $response->addressId); }), [ new CsrfTokenBadge('authenticate', $csrfToken), @@ -75,6 +70,36 @@ class Authenticator extends AbstractLoginFormAuthenticator implements Authentica ); } + private function createOrUpdateLocalUser(string $email, string $password, ?int $personId, ?int $addressId): User + { + try { + $crmAttributes = $this->apiClient->getCrmAttributes($email, $password); + } catch (ApiClientException $e) { + throw new CustomUserMessageAuthenticationException($e->getMessage()); + } + + $roles = $this->collectRoles($crmAttributes); + $encryptedPassword = $this->crypt->encrypt($password); + + $userRepository = $this->entityManager->getRepository(User::class); + + if (null === $user = $userRepository->findOneBy(['email' => $email])) { + $user = new User($email, $encryptedPassword); + $this->entityManager->persist($user); + } + + $user + ->setPersonId($personId) + ->setAddressId($addressId) + ->setRoles($roles) + ->setLastLoginAt(new \DateTimeImmutable()) + ; + + $this->entityManager->flush(); + + return $user; + } + public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response { $this->logger->info('Login', [