diff --git a/src/Surfnet/Stepup/Identity/Identity.php b/src/Surfnet/Stepup/Identity/Identity.php index f84a0baed..4a16fb80c 100644 --- a/src/Surfnet/Stepup/Identity/Identity.php +++ b/src/Surfnet/Stepup/Identity/Identity.php @@ -1003,8 +1003,6 @@ public function expressPreferredLocale(Locale $preferredLocale): void public function forget(): void { - $this->assertNotForgotten(); - if ($this->registrationAuthorities->count() !== 0) { throw new DomainException('Cannot forget an identity that is currently accredited as an RA(A)'); } diff --git a/src/Surfnet/Stepup/Projector/Projector.php b/src/Surfnet/Stepup/Projector/Projector.php new file mode 100644 index 000000000..96d5efa63 --- /dev/null +++ b/src/Surfnet/Stepup/Projector/Projector.php @@ -0,0 +1,31 @@ +allowedSecondFactorRepository->save($allowedSecondFactor); } } + + protected function applyIdentityForgottenEvent(IdentityForgottenEvent $event): void + { + // do nothing, no sensitive data in this projection + } } diff --git a/src/Surfnet/StepupMiddleware/ApiBundle/Configuration/Projector/ConfiguredInstitutionProjector.php b/src/Surfnet/StepupMiddleware/ApiBundle/Configuration/Projector/ConfiguredInstitutionProjector.php index 554863814..0f9db03b0 100644 --- a/src/Surfnet/StepupMiddleware/ApiBundle/Configuration/Projector/ConfiguredInstitutionProjector.php +++ b/src/Surfnet/StepupMiddleware/ApiBundle/Configuration/Projector/ConfiguredInstitutionProjector.php @@ -18,7 +18,8 @@ namespace Surfnet\StepupMiddleware\ApiBundle\Configuration\Projector; -use Broadway\ReadModel\Projector; +use Surfnet\Stepup\Identity\Event\IdentityForgottenEvent; +use Surfnet\Stepup\Projector\Projector; use Surfnet\Stepup\Configuration\Event\InstitutionConfigurationRemovedEvent; use Surfnet\Stepup\Configuration\Event\NewInstitutionConfigurationCreatedEvent; use Surfnet\StepupMiddleware\ApiBundle\Configuration\Entity\ConfiguredInstitution; @@ -40,4 +41,9 @@ public function applyInstitutionConfigurationRemovedEvent(InstitutionConfigurati { $this->configuredInstitutionRepository->removeConfigurationFor($event->institution); } + + protected function applyIdentityForgottenEvent(IdentityForgottenEvent $event): void + { + // do nothing, no sensitive data in this projection + } } diff --git a/src/Surfnet/StepupMiddleware/ApiBundle/Configuration/Projector/InstitutionAuthorizationProjector.php b/src/Surfnet/StepupMiddleware/ApiBundle/Configuration/Projector/InstitutionAuthorizationProjector.php index a13db3a70..b439ebcc9 100644 --- a/src/Surfnet/StepupMiddleware/ApiBundle/Configuration/Projector/InstitutionAuthorizationProjector.php +++ b/src/Surfnet/StepupMiddleware/ApiBundle/Configuration/Projector/InstitutionAuthorizationProjector.php @@ -18,7 +18,8 @@ namespace Surfnet\StepupMiddleware\ApiBundle\Configuration\Projector; -use Broadway\ReadModel\Projector; +use Surfnet\Stepup\Identity\Event\IdentityForgottenEvent; +use Surfnet\Stepup\Projector\Projector; use Surfnet\Stepup\Configuration\Event\InstitutionConfigurationRemovedEvent; use Surfnet\Stepup\Configuration\Event\NewInstitutionConfigurationCreatedEvent; use Surfnet\Stepup\Configuration\Event\SelectRaaOptionChangedEvent; @@ -72,4 +73,9 @@ public function applyInstitutionConfigurationRemovedEvent(InstitutionConfigurati $event->institution, ); } + + protected function applyIdentityForgottenEvent(IdentityForgottenEvent $event): void + { + // do nothing, no sensitive data in this projection + } } diff --git a/src/Surfnet/StepupMiddleware/ApiBundle/Configuration/Projector/InstitutionConfigurationOptionsProjector.php b/src/Surfnet/StepupMiddleware/ApiBundle/Configuration/Projector/InstitutionConfigurationOptionsProjector.php index 9f22e2dc2..7aadab6d0 100644 --- a/src/Surfnet/StepupMiddleware/ApiBundle/Configuration/Projector/InstitutionConfigurationOptionsProjector.php +++ b/src/Surfnet/StepupMiddleware/ApiBundle/Configuration/Projector/InstitutionConfigurationOptionsProjector.php @@ -18,7 +18,8 @@ namespace Surfnet\StepupMiddleware\ApiBundle\Configuration\Projector; -use Broadway\ReadModel\Projector; +use Surfnet\Stepup\Identity\Event\IdentityForgottenEvent; +use Surfnet\Stepup\Projector\Projector; use Surfnet\Stepup\Configuration\Event\InstitutionConfigurationRemovedEvent; use Surfnet\Stepup\Configuration\Event\NewInstitutionConfigurationCreatedEvent; use Surfnet\Stepup\Configuration\Event\NumberOfTokensPerIdentityOptionChangedEvent; @@ -32,6 +33,9 @@ use Surfnet\StepupMiddleware\ApiBundle\Configuration\Repository\AllowedSecondFactorRepository; use Surfnet\StepupMiddleware\ApiBundle\Configuration\Repository\InstitutionConfigurationOptionsRepository; +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ final class InstitutionConfigurationOptionsProjector extends Projector { public function __construct( @@ -131,4 +135,9 @@ public function applyInstitutionConfigurationRemovedEvent(InstitutionConfigurati $this->institutionConfigurationOptionsRepository->removeConfigurationOptionsFor($event->institution); $this->allowedSecondFactorRepository->clearAllowedSecondFactorListFor($event->institution); } + + protected function applyIdentityForgottenEvent(IdentityForgottenEvent $event): void + { + // do nothing, no sensitive data in this projection + } } diff --git a/src/Surfnet/StepupMiddleware/ApiBundle/Configuration/Projector/RaLocationProjector.php b/src/Surfnet/StepupMiddleware/ApiBundle/Configuration/Projector/RaLocationProjector.php index 183ec7946..1c32c99d3 100644 --- a/src/Surfnet/StepupMiddleware/ApiBundle/Configuration/Projector/RaLocationProjector.php +++ b/src/Surfnet/StepupMiddleware/ApiBundle/Configuration/Projector/RaLocationProjector.php @@ -18,7 +18,8 @@ namespace Surfnet\StepupMiddleware\ApiBundle\Configuration\Projector; -use Broadway\ReadModel\Projector; +use Surfnet\Stepup\Identity\Event\IdentityForgottenEvent; +use Surfnet\Stepup\Projector\Projector; use Surfnet\Stepup\Configuration\Event\InstitutionConfigurationRemovedEvent; use Surfnet\Stepup\Configuration\Event\RaLocationAddedEvent; use Surfnet\Stepup\Configuration\Event\RaLocationContactInformationChangedEvent; @@ -100,4 +101,9 @@ private function fetchRaLocationById(RaLocationId $raLocationId): RaLocation return $raLocation; } + + protected function applyIdentityForgottenEvent(IdentityForgottenEvent $event): void + { + // do nothing, no sensitive data in this projection + } } diff --git a/src/Surfnet/StepupMiddleware/ApiBundle/Controller/DeprovisionController.php b/src/Surfnet/StepupMiddleware/ApiBundle/Controller/DeprovisionController.php index 08db563fe..22dc3b71c 100644 --- a/src/Surfnet/StepupMiddleware/ApiBundle/Controller/DeprovisionController.php +++ b/src/Surfnet/StepupMiddleware/ApiBundle/Controller/DeprovisionController.php @@ -22,7 +22,6 @@ use Surfnet\Stepup\Exception\DomainException; use Surfnet\Stepup\Helper\UserDataFormatterInterface; use Surfnet\StepupMiddleware\ApiBundle\Service\DeprovisionServiceInterface; -use Surfnet\StepupMiddleware\ApiBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; class DeprovisionController extends AbstractController @@ -42,11 +41,12 @@ public function deprovision(string $collabPersonId): JsonResponse if ($userData !== []) { $this->deprovisionService->deprovision($collabPersonId); } - } catch (DomainException) { + } catch (DomainException $e) { // On domain exceptions, like when the identity is forgotten, we return OK, with empty data // just so the deprovision run does not end prematurely. At this point, no other domain exceptions // are thrown. $userData = []; + $errors = [$e->getMessage()]; } catch (Exception $e) { $userData = []; $errors = [$e->getMessage()]; @@ -57,8 +57,11 @@ public function deprovision(string $collabPersonId): JsonResponse public function dryRun(string $collabPersonId): JsonResponse { $this->denyAccessUnlessGrantedOneOff(['ROLE_DEPROVISION']); + $errors = []; try { + $this->deprovisionService->assertIsAllowed($collabPersonId); + $userData = $this->deprovisionService->readUserData($collabPersonId); } catch (Exception $e) { $userData = []; diff --git a/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Entity/AuditLogEntry.php b/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Entity/AuditLogEntry.php index cbd110731..e057891d4 100644 --- a/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Entity/AuditLogEntry.php +++ b/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Entity/AuditLogEntry.php @@ -153,7 +153,7 @@ class AuditLogEntry implements JsonSerializable public ?string $secondFactorType = null; #[ORM\Column(length: 255, nullable: true)] - public ?string $recoveryTokenIdentifier; + public ?string $recoveryTokenIdentifier = null; #[ORM\Column(length: 36, nullable: true)] public ?string $recoveryTokenType = null; diff --git a/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Projector/AuditLogProjector.php b/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Projector/AuditLogProjector.php index e22f7c590..19e3d278a 100644 --- a/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Projector/AuditLogProjector.php +++ b/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Projector/AuditLogProjector.php @@ -19,7 +19,6 @@ namespace Surfnet\StepupMiddleware\ApiBundle\Identity\Projector; use Broadway\Domain\DomainMessage; -use Broadway\EventHandling\EventListener; use DateTime as CoreDateTime; use Ramsey\Uuid\Uuid; use Surfnet\Stepup\DateTime\DateTime; @@ -33,6 +32,7 @@ use Surfnet\Stepup\Identity\Value\RecoveryTokenIdentifierFactory; use Surfnet\Stepup\Identity\Value\RecoveryTokenType; use Surfnet\Stepup\Identity\Value\VettingType; +use Surfnet\Stepup\Projector\Projector; use Surfnet\StepupMiddleware\ApiBundle\Exception\RuntimeException; use Surfnet\StepupMiddleware\ApiBundle\Identity\Entity\AuditLogEntry; use Surfnet\StepupMiddleware\ApiBundle\Identity\Entity\Identity; @@ -42,7 +42,7 @@ /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class AuditLogProjector implements EventListener +class AuditLogProjector extends Projector { public function __construct( private readonly AuditLogRepository $auditLogRepository, @@ -138,7 +138,7 @@ private function applyAuditableEvent(AuditableEvent $event, DomainMessage $domai $this->auditLogRepository->save($entry); } - private function applyIdentityForgottenEvent(IdentityForgottenEvent $event): void + protected function applyIdentityForgottenEvent(IdentityForgottenEvent $event): void { $entries = $this->auditLogRepository->findByIdentityId($event->identityId); foreach ($entries as $auditLogEntry) { diff --git a/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Projector/IdentityProjector.php b/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Projector/IdentityProjector.php index a6574ab84..a6858698d 100644 --- a/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Projector/IdentityProjector.php +++ b/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Projector/IdentityProjector.php @@ -18,9 +18,10 @@ namespace Surfnet\StepupMiddleware\ApiBundle\Identity\Projector; -use Broadway\ReadModel\Projector; +use Surfnet\Stepup\Projector\Projector; use Surfnet\Stepup\Identity\Event\IdentityCreatedEvent; use Surfnet\Stepup\Identity\Event\IdentityEmailChangedEvent; +use Surfnet\Stepup\Identity\Event\IdentityForgottenEvent; use Surfnet\Stepup\Identity\Event\IdentityRenamedEvent; use Surfnet\Stepup\Identity\Event\LocalePreferenceExpressedEvent; use Surfnet\Stepup\Identity\Event\SecondFactorVettedEvent; @@ -95,4 +96,9 @@ private function determinePossessionOfSelfAssertedToken(VettingType $vettingType } } } + + protected function applyIdentityForgottenEvent(IdentityForgottenEvent $event): void + { + $this->identityRepository->updateStatusByIdentityIdToForgotten($event->identityId); + } } diff --git a/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Projector/IdentitySelfAssertedTokenOptionsProjector.php b/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Projector/IdentitySelfAssertedTokenOptionsProjector.php index 695ec0b37..d08af17a2 100644 --- a/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Projector/IdentitySelfAssertedTokenOptionsProjector.php +++ b/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Projector/IdentitySelfAssertedTokenOptionsProjector.php @@ -18,7 +18,8 @@ namespace Surfnet\StepupMiddleware\ApiBundle\Identity\Projector; -use Broadway\ReadModel\Projector; +use Surfnet\Stepup\Identity\Event\IdentityForgottenEvent; +use Surfnet\Stepup\Projector\Projector; use Surfnet\Stepup\Identity\Event\IdentityCreatedEvent; use Surfnet\Stepup\Identity\Event\SecondFactorVettedEvent; use Surfnet\Stepup\Identity\Event\SecondFactorVettedWithoutTokenProofOfPossession; @@ -81,4 +82,9 @@ private function determinePossessionOfToken(VettingType $vettingType, IdentityId $identitySelfAssertedTokenOptions->possessedToken = true; $this->repository->save($identitySelfAssertedTokenOptions); } + + protected function applyIdentityForgottenEvent(IdentityForgottenEvent $event): void + { + // do nothing, no sensitive data in this projection + } } diff --git a/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Projector/InstitutionListingProjector.php b/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Projector/InstitutionListingProjector.php index 797a7036c..925983bfb 100644 --- a/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Projector/InstitutionListingProjector.php +++ b/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Projector/InstitutionListingProjector.php @@ -18,7 +18,8 @@ namespace Surfnet\StepupMiddleware\ApiBundle\Identity\Projector; -use Broadway\ReadModel\Projector; +use Surfnet\Stepup\Identity\Event\IdentityForgottenEvent; +use Surfnet\Stepup\Projector\Projector; use Surfnet\Stepup\Identity\Event\IdentityCreatedEvent; use Surfnet\StepupMiddleware\ApiBundle\Identity\Repository\InstitutionListingRepository; @@ -36,4 +37,9 @@ public function applyIdentityCreatedEvent(IdentityCreatedEvent $event): void { $this->institutionListingRepository->addIfNotExists($event->identityInstitution); } + + protected function applyIdentityForgottenEvent(IdentityForgottenEvent $event): void + { + // do nothing, no sensitive data in this projection + } } diff --git a/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Projector/RaListingProjector.php b/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Projector/RaListingProjector.php index 0fe328319..287673e21 100644 --- a/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Projector/RaListingProjector.php +++ b/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Projector/RaListingProjector.php @@ -18,7 +18,7 @@ namespace Surfnet\StepupMiddleware\ApiBundle\Identity\Projector; -use Broadway\ReadModel\Projector; +use Surfnet\Stepup\Projector\Projector; use Surfnet\Stepup\Identity\Event\AppointedAsRaaEvent; use Surfnet\Stepup\Identity\Event\AppointedAsRaaForInstitutionEvent; use Surfnet\Stepup\Identity\Event\AppointedAsRaEvent; diff --git a/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Projector/RaSecondFactorProjector.php b/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Projector/RaSecondFactorProjector.php index a9921fd6f..c25c8f6ea 100644 --- a/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Projector/RaSecondFactorProjector.php +++ b/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Projector/RaSecondFactorProjector.php @@ -18,7 +18,7 @@ namespace Surfnet\StepupMiddleware\ApiBundle\Identity\Projector; -use Broadway\ReadModel\Projector; +use Surfnet\Stepup\Projector\Projector; use Surfnet\Stepup\Identity\Event\CompliedWithUnverifiedSecondFactorRevocationEvent; use Surfnet\Stepup\Identity\Event\CompliedWithVerifiedSecondFactorRevocationEvent; use Surfnet\Stepup\Identity\Event\CompliedWithVettedSecondFactorRevocationEvent; diff --git a/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Projector/RecoveryTokenProjector.php b/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Projector/RecoveryTokenProjector.php index 901eca786..13206deed 100644 --- a/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Projector/RecoveryTokenProjector.php +++ b/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Projector/RecoveryTokenProjector.php @@ -18,7 +18,7 @@ namespace Surfnet\StepupMiddleware\ApiBundle\Identity\Projector; -use Broadway\ReadModel\Projector; +use Surfnet\Stepup\Projector\Projector; use Surfnet\Stepup\Identity\Event\CompliedWithRecoveryCodeRevocationEvent; use Surfnet\Stepup\Identity\Event\IdentityForgottenEvent; use Surfnet\Stepup\Identity\Event\PhoneRecoveryTokenPossessionProvenEvent; diff --git a/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Projector/SecondFactorProjector.php b/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Projector/SecondFactorProjector.php index 093ed64be..c9307d862 100644 --- a/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Projector/SecondFactorProjector.php +++ b/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Projector/SecondFactorProjector.php @@ -18,7 +18,7 @@ namespace Surfnet\StepupMiddleware\ApiBundle\Identity\Projector; -use Broadway\ReadModel\Projector; +use Surfnet\Stepup\Projector\Projector; use Surfnet\Stepup\Identity\Event\CompliedWithUnverifiedSecondFactorRevocationEvent; use Surfnet\Stepup\Identity\Event\CompliedWithVerifiedSecondFactorRevocationEvent; use Surfnet\Stepup\Identity\Event\CompliedWithVettedSecondFactorRevocationEvent; diff --git a/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Projector/SecondFactorRevocationProjector.php b/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Projector/SecondFactorRevocationProjector.php index e5e3047d2..58f543bc5 100644 --- a/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Projector/SecondFactorRevocationProjector.php +++ b/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Projector/SecondFactorRevocationProjector.php @@ -19,7 +19,8 @@ namespace Surfnet\StepupMiddleware\ApiBundle\Identity\Projector; use Broadway\Domain\DomainMessage; -use Broadway\ReadModel\Projector; +use Surfnet\Stepup\Identity\Event\IdentityForgottenEvent; +use Surfnet\Stepup\Projector\Projector; use DateTime as CoreDateTime; use Ramsey\Uuid\Uuid; use Surfnet\Stepup\DateTime\DateTime; @@ -61,4 +62,9 @@ protected function applyCompliedWithVettedSecondFactorRevocationEvent( $this->repository->save($revocation); } + + protected function applyIdentityForgottenEvent(IdentityForgottenEvent $event): void + { + // do nothing, no sensitive data in this projection + } } diff --git a/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Projector/SraaProjector.php b/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Projector/SraaProjector.php index e4e40341c..31402a060 100644 --- a/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Projector/SraaProjector.php +++ b/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Projector/SraaProjector.php @@ -18,7 +18,8 @@ namespace Surfnet\StepupMiddleware\ApiBundle\Identity\Projector; -use Broadway\ReadModel\Projector; +use Surfnet\Stepup\Identity\Event\IdentityForgottenEvent; +use Surfnet\Stepup\Projector\Projector; use Surfnet\Stepup\Configuration\Event\SraaUpdatedEvent; use Surfnet\Stepup\Identity\Value\NameId; use Surfnet\StepupMiddleware\ApiBundle\Identity\Entity\Sraa; @@ -41,4 +42,9 @@ public function applySraaUpdatedEvent(SraaUpdatedEvent $event): void $this->sraaRepository->saveAll($sraaList); } + + protected function applyIdentityForgottenEvent(IdentityForgottenEvent $event): void + { + // do nothing, no sensitive data in this projection + } } diff --git a/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Projector/VettingTypeHintProjector.php b/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Projector/VettingTypeHintProjector.php index 1c184f337..d3d8e8cfd 100644 --- a/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Projector/VettingTypeHintProjector.php +++ b/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Projector/VettingTypeHintProjector.php @@ -18,7 +18,8 @@ namespace Surfnet\StepupMiddleware\ApiBundle\Identity\Projector; -use Broadway\ReadModel\Projector; +use Surfnet\Stepup\Identity\Event\IdentityForgottenEvent; +use Surfnet\Stepup\Projector\Projector; use Surfnet\Stepup\Identity\Event\VettingTypeHintsSavedEvent; use Surfnet\StepupMiddleware\ApiBundle\Identity\Entity\VettingTypeHint; use Surfnet\StepupMiddleware\ApiBundle\Identity\Repository\VettingTypeHintRepository; @@ -45,4 +46,9 @@ public function applyVettingTypeHintsSavedEvent(VettingTypeHintsSavedEvent $even $entity->hints = $event->hints; $this->vettingTypeHintRepository->save($entity); } + + protected function applyIdentityForgottenEvent(IdentityForgottenEvent $event): void + { + // do nothing, no sensitive data in this projection + } } diff --git a/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Projector/WhitelistProjector.php b/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Projector/WhitelistProjector.php index e08240a44..74ae3b35a 100644 --- a/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Projector/WhitelistProjector.php +++ b/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Projector/WhitelistProjector.php @@ -18,7 +18,8 @@ namespace Surfnet\StepupMiddleware\ApiBundle\Identity\Projector; -use Broadway\ReadModel\Projector; +use Surfnet\Stepup\Identity\Event\IdentityForgottenEvent; +use Surfnet\Stepup\Projector\Projector; use Surfnet\Stepup\Identity\Event\InstitutionsAddedToWhitelistEvent; use Surfnet\Stepup\Identity\Event\InstitutionsRemovedFromWhitelistEvent; use Surfnet\Stepup\Identity\Event\WhitelistCreatedEvent; @@ -75,4 +76,9 @@ protected function applyInstitutionsRemovedFromWhitelistEvent(InstitutionsRemove $this->whitelistEntryRepository->remove($whitelistEntries); } + + protected function applyIdentityForgottenEvent(IdentityForgottenEvent $event): void + { + // do nothing, no sensitive data in this projection + } } diff --git a/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Repository/IdentityRepository.php b/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Repository/IdentityRepository.php index 16e84b468..eed634b59 100644 --- a/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Repository/IdentityRepository.php +++ b/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Repository/IdentityRepository.php @@ -22,6 +22,9 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\Query; use Doctrine\Persistence\ManagerRegistry; +use Surfnet\Stepup\Identity\Value\CommonName; +use Surfnet\Stepup\Identity\Value\DocumentNumber; +use Surfnet\Stepup\Identity\Value\Email; use Surfnet\Stepup\Identity\Value\IdentityId; use Surfnet\Stepup\Identity\Value\Institution; use Surfnet\Stepup\Identity\Value\NameId; @@ -140,4 +143,18 @@ public function findOneByNameId(string $nameId): ?Identity { return $this->findOneBy(['nameId' => $nameId]); } + + public function updateStatusByIdentityIdToForgotten(IdentityId $identityId): void + { + $this->getEntityManager()->createQueryBuilder() + ->update($this->getEntityName(), 'i') + ->set('i.commonName', ":name") + ->set('i.email', ":email") + ->where('i.id = :id') + ->setParameter('id', $identityId->getIdentityId()) + ->setParameter('name', CommonName::unknown()) + ->setParameter('email', Email::unknown()) + ->getQuery() + ->execute(); + } } diff --git a/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Repository/RaListingRepository.php b/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Repository/RaListingRepository.php index 8515f6056..940762332 100644 --- a/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Repository/RaListingRepository.php +++ b/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Repository/RaListingRepository.php @@ -237,4 +237,9 @@ public function removeByIdentityIdAndInstitution(IdentityId $identityId, Institu ->getQuery() ->execute(); } + + public function contains(IdentityId $identityId): bool + { + return count(parent::findBy(['identityId' => (string)$identityId])) > 0; + } } diff --git a/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Repository/RaSecondFactorRepository.php b/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Repository/RaSecondFactorRepository.php index 2d2b57a8d..811c0a885 100644 --- a/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Repository/RaSecondFactorRepository.php +++ b/src/Surfnet/StepupMiddleware/ApiBundle/Identity/Repository/RaSecondFactorRepository.php @@ -23,6 +23,9 @@ use Doctrine\ORM\Query; use Doctrine\Persistence\ManagerRegistry; use Surfnet\Stepup\Exception\RuntimeException; +use Surfnet\Stepup\Identity\Value\CommonName; +use Surfnet\Stepup\Identity\Value\DocumentNumber; +use Surfnet\Stepup\Identity\Value\Email; use Surfnet\Stepup\Identity\Value\IdentityId; use Surfnet\StepupMiddleware\ApiBundle\Authorization\Filter\InstitutionAuthorizationRepositoryFilter; use Surfnet\StepupMiddleware\ApiBundle\Doctrine\Type\SecondFactorStatusType; @@ -31,6 +34,7 @@ use Surfnet\StepupMiddleware\ApiBundle\Identity\Value\SecondFactorStatus; /** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @extends ServiceEntityRepository */ class RaSecondFactorRepository extends ServiceEntityRepository @@ -203,9 +207,15 @@ public function updateStatusByIdentityIdToForgotten(IdentityId $identityId): voi $this->getEntityManager()->createQueryBuilder() ->update($this->getEntityName(), 'rasf') ->set('rasf.status', ":forgotten") + ->set('rasf.name', ":name") + ->set('rasf.email', ":email") + ->set('rasf.documentNumber', ":documentNumber") ->where('rasf.identityId = :identityId') ->setParameter('identityId', $identityId->getIdentityId()) ->setParameter('forgotten', 40) + ->setParameter('name', CommonName::unknown()) + ->setParameter('email', Email::unknown()) + ->setParameter('documentNumber', DocumentNumber::unknown()) ->getQuery() ->execute(); } diff --git a/src/Surfnet/StepupMiddleware/ApiBundle/Resources/config/services.yml b/src/Surfnet/StepupMiddleware/ApiBundle/Resources/config/services.yml index e7c1b9d76..3a032f7f7 100644 --- a/src/Surfnet/StepupMiddleware/ApiBundle/Resources/config/services.yml +++ b/src/Surfnet/StepupMiddleware/ApiBundle/Resources/config/services.yml @@ -156,6 +156,8 @@ services: $eventSourcingRepository: '@surfnet_stepup.repository.identity' $apiRepository: '@surfnet_stepup_middleware_api.repository.identity' $logger: '@logger' + $sraaRepository: '@surfnet_stepup_middleware_api.repository.sraa' + $raListingRepository: '@surfnet_stepup_middleware_api.repository.ra_listing' Surfnet\Stepup\Helper\RecoveryTokenSecretHelper: class: Surfnet\Stepup\Helper\RecoveryTokenSecretHelper diff --git a/src/Surfnet/StepupMiddleware/ApiBundle/Service/DeprovisionService.php b/src/Surfnet/StepupMiddleware/ApiBundle/Service/DeprovisionService.php index 2c50edb93..e45eea8d9 100644 --- a/src/Surfnet/StepupMiddleware/ApiBundle/Service/DeprovisionService.php +++ b/src/Surfnet/StepupMiddleware/ApiBundle/Service/DeprovisionService.php @@ -20,22 +20,31 @@ use Psr\Log\LoggerInterface; use Ramsey\Uuid\Uuid; +use RuntimeException; use Surfnet\Stepup\Identity\EventSourcing\IdentityRepository; use Surfnet\Stepup\Identity\Value\IdentityId; +use Surfnet\Stepup\Identity\Value\NameId; use Surfnet\StepupMiddleware\ApiBundle\Exception\UserNotFoundException; use Surfnet\StepupMiddleware\ApiBundle\Identity\Entity\Identity; use Surfnet\StepupMiddleware\ApiBundle\Identity\Repository\IdentityRepository as ApiIdentityRepository; +use Surfnet\StepupMiddleware\ApiBundle\Identity\Repository\RaListingRepository; +use Surfnet\StepupMiddleware\ApiBundle\Identity\Repository\SraaRepository; use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\ForgetIdentityCommand; use Surfnet\StepupMiddleware\CommandHandlingBundle\Pipeline\Pipeline; use function sprintf; +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class DeprovisionService implements DeprovisionServiceInterface { public function __construct( - private readonly Pipeline $pipeline, - private readonly IdentityRepository $eventSourcingRepository, - private readonly ApiIdentityRepository $apiRepository, - private readonly LoggerInterface $logger, + private readonly Pipeline $pipeline, + private readonly IdentityRepository $eventSourcingRepository, + private readonly ApiIdentityRepository $apiRepository, + private readonly LoggerInterface $logger, + private readonly SraaRepository $sraaRepository, + private readonly RaListingRepository $raListingRepository, ) { } @@ -85,4 +94,22 @@ private function getIdentityByNameId(string $collabPersonId): Identity } return $user; } + + public function assertIsAllowed(string $collabPersonId): void + { + $nameId = new NameId($collabPersonId); + $identity = $this->apiRepository->findOneByNameId($nameId); + + if ($identity === null) { + throw new RuntimeException('Cannot forget an identity that does not exist.'); + } + + if ($this->sraaRepository->contains($identity->nameId)) { + throw new RuntimeException('Cannot forget an identity that is currently accredited as an SRAA'); + } + + if ($this->raListingRepository->contains(new IdentityId($identity->id))) { + throw new RuntimeException('Cannot forget an identity that is currently accredited as an RA(A)'); + } + } } diff --git a/src/Surfnet/StepupMiddleware/ApiBundle/Service/DeprovisionServiceInterface.php b/src/Surfnet/StepupMiddleware/ApiBundle/Service/DeprovisionServiceInterface.php index e44f5b2be..2b22cff4a 100644 --- a/src/Surfnet/StepupMiddleware/ApiBundle/Service/DeprovisionServiceInterface.php +++ b/src/Surfnet/StepupMiddleware/ApiBundle/Service/DeprovisionServiceInterface.php @@ -34,4 +34,10 @@ public function readUserData(string $collabPersonId): array; * any user data either. */ public function deprovision(string $collabPersonId): void; + + /** + * This method checks if the right to be forgotten command + * can be used on the specified user. + */ + public function assertIsAllowed(string $collabPersonId): void; } diff --git a/src/Surfnet/StepupMiddleware/ApiBundle/Tests/Service/DeprovisionServiceTest.php b/src/Surfnet/StepupMiddleware/ApiBundle/Tests/Service/DeprovisionServiceTest.php index 7cd181948..49565e34c 100644 --- a/src/Surfnet/StepupMiddleware/ApiBundle/Tests/Service/DeprovisionServiceTest.php +++ b/src/Surfnet/StepupMiddleware/ApiBundle/Tests/Service/DeprovisionServiceTest.php @@ -24,9 +24,13 @@ use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; use Surfnet\Stepup\Identity\EventSourcing\IdentityRepository; +use Surfnet\Stepup\Identity\Value\IdentityId; use Surfnet\Stepup\Identity\Value\Institution; +use Surfnet\Stepup\Identity\Value\NameId; use Surfnet\StepupMiddleware\ApiBundle\Identity\Entity\Identity; use Surfnet\StepupMiddleware\ApiBundle\Identity\Repository\IdentityRepository as ApiIdentityRepository; +use Surfnet\StepupMiddleware\ApiBundle\Identity\Repository\RaListingRepository; +use Surfnet\StepupMiddleware\ApiBundle\Identity\Repository\SraaRepository; use Surfnet\StepupMiddleware\ApiBundle\Service\DeprovisionService; use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\ForgetIdentityCommand; use Surfnet\StepupMiddleware\CommandHandlingBundle\Pipeline\Pipeline; @@ -43,14 +47,21 @@ class DeprovisionServiceTest extends TestCase private MockInterface&IdentityRepository $eventRepo; + private MockInterface&SraaRepository $sraaRepo; + + private MockInterface&RaListingRepository $raListingRepo; + protected function setUp(): void { $this->pipeline = m::mock(Pipeline::class); $this->apiRepo = m::mock(ApiIdentityRepository::class); $this->eventRepo = m::mock(IdentityRepository::class); + $this->sraaRepo = m::mock(SraaRepository::class); + $this->raListingRepo = m::mock(RaListingRepository::class); + $logger = m::mock(LoggerInterface::class); $logger->shouldIgnoreMissing(); // Not going to verify every log message at this point - $this->deprovisionService = new DeprovisionService($this->pipeline, $this->eventRepo, $this->apiRepo, $logger); + $this->deprovisionService = new DeprovisionService($this->pipeline, $this->eventRepo, $this->apiRepo, $logger, $this->sraaRepo, $this->raListingRepo); } public function test_it_can_be_created(): void @@ -125,4 +136,35 @@ public function test_deprovision_method_performs_the_right_to_be_forgotten_comma $this->deprovisionService->deprovision('urn:collab:person:example.com:maynard_keenan'); } + + public function test_is_allowed_to_deprovision_user(): void + { + $nameId = 'urn:collab:person:example.com:maynard_keenan'; + $identity = m::mock(Identity::class); + $identity->id = '0bf0b464-a5de-11ec-b909-0242ac120002'; + $identity->institution = new Institution('tool'); + $identity->nameId = new NameId($nameId); + + $this->apiRepo + ->shouldReceive('findOneByNameId') + ->with($nameId) + ->once() + ->andReturn($identity); + + $this->sraaRepo + ->shouldReceive('contains') + ->with($identity->nameId) + ->once() + ->andReturn(false); + + $this->raListingRepo + ->shouldReceive('contains') + ->with(m::on(function (IdentityId $identityId) use ($identity): bool { + return $identityId->getIdentityId() === $identity->id; + })) + ->once() + ->andReturn(false); + + $this->deprovisionService->assertIsAllowed($nameId); + } } diff --git a/src/Surfnet/StepupMiddleware/CommandHandlingBundle/Tests/Identity/CommandHandler/RightToBeForgottenCommandHandlerTest.php b/src/Surfnet/StepupMiddleware/CommandHandlingBundle/Tests/Identity/CommandHandler/RightToBeForgottenCommandHandlerTest.php index 039117b0e..fef449cc0 100644 --- a/src/Surfnet/StepupMiddleware/CommandHandlingBundle/Tests/Identity/CommandHandler/RightToBeForgottenCommandHandlerTest.php +++ b/src/Surfnet/StepupMiddleware/CommandHandlingBundle/Tests/Identity/CommandHandler/RightToBeForgottenCommandHandlerTest.php @@ -160,11 +160,8 @@ public function an_identity_can_be_forgotten(): void * @group command-handler * @group sensitive-data */ - public function an_identity_may_not_be_forgotten_twice(): void + public function an_identity_may_be_forgotten_twice(): void { - $this->expectExceptionMessage("Operation on this Identity is not allowed: it has been forgotten"); - $this->expectException(DomainException::class); - $identityId = new IdentityId('A'); $institution = new Institution('Helsingin Yliopisto'); $nameId = new NameId('urn:eeva-kuopio'); @@ -178,6 +175,11 @@ public function an_identity_may_not_be_forgotten_twice(): void ->with(new IsEqual($nameId), new IsEqual($institution)) ->andReturn($this->createIdentity($identityId->getIdentityId())); + $this->sensitiveDataService + ->shouldReceive('forgetSensitiveData') + ->once() + ->with(new IsEqual($identityId)); + $this->sraaRepository->shouldReceive('contains')->once()->with(new IsEqual($nameId))->andReturn(false); $command = new ForgetIdentityCommand(); @@ -207,7 +209,10 @@ public function an_identity_may_not_be_forgotten_twice(): void ), new IdentityForgottenEvent($identityId, $institution), ]) - ->when($command); + ->when($command) + ->then([ + new IdentityForgottenEvent($identityId, $institution), + ]); } /** diff --git a/src/Surfnet/StepupMiddleware/GatewayBundle/Projector/InstitutionConfigurationProjector.php b/src/Surfnet/StepupMiddleware/GatewayBundle/Projector/InstitutionConfigurationProjector.php index 6d4672d8b..39e4b1dab 100644 --- a/src/Surfnet/StepupMiddleware/GatewayBundle/Projector/InstitutionConfigurationProjector.php +++ b/src/Surfnet/StepupMiddleware/GatewayBundle/Projector/InstitutionConfigurationProjector.php @@ -20,7 +20,8 @@ namespace Surfnet\StepupMiddleware\GatewayBundle\Projector; -use Broadway\ReadModel\Projector; +use Surfnet\Stepup\Identity\Event\IdentityForgottenEvent; +use Surfnet\Stepup\Projector\Projector; use Surfnet\Stepup\Configuration\Event\InstitutionConfigurationRemovedEvent; use Surfnet\Stepup\Configuration\Event\NewInstitutionConfigurationCreatedEvent; use Surfnet\Stepup\Configuration\Event\SsoOn2faOptionChangedEvent; @@ -64,4 +65,9 @@ public function applyInstitutionConfigurationRemovedEvent(InstitutionConfigurati { $this->repository->removeFor((string)$event->institution); } + + protected function applyIdentityForgottenEvent(IdentityForgottenEvent $event): void + { + // do nothing, no sensitive data in this projection + } } diff --git a/src/Surfnet/StepupMiddleware/GatewayBundle/Projector/SamlEntityProjector.php b/src/Surfnet/StepupMiddleware/GatewayBundle/Projector/SamlEntityProjector.php index 3b9a5675d..c3c442ec0 100644 --- a/src/Surfnet/StepupMiddleware/GatewayBundle/Projector/SamlEntityProjector.php +++ b/src/Surfnet/StepupMiddleware/GatewayBundle/Projector/SamlEntityProjector.php @@ -18,7 +18,8 @@ namespace Surfnet\StepupMiddleware\GatewayBundle\Projector; -use Broadway\ReadModel\Projector; +use Surfnet\Stepup\Identity\Event\IdentityForgottenEvent; +use Surfnet\Stepup\Projector\Projector; use Surfnet\Stepup\Configuration\Event\IdentityProvidersUpdatedEvent; use Surfnet\Stepup\Configuration\Event\ServiceProvidersUpdatedEvent; use Surfnet\StepupMiddleware\GatewayBundle\Entity\SamlEntity; @@ -56,4 +57,9 @@ public function applyIdentityProvidersUpdatedEvent(IdentityProvidersUpdatedEvent $this->samlEntityRepository->replaceAllIdps($spConfigurations); } + + protected function applyIdentityForgottenEvent(IdentityForgottenEvent $event): void + { + // do nothing, no sensitive data in this projection + } } diff --git a/src/Surfnet/StepupMiddleware/GatewayBundle/Projector/SecondFactorProjector.php b/src/Surfnet/StepupMiddleware/GatewayBundle/Projector/SecondFactorProjector.php index fd217359c..0d5bba432 100644 --- a/src/Surfnet/StepupMiddleware/GatewayBundle/Projector/SecondFactorProjector.php +++ b/src/Surfnet/StepupMiddleware/GatewayBundle/Projector/SecondFactorProjector.php @@ -18,7 +18,7 @@ namespace Surfnet\StepupMiddleware\GatewayBundle\Projector; -use Broadway\ReadModel\Projector; +use Surfnet\Stepup\Projector\Projector; use Surfnet\Stepup\Identity\Event\CompliedWithVettedSecondFactorRevocationEvent; use Surfnet\Stepup\Identity\Event\IdentityForgottenEvent; use Surfnet\Stepup\Identity\Event\LocalePreferenceExpressedEvent; diff --git a/src/Surfnet/StepupMiddleware/GatewayBundle/Projector/WhitelistProjector.php b/src/Surfnet/StepupMiddleware/GatewayBundle/Projector/WhitelistProjector.php index 8c9bd5838..7e2716429 100644 --- a/src/Surfnet/StepupMiddleware/GatewayBundle/Projector/WhitelistProjector.php +++ b/src/Surfnet/StepupMiddleware/GatewayBundle/Projector/WhitelistProjector.php @@ -18,7 +18,8 @@ namespace Surfnet\StepupMiddleware\GatewayBundle\Projector; -use Broadway\ReadModel\Projector; +use Surfnet\Stepup\Identity\Event\IdentityForgottenEvent; +use Surfnet\Stepup\Projector\Projector; use Surfnet\Stepup\Identity\Event\InstitutionsAddedToWhitelistEvent; use Surfnet\Stepup\Identity\Event\InstitutionsRemovedFromWhitelistEvent; use Surfnet\Stepup\Identity\Event\WhitelistCreatedEvent; @@ -75,4 +76,9 @@ protected function applyInstitutionsRemovedFromWhitelistEvent(InstitutionsRemove $this->whitelistEntryRepository->remove($whitelistEntries); } + + protected function applyIdentityForgottenEvent(IdentityForgottenEvent $event): void + { + // do nothing, no sensitive data in this projection + } } diff --git a/src/Surfnet/StepupMiddleware/ManagementBundle/Configuration/Projector/EmailTemplatesProjector.php b/src/Surfnet/StepupMiddleware/ManagementBundle/Configuration/Projector/EmailTemplatesProjector.php index 51b22ebfc..91cef55bd 100644 --- a/src/Surfnet/StepupMiddleware/ManagementBundle/Configuration/Projector/EmailTemplatesProjector.php +++ b/src/Surfnet/StepupMiddleware/ManagementBundle/Configuration/Projector/EmailTemplatesProjector.php @@ -18,7 +18,8 @@ namespace Surfnet\StepupMiddleware\ManagementBundle\Configuration\Projector; -use Broadway\ReadModel\Projector; +use Surfnet\Stepup\Identity\Event\IdentityForgottenEvent; +use Surfnet\Stepup\Projector\Projector; use Surfnet\Stepup\Configuration\Event\EmailTemplatesUpdatedEvent; use Surfnet\StepupMiddleware\ManagementBundle\Configuration\Entity\EmailTemplate; use Surfnet\StepupMiddleware\ManagementBundle\Configuration\Repository\EmailTemplateRepository; @@ -40,4 +41,9 @@ public function applyEmailTemplatesUpdatedEvent(EmailTemplatesUpdatedEvent $even } } } + + protected function applyIdentityForgottenEvent(IdentityForgottenEvent $event): void + { + // do nothing, no sensitive data in this projection + } }