feat: minimal locally stored user entity

This commit is contained in:
Björn Fromme
2025-04-24 17:09:05 +02:00
parent 53884e4e12
commit c418ae6399
14 changed files with 217 additions and 200 deletions
-106
View File
@@ -1,106 +0,0 @@
<?php
namespace App\BusProNet\Security;
use App\BusProNet\ApiClient;
use App\BusProNet\Exception\ApiClientException;
use App\BusProNet\Model\CrmAttributes;
use App\BusProNet\Model\PersonalData;
use App\Security\Crypt;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Http\Authenticator\AbstractLoginFormAuthenticator;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\CsrfTokenBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\RememberMeBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
class Authenticator extends AbstractLoginFormAuthenticator implements AuthenticationEntryPointInterface
{
public function __construct(
private readonly UrlGeneratorInterface $urlGenerator,
private readonly ApiClient $apiClient,
private readonly Crypt $crypt,
private readonly LoggerInterface $logger,
) {
}
protected function getLoginUrl(Request $request): string
{
return $this->urlGenerator->generate('app_login');
}
public function authenticate(Request $request): Passport
{
$email = trim($request->request->getString('_username'));
$passwordPlain = trim($request->request->getString('_password'));
// Very lame hashing applied here as required by BPN
$password = md5($passwordPlain);
try {
$response = $this->apiClient->getPersonalData($email, $password);
} catch (ApiClientException $e) {
throw new CustomUserMessageAuthenticationException($e->getMessage());
}
if (false === $response instanceof PersonalData) {
throw new CustomUserMessageAuthenticationException('Der Login ist fehlgeschlagen :(');
}
$csrfToken = $request->request->getString('_csrf_token');
return new SelfValidatingPassport(
new UserBadge($email, function () use ($email, $password, $response, $request) {
$crmAttributes = $this->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;
}),
[
new CsrfTokenBadge('authenticate', $csrfToken),
new RememberMeBadge(),
]
);
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
{
$this->logger->info('Login', [
'email' => $token->getUserIdentifier(),
]);
return new RedirectResponse($this->urlGenerator->generate('app_personal_data'));
}
private function collectRoles(CrmAttributes $crmAttributes): array
{
// Collect user's roles from CRM attributes
$roles = [];
if ($crmAttributes->admin) {
$roles[] = 'ROLE_ADMIN';
} elseif ($crmAttributes->manager) {
$roles[] = 'ROLE_MANAGER';
} elseif ($crmAttributes->houseManager) {
$roles[] = 'ROLE_HOUSE_MANAGER';
}
if ($crmAttributes->teamer) {
$roles[] = 'ROLE_TEAMER';
}
return $roles;
}
}
-51
View File
@@ -1,51 +0,0 @@
<?php
namespace App\BusProNet\Security;
use Symfony\Component\Security\Core\User\UserInterface;
class User implements UserInterface
{
public function __construct(
private readonly string $email,
private readonly int $personId,
private readonly int $addressId,
private readonly ?string $password = null,
private readonly array $roles = [],
) {
}
public function getEmail(): ?string
{
return $this->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;
}
}
-60
View File
@@ -1,60 +0,0 @@
<?php
namespace App\BusProNet\Security;
use App\BusProNet\ApiClient;
use App\BusProNet\Exception\ApiClientException;
use App\BusProNet\Model\PersonalData;
use App\Controller\Traits\CredentialsTrait;
use App\Security\Crypt;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Security\Core\Exception\UserNotFoundException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
class UserProvider implements UserProviderInterface
{
use CredentialsTrait;
public function __construct(
private readonly ApiClient $apiClient,
private readonly RequestStack $requestStack,
private readonly Crypt $crypt,
) {
}
public function refreshUser(UserInterface $user): UserInterface
{
if (null !== $activeUser = $this->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;
}
}