From 72feac7900ba7994fbcde43de07ac4fb32a0778c Mon Sep 17 00:00:00 2001 From: Jesse Rushlow Date: Mon, 13 Dec 2021 06:41:42 -0500 Subject: [PATCH 1/5] WIP - add Symfony Uid Support --- .../ResetPasswordRequestRepositoryTrait.php | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/Persistence/Repository/ResetPasswordRequestRepositoryTrait.php b/src/Persistence/Repository/ResetPasswordRequestRepositoryTrait.php index 6f321c04..06222ca6 100644 --- a/src/Persistence/Repository/ResetPasswordRequestRepositoryTrait.php +++ b/src/Persistence/Repository/ResetPasswordRequestRepositoryTrait.php @@ -9,6 +9,9 @@ namespace SymfonyCasts\Bundle\ResetPassword\Persistence\Repository; +use Symfony\Component\Uid\AbstractUid; +use Symfony\Component\Uid\Ulid; +use Symfony\Component\Uid\Uuid; use SymfonyCasts\Bundle\ResetPassword\Model\ResetPasswordRequestInterface; /** @@ -41,11 +44,13 @@ public function findResetPasswordRequest(string $selector): ?ResetPasswordReques public function getMostRecentNonExpiredRequestDate(object $user): ?\DateTimeInterface { + $params = $this->getQueryParams($user); + // Normally there is only 1 max request per use, but written to be flexible /** @var ResetPasswordRequestInterface $resetPasswordRequest */ $resetPasswordRequest = $this->createQueryBuilder('t') ->where('t.user = :user') - ->setParameter('user', $user) + ->setParameter('user', $params['value'], $params['type']) ->orderBy('t.requestedAt', 'DESC') ->setMaxResults(1) ->getQuery() @@ -61,10 +66,12 @@ public function getMostRecentNonExpiredRequestDate(object $user): ?\DateTimeInte public function removeResetPasswordRequest(ResetPasswordRequestInterface $resetPasswordRequest): void { + $params = $this->getQueryParams($resetPasswordRequest->getUser()); + $this->createQueryBuilder('t') ->delete() ->where('t.user = :user') - ->setParameter('user', $resetPasswordRequest->getUser()) + ->setParameter('user', $params['value'], $params['type']) ->getQuery() ->execute() ; @@ -82,4 +89,24 @@ public function removeExpiredResetPasswordRequests(): int return $query->execute(); } + + private function getQueryParams(object $user): array + { + $paramValue = $user; + $paramType = null; + + if (method_exists($paramValue, 'getId') && class_exists(AbstractUid::class)) { + if ($paramValue->getId instanceof Uuid) { + $paramType = 'uuid'; + $paramValue = $paramValue->getId(); + } + + if ($paramValue->getId instanceof Ulid) { + $paramType = 'ulid'; + $paramValue = $paramValue->getId(); + } + } + + return ['value' => $paramValue, 'type' => $paramType]; + } } From de605e5e858ba32f4aaaacb272b7c5b565e3c45a Mon Sep 17 00:00:00 2001 From: Jesse Rushlow Date: Mon, 13 Dec 2021 10:48:22 -0500 Subject: [PATCH 2/5] test for problem --- composer.json | 3 +- .../ResetPasswordTestFixtureUuidRequest.php | 78 ++++++ ...ResetPasswordRequestRepositoryUuidTest.php | 222 ++++++++++++++++++ 3 files changed, 302 insertions(+), 1 deletion(-) create mode 100644 tests/Fixtures/Entity/ResetPasswordTestFixtureUuidRequest.php create mode 100644 tests/FunctionalTests/Persistence/ResetPasswordRequestRepositoryUuidTest.php diff --git a/composer.json b/composer.json index ba5761bc..8bf7c9ef 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,8 @@ "symfony/framework-bundle": "^4.4 | ^5.0 | ^6.0", "symfony/phpunit-bridge": "^5.0 | ^6.0", "vimeo/psalm": "^4.3", - "doctrine/doctrine-bundle": "^2.0.3" + "doctrine/doctrine-bundle": "^2.0.3", + "symfony/uid": "6.1.x-dev" }, "conflict": { "doctrine/orm": "<2.7", diff --git a/tests/Fixtures/Entity/ResetPasswordTestFixtureUuidRequest.php b/tests/Fixtures/Entity/ResetPasswordTestFixtureUuidRequest.php new file mode 100644 index 00000000..b926a465 --- /dev/null +++ b/tests/Fixtures/Entity/ResetPasswordTestFixtureUuidRequest.php @@ -0,0 +1,78 @@ + + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace SymfonyCasts\Bundle\ResetPassword\Tests\Fixtures\Entity; + +use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Uid\Uuid; +use SymfonyCasts\Bundle\ResetPassword\Model\ResetPasswordRequestInterface; + +/** + * @author Jesse Rushlow + * @author Ryan Weaver + * + * @internal + * @ORM\Entity(repositoryClass="SymfonyCasts\Bundle\ResetPassword\Tests\Fixtures\ResetPasswordTestFixtureRequestRepository") + */ +final class ResetPasswordTestFixtureUuidRequest implements ResetPasswordRequestInterface +{ + /** + * @ORM\Id() + * @ORM\Column(type="uuid", unique=true) + */ + public $id; + + /** + * @ORM\Column(type="string", nullable=true) + */ + public $selector; + + /** + * @ORM\Column(type="datetime_immutable", nullable=true) + */ + public $expiresAt; + + /** + * @ORM\Column(type="datetime_immutable", nullable=true) + */ + public $requestedAt; + + /** + * @ORM\ManyToOne(targetEntity="ResetPasswordTestFixtureUser") + */ + public $user; + + public function __construct() + { + $this->id = Uuid::v4(); + } + + public function getRequestedAt(): \DateTimeInterface + { + return $this->requestedAt; + } + + public function isExpired(): bool + { + return $this->expiresAt->getTimestamp() <= time(); + } + + public function getExpiresAt(): \DateTimeInterface + { + } + + public function getHashedToken(): string + { + } + + public function getUser(): object + { + return $this->user; + } +} diff --git a/tests/FunctionalTests/Persistence/ResetPasswordRequestRepositoryUuidTest.php b/tests/FunctionalTests/Persistence/ResetPasswordRequestRepositoryUuidTest.php new file mode 100644 index 00000000..97580c09 --- /dev/null +++ b/tests/FunctionalTests/Persistence/ResetPasswordRequestRepositoryUuidTest.php @@ -0,0 +1,222 @@ + + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace SymfonyCasts\Bundle\ResetPassword\Tests\FunctionalTests\Persistence; + +use Doctrine\Bundle\DoctrineBundle\Registry; +use Doctrine\ORM\Tools\SchemaTool; +use Doctrine\Persistence\ObjectManager; +use PHPUnit\Framework\TestCase; +use SymfonyCasts\Bundle\ResetPassword\Tests\Fixtures\Entity\ResetPasswordTestFixtureUser; +use SymfonyCasts\Bundle\ResetPassword\Tests\Fixtures\Entity\ResetPasswordTestFixtureUuidRequest; +use SymfonyCasts\Bundle\ResetPassword\Tests\Fixtures\ResetPasswordTestFixtureRequestRepository; +use SymfonyCasts\Bundle\ResetPassword\Tests\ResetPasswordTestKernel; + +/** + * @author Jesse Rushlow + * @author Ryan Weaver + * + * @internal + */ +final class ResetPasswordRequestRepositoryUuidTest extends TestCase +{ + /** + * @var ObjectManager|object + */ + private $manager; + + /** + * @var ResetPasswordTestFixtureRequestRepository + */ + private $repository; + + /** + * {@inheritdoc} + */ + protected function setUp(): void + { + $kernel = new ResetPasswordTestKernel(); + $kernel->boot(); + + $container = $kernel->getContainer(); + + /** @var Registry $registry */ + $registry = $container->get('doctrine'); + $this->manager = $registry->getManager(); + + $this->configureDatabase(); + + $this->repository = $this->manager->getRepository(ResetPasswordTestFixtureUuidRequest::class); + } + + public function testPersistResetPasswordRequestPersistsRequestObject(): void + { + $fixture = new ResetPasswordTestFixtureUuidRequest(); + + $this->repository->persistResetPasswordRequest($fixture); + + $result = $this->repository->findAll(); + + self::assertSame($fixture, $result[0]); + } + + public function testGetUserIdentifierRetrievesObjectIdFromPersistence(): void + { + $fixture = new ResetPasswordTestFixtureUuidRequest(); + + $this->manager->persist($fixture); + $this->manager->flush(); + + $result = $this->repository->getUserIdentifier($fixture); + + self::assertSame('1', $result); + } + + public function testFindResetPasswordRequestReturnsObjectWithGivenSelector(): void + { + $fixture = new ResetPasswordTestFixtureUuidRequest(); + $fixture->selector = '1234'; + + $this->manager->persist($fixture); + $this->manager->flush(); + + $result = $this->repository->findResetPasswordRequest('1234'); + + self::assertSame($fixture, $result); + } + + public function testGetMostRecentNonExpiredRequestDateReturnsExpected(): void + { + $userFixture = new ResetPasswordTestFixtureUser(); + + $this->manager->persist($userFixture); + + $fixtureOld = new ResetPasswordTestFixtureUuidRequest(); + $fixtureOld->requestedAt = new \DateTimeImmutable('-5 minutes'); + $fixtureOld->user = $userFixture; + + $this->manager->persist($fixtureOld); + + $expectedTime = new \DateTimeImmutable(); + + $fixtureNewest = new ResetPasswordTestFixtureUuidRequest(); + $fixtureNewest->expiresAt = new \DateTimeImmutable('+1 hours'); + $fixtureNewest->requestedAt = $expectedTime; + $fixtureNewest->user = $userFixture; + + $this->manager->persist($fixtureNewest); + $this->manager->flush(); + + $result = $this->repository->getMostRecentNonExpiredRequestDate($userFixture); + + self::assertSame($expectedTime, $result); + } + + public function testGetMostRecentNonExpiredRequestDateReturnsNullOnExpiredRequest(): void + { + $userFixture = new ResetPasswordTestFixtureUser(); + + $this->manager->persist($userFixture); + + $expiredFixture = new ResetPasswordTestFixtureUuidRequest(); + $expiredFixture->user = $userFixture; + $expiredFixture->expiresAt = new \DateTimeImmutable('-1 hours'); + $expiredFixture->requestedAt = new \DateTimeImmutable('-2 hours'); + + $this->manager->persist($expiredFixture); + $this->manager->flush(); + + $result = $this->repository->getMostRecentNonExpiredRequestDate($userFixture); + + self::assertNull($result); + } + + public function testGetMostRecentNonExpiredRequestDateReturnsNullIfRequestNotFound(): void + { + $userFixture = new ResetPasswordTestFixtureUser(); + + $this->manager->persist($userFixture); + $this->manager->persist(new ResetPasswordTestFixtureUuidRequest()); + $this->manager->flush(); + + $result = $this->repository->getMostRecentNonExpiredRequestDate($userFixture); + + self::assertNull($result); + } + + public function testRemoveResetPasswordRequestRemovedGivenObjectFromPersistence(): void + { + $userFixture = new ResetPasswordTestFixtureUser(); + $requestFixture = new ResetPasswordTestFixtureUuidRequest(); + $requestFixture->user = $userFixture; + + $this->manager->persist($requestFixture); + $this->manager->persist($userFixture); + $this->manager->flush(); + + $this->repository->removeResetPasswordRequest($requestFixture); + + $this->assertCount(0, $this->repository->findAll()); + } + + public function testRemoveResetPasswordRequestRemovesAllRequestsForUser(): void + { + $userFixtureA = new ResetPasswordTestFixtureUser(); + $userFixtureB = new ResetPasswordTestFixtureUser(); + $requestFixtureA = new ResetPasswordTestFixtureUuidRequest(); + $requestFixtureA->user = $userFixtureA; + $requestFixtureB = new ResetPasswordTestFixtureUuidRequest(); + $requestFixtureB->user = $userFixtureA; + $requestFixtureC = new ResetPasswordTestFixtureUuidRequest(); + $requestFixtureC->user = $userFixtureB; + + $this->manager->persist($requestFixtureA); + $this->manager->persist($requestFixtureB); + $this->manager->persist($requestFixtureC); + $this->manager->persist($userFixtureA); + $this->manager->persist($userFixtureB); + $this->manager->flush(); + + $this->repository->removeResetPasswordRequest($requestFixtureB); + + $requests = $this->repository->findAll(); + + $this->assertCount(1, $requests); + $this->assertSame($requestFixtureC->id, $requests[0]->id); + } + + public function testRemovedExpiredResetPasswordRequestsOnlyRemovedExpiredRequestsFromPersistence(): void + { + $expiredFixture = new ResetPasswordTestFixtureUuidRequest(); + $expiredFixture->expiresAt = new \DateTimeImmutable('-2 weeks'); + + $this->manager->persist($expiredFixture); + + $futureFixture = new ResetPasswordTestFixtureUuidRequest(); + + $this->manager->persist($futureFixture); + $this->manager->flush(); + + $this->repository->removeExpiredResetPasswordRequests(); + + $result = $this->repository->findAll(); + + self::assertCount(1, $result); + self::assertSame($futureFixture, $result[0]); + } + + private function configureDatabase(): void + { + $metaData = $this->manager->getMetadataFactory(); + + $tool = new SchemaTool($this->manager); + $tool->dropDatabase(); + $tool->createSchema($metaData->getAllMetadata()); + } +} From 96c37632deb6f13b023b29b9e5abf3cff4fe104a Mon Sep 17 00:00:00 2001 From: Jesse Rushlow Date: Mon, 13 Dec 2021 10:51:40 -0500 Subject: [PATCH 3/5] fix uid version requirements --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 8bf7c9ef..379f9231 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ "symfony/phpunit-bridge": "^5.0 | ^6.0", "vimeo/psalm": "^4.3", "doctrine/doctrine-bundle": "^2.0.3", - "symfony/uid": "6.1.x-dev" + "symfony/uid": "^v5.1 | ^6.0" }, "conflict": { "doctrine/orm": "<2.7", From 01fa56e97e39ce12cb8fff7aed3eb16957247379 Mon Sep 17 00:00:00 2001 From: Jesse Rushlow Date: Mon, 13 Dec 2021 11:19:10 -0500 Subject: [PATCH 4/5] try to fail ci --- .../ResetPasswordTestFixtureUuidRequest.php | 4 +- ...sswordTestFixtureRequestUuidRepository.php | 43 +++++++++++++++++++ ...ResetPasswordRequestRepositoryUuidTest.php | 12 ++++-- tests/ResetPasswordTestKernel.php | 7 +++ 4 files changed, 60 insertions(+), 6 deletions(-) create mode 100644 tests/Fixtures/ResetPasswordTestFixtureRequestUuidRepository.php diff --git a/tests/Fixtures/Entity/ResetPasswordTestFixtureUuidRequest.php b/tests/Fixtures/Entity/ResetPasswordTestFixtureUuidRequest.php index b926a465..15b72262 100644 --- a/tests/Fixtures/Entity/ResetPasswordTestFixtureUuidRequest.php +++ b/tests/Fixtures/Entity/ResetPasswordTestFixtureUuidRequest.php @@ -18,13 +18,13 @@ * @author Ryan Weaver * * @internal - * @ORM\Entity(repositoryClass="SymfonyCasts\Bundle\ResetPassword\Tests\Fixtures\ResetPasswordTestFixtureRequestRepository") + * @ORM\Entity(repositoryClass="SymfonyCasts\Bundle\ResetPassword\Tests\Fixtures\ResetPasswordTestFixtureRequestUuidRepository") */ final class ResetPasswordTestFixtureUuidRequest implements ResetPasswordRequestInterface { /** * @ORM\Id() - * @ORM\Column(type="uuid", unique=true) + * @ORM\Column(type="uuid") */ public $id; diff --git a/tests/Fixtures/ResetPasswordTestFixtureRequestUuidRepository.php b/tests/Fixtures/ResetPasswordTestFixtureRequestUuidRepository.php new file mode 100644 index 00000000..fce81e1c --- /dev/null +++ b/tests/Fixtures/ResetPasswordTestFixtureRequestUuidRepository.php @@ -0,0 +1,43 @@ + + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace SymfonyCasts\Bundle\ResetPassword\Tests\Fixtures; + +use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; +use Doctrine\Persistence\ManagerRegistry; +use SymfonyCasts\Bundle\ResetPassword\Model\ResetPasswordRequestInterface; +use SymfonyCasts\Bundle\ResetPassword\Persistence\Repository\ResetPasswordRequestRepositoryTrait; +use SymfonyCasts\Bundle\ResetPassword\Persistence\ResetPasswordRequestRepositoryInterface; +use SymfonyCasts\Bundle\ResetPassword\Tests\Fixtures\Entity\ResetPasswordTestFixtureUuidRequest; + +/** + * @author Jesse Rushlow + * @author Ryan Weaver + * + * @internal + */ +final class ResetPasswordTestFixtureRequestUuidRepository extends ServiceEntityRepository implements ResetPasswordRequestRepositoryInterface +{ + use ResetPasswordRequestRepositoryTrait; + + private $manager; + + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, ResetPasswordTestFixtureUuidRequest::class); + } + + public function createResetPasswordRequest( + object $user, + \DateTimeInterface $expiresAt, + string $selector, + string $hashedToken + ): ResetPasswordRequestInterface { + } +} diff --git a/tests/FunctionalTests/Persistence/ResetPasswordRequestRepositoryUuidTest.php b/tests/FunctionalTests/Persistence/ResetPasswordRequestRepositoryUuidTest.php index 97580c09..0835afb2 100644 --- a/tests/FunctionalTests/Persistence/ResetPasswordRequestRepositoryUuidTest.php +++ b/tests/FunctionalTests/Persistence/ResetPasswordRequestRepositoryUuidTest.php @@ -13,9 +13,10 @@ use Doctrine\ORM\Tools\SchemaTool; use Doctrine\Persistence\ObjectManager; use PHPUnit\Framework\TestCase; -use SymfonyCasts\Bundle\ResetPassword\Tests\Fixtures\Entity\ResetPasswordTestFixtureUser; +use Symfony\Component\Uid\Uuid; use SymfonyCasts\Bundle\ResetPassword\Tests\Fixtures\Entity\ResetPasswordTestFixtureUuidRequest; -use SymfonyCasts\Bundle\ResetPassword\Tests\Fixtures\ResetPasswordTestFixtureRequestRepository; +use SymfonyCasts\Bundle\ResetPassword\Tests\Fixtures\Entity\ResetPasswordTestFixtureUser; +use SymfonyCasts\Bundle\ResetPassword\Tests\Fixtures\ResetPasswordTestFixtureRequestUuidRepository; use SymfonyCasts\Bundle\ResetPassword\Tests\ResetPasswordTestKernel; /** @@ -32,7 +33,7 @@ final class ResetPasswordRequestRepositoryUuidTest extends TestCase private $manager; /** - * @var ResetPasswordTestFixtureRequestRepository + * @var ResetPasswordTestFixtureRequestUuidRepository */ private $repository; @@ -75,7 +76,10 @@ public function testGetUserIdentifierRetrievesObjectIdFromPersistence(): void $result = $this->repository->getUserIdentifier($fixture); - self::assertSame('1', $result); + $result = Uuid::fromString($result); + + self::assertInstanceOf(Uuid::class, $result); +// self::assertSame('1', $result); } public function testFindResetPasswordRequestReturnsObjectWithGivenSelector(): void diff --git a/tests/ResetPasswordTestKernel.php b/tests/ResetPasswordTestKernel.php index 38fdf85c..92aa5259 100644 --- a/tests/ResetPasswordTestKernel.php +++ b/tests/ResetPasswordTestKernel.php @@ -19,6 +19,7 @@ use Symfony\Component\Routing\RouteCollection; use SymfonyCasts\Bundle\ResetPassword\SymfonyCastsResetPasswordBundle; use SymfonyCasts\Bundle\ResetPassword\Tests\Fixtures\ResetPasswordTestFixtureRequestRepository; +use SymfonyCasts\Bundle\ResetPassword\Tests\Fixtures\ResetPasswordTestFixtureRequestUuidRepository; /** * @author Jesse Rushlow @@ -104,6 +105,12 @@ public function registerContainerConfiguration(LoaderInterface $loader): void ->setAutowired(true) ; + $container->register(ResetPasswordTestFixtureRequestUuidRepository::class) + ->setAutoconfigured(true) + ->setAutowired(true) + ; + + $container->loadFromExtension('symfonycasts_reset_password', [ 'request_password_repository' => ResetPasswordTestFixtureRequestRepository::class, ]); From b5136e9e271d547aa505ae4b75f999080f1f4895 Mon Sep 17 00:00:00 2001 From: Jesse Rushlow Date: Mon, 13 Dec 2021 11:20:59 -0500 Subject: [PATCH 5/5] tmp stop type juggling --- .../Repository/ResetPasswordRequestRepositoryTrait.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Persistence/Repository/ResetPasswordRequestRepositoryTrait.php b/src/Persistence/Repository/ResetPasswordRequestRepositoryTrait.php index 06222ca6..905e2bb1 100644 --- a/src/Persistence/Repository/ResetPasswordRequestRepositoryTrait.php +++ b/src/Persistence/Repository/ResetPasswordRequestRepositoryTrait.php @@ -50,7 +50,7 @@ public function getMostRecentNonExpiredRequestDate(object $user): ?\DateTimeInte /** @var ResetPasswordRequestInterface $resetPasswordRequest */ $resetPasswordRequest = $this->createQueryBuilder('t') ->where('t.user = :user') - ->setParameter('user', $params['value'], $params['type']) + ->setParameter('user', $user) ->orderBy('t.requestedAt', 'DESC') ->setMaxResults(1) ->getQuery() @@ -71,7 +71,7 @@ public function removeResetPasswordRequest(ResetPasswordRequestInterface $resetP $this->createQueryBuilder('t') ->delete() ->where('t.user = :user') - ->setParameter('user', $params['value'], $params['type']) + ->setParameter('user', $resetPasswordRequest->getUser()) ->getQuery() ->execute() ;