diff --git a/composer.json b/composer.json index 77bb5874..0c08c4cc 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,7 @@ "elao/enum": "^1.7", "erusev/parsedown": "^1.7", "friendsofsymfony/jsrouting-bundle": "^2.5", - "knpuniversity/oauth2-client-bundle": "^2.7", + "knpuniversity/oauth2-client-bundle": "^2.15", "league/csv": "^9.6", "nelmio/cors-bundle": "^2.1", "phpdocumentor/reflection-docblock": "^5.3", @@ -59,7 +59,7 @@ "twig/markdown-extra": "^3.0", "twig/string-extra": "^3.0", "twig/twig": "^2.12|^3.0", - "wohali/oauth2-discord-new": "^1.0" + "wohali/oauth2-discord-new": "^1.2" }, "require-dev": { "phpunit/phpunit": "^9.5", diff --git a/composer.lock b/composer.lock index b0b07f7f..761bfcde 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "3b81e6b67679231f4f8e75e4587ff19d", + "content-hash": "a7fd5f3c89fd35affcf79111f644cf94", "packages": [ { "name": "api-platform/core", diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 844ab59e..58a0d726 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -31,9 +31,8 @@ security: secret: '%kernel.secret%' lifetime: 604800 # 1 week in seconds always_remember_me: true - guard: - authenticators: - - App\Security\Authenticator\DiscordAuthenticator + custom_authenticators: + - App\Security\Authenticator\DiscordAuthenticator logout: path: app_security_logout diff --git a/src/Command/PermissionsMakeAdminCommand.php b/src/Command/PermissionsMakeAdminCommand.php index 196e6efc..1ede5a98 100644 --- a/src/Command/PermissionsMakeAdminCommand.php +++ b/src/Command/PermissionsMakeAdminCommand.php @@ -74,8 +74,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int return 1; } - $discordUserId = (int) $discordUserId; - $user = $this->userRepository->findOneByExternalId($discordUserId); if (!$user) { $io->error(sprintf('User not found by given id: "%s"!', $discordUserId)); diff --git a/src/Repository/User/UserRepository.php b/src/Repository/User/UserRepository.php index a2c6060b..c65df537 100644 --- a/src/Repository/User/UserRepository.php +++ b/src/Repository/User/UserRepository.php @@ -21,7 +21,7 @@ public function __construct(ManagerRegistry $registry) parent::__construct($registry, User::class); } - public function findOneByExternalId(int $externalId): ?User + public function findOneByExternalId(string $externalId): ?User { return $this->findOneBy([ 'externalId' => $externalId, diff --git a/src/Security/Authenticator/DiscordAuthenticator.php b/src/Security/Authenticator/DiscordAuthenticator.php index bfa5a3e4..08e3c56d 100644 --- a/src/Security/Authenticator/DiscordAuthenticator.php +++ b/src/Security/Authenticator/DiscordAuthenticator.php @@ -6,6 +6,7 @@ use App\Entity\Permissions\UserPermissions; use App\Entity\User\User; +use App\Repository\User\UserRepository; use App\Security\Enum\ConnectionsEnum; use App\Security\Exception\MultipleRolesFound; use App\Security\Exception\RequiredRolesNotAssignedException; @@ -17,17 +18,18 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\EntityManagerInterface; use KnpU\OAuth2ClientBundle\Client\ClientRegistry; -use KnpU\OAuth2ClientBundle\Client\OAuth2ClientInterface; -use KnpU\OAuth2ClientBundle\Security\Authenticator\SocialAuthenticator; -use League\OAuth2\Client\Token\AccessToken; +use KnpU\OAuth2ClientBundle\Security\Authenticator\OAuth2Authenticator; use Ramsey\Uuid\Uuid; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\RouterInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; -use Symfony\Component\Security\Core\Exception\UserNotFoundException; -use Symfony\Component\Security\Core\User\UserProviderInterface; +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; use Wohali\OAuth2\Client\Provider\DiscordResourceOwner; use function React\Async\await; @@ -35,7 +37,7 @@ /** * @see https://github.com/knpuniversity/oauth2-client-bundle */ -class DiscordAuthenticator extends SocialAuthenticator +class DiscordAuthenticator extends OAuth2Authenticator implements AuthenticationEntryPointInterface { protected const DISCORD_CLIENT_NAME = 'discord_main'; @@ -46,6 +48,7 @@ class DiscordAuthenticator extends SocialAuthenticator public function __construct( private ClientRegistry $clientRegistry, + private UserRepository $userRepository, private EntityManagerInterface $em, private RouterInterface $router, private DiscordClientFactory $discordClientFactory, @@ -67,18 +70,13 @@ public function supports(Request $request): bool return self::SUPPORTED_ROUTE_NAME === $request->attributes->get('_route'); } - public function getCredentials(Request $request): AccessToken + public function authenticate(Request $request): Passport { - return $this->fetchAccessToken($this->getDiscordClient()); - } + $client = $this->clientRegistry->getClient(self::DISCORD_CLIENT_NAME); + $accessToken = $this->fetchAccessToken($client); - /** - * @param AccessToken $credentials - */ - public function getUser($credentials, UserProviderInterface $userProvider): User - { /** @var DiscordResourceOwner $discordResourceOwner */ - $discordResourceOwner = $this->getDiscordClient()->fetchUserFromToken($credentials); + $discordResourceOwner = $client->fetchUserFromToken($accessToken); $userId = $discordResourceOwner->getId(); $username = $discordResourceOwner->getUsername(); @@ -87,7 +85,7 @@ public function getUser($credentials, UserProviderInterface $userProvider): User $externalId = $discordResourceOwner->getId(); $discordClientAsBot = $this->discordClientFactory->createBotClient($this->botToken); - $discordClientAsUser = $this->discordClientFactory->createUserClient($credentials->getToken()); + $discordClientAsUser = $this->discordClientFactory->createUserClient($accessToken->getToken()); $serverResponse = await($discordClientAsBot->get( Endpoint::bind(Endpoint::GUILD, $this->discordServerId) @@ -130,9 +128,8 @@ public function getUser($credentials, UserProviderInterface $userProvider): User $steamId = $steamConnection ? (int) $steamConnection->id : null; - try { - /** @var User $user */ - $user = $userProvider->loadUserByIdentifier($externalId); + $user = $this->userRepository->findOneByExternalId($externalId); + if ($user instanceof User) { $user->update( $fullUsername, $email, @@ -142,43 +139,47 @@ public function getUser($credentials, UserProviderInterface $userProvider): User $discordResourceOwner->getAvatarHash(), $steamId ); - } catch (UserNotFoundException $ex) { - $permissions = new UserPermissions(Uuid::uuid4()); - $user = new User( - Uuid::uuid4(), - $fullUsername, - $email, - $externalId, - $permissions, - [], - $discordResourceOwner->getAvatarHash(), - $steamId - ); - $this->em->persist($permissions); - $this->em->persist($user); + $this->em->flush(); + + return new SelfValidatingPassport(new UserBadge($fullUsername, fn () => $user)); } + $permissions = new UserPermissions(Uuid::uuid4()); + $user = new User( + Uuid::uuid4(), + $fullUsername, + $email, + $externalId, + $permissions, + [], + $discordResourceOwner->getAvatarHash(), + $steamId + ); + + $this->em->persist($permissions); + $this->em->persist($user); + $this->em->flush(); - return $user; + return new SelfValidatingPassport(new UserBadge($fullUsername, fn () => $user)); } - public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?RedirectResponse + public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response { - $targetUrl = $this->router->generate(self::HOME_JOIN_US_PAGE_ROUTE_NAME); + $targetUrl = $this->router->generate(self::HOME_INDEX_PAGE_ROUTE_NAME); return new RedirectResponse($targetUrl); } - public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $providerKey): ?RedirectResponse + public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response { - $targetUrl = $this->router->generate(self::HOME_INDEX_PAGE_ROUTE_NAME); + $targetUrl = $this->router->generate(self::HOME_JOIN_US_PAGE_ROUTE_NAME); return new RedirectResponse($targetUrl); } - protected function getRoleIdByName(array $roles, string $roleName): string + private function getRoleIdByName(array $roles, string $roleName): string { $rolesFound = (new ArrayCollection($roles))->filter(static fn (\stdClass $role) => $role->name === $roleName); @@ -188,9 +189,4 @@ protected function getRoleIdByName(array $roles, string $roleName): string default => throw new MultipleRolesFound(sprintf('Multiple roles found by given name "%s"!', $roleName)) }; } - - protected function getDiscordClient(): OAuth2ClientInterface - { - return $this->clientRegistry->getClient(self::DISCORD_CLIENT_NAME); - } }