| @@ 33-149 (lines=117) @@ | ||
| 30 | use Symfony\Component\Console\Output\OutputInterface; |
|
| 31 | use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken; |
|
| 32 | ||
| 33 | final class BootstrapSmsSecondFactorCommand extends AbstractBootstrapCommand |
|
| 34 | { |
|
| 35 | protected function configure() |
|
| 36 | { |
|
| 37 | $this |
|
| 38 | ->setDescription('Creates a SMS second factor for a specified user') |
|
| 39 | ->addArgument('name-id', InputArgument::REQUIRED, 'The NameID of the identity to create') |
|
| 40 | ->addArgument('institution', InputArgument::REQUIRED, 'The institution of the identity to create') |
|
| 41 | ->addArgument( |
|
| 42 | 'phone-number', |
|
| 43 | InputArgument::REQUIRED, |
|
| 44 | 'The phone number of the user should be formatted like "+31 (0) 612345678"' |
|
| 45 | ) |
|
| 46 | ->addArgument( |
|
| 47 | 'registration-status', |
|
| 48 | InputArgument::REQUIRED, |
|
| 49 | 'Valid arguments: unverified, verified, vetted' |
|
| 50 | ) |
|
| 51 | ->addArgument('actor-id', InputArgument::REQUIRED, 'The id of the vetting actor'); |
|
| 52 | } |
|
| 53 | ||
| 54 | protected function execute(InputInterface $input, OutputInterface $output) |
|
| 55 | { |
|
| 56 | $this->tokenStorage->setToken( |
|
| 57 | new AnonymousToken('cli.bootstrap-sms-token', 'cli', ['ROLE_SS', 'ROLE_RA']) |
|
| 58 | ); |
|
| 59 | $nameId = new NameId($input->getArgument('name-id')); |
|
| 60 | $institutionText = $input->getArgument('institution'); |
|
| 61 | $institution = new Institution($institutionText); |
|
| 62 | $mailVerificationRequired = $this->requiresMailVerification($institutionText); |
|
| 63 | $registrationStatus = $input->getArgument('registration-status'); |
|
| 64 | $phoneNumber = $input->getArgument('phone-number'); |
|
| 65 | $actorId = $input->getArgument('actor-id'); |
|
| 66 | $this->enrichEventMetadata($actorId); |
|
| 67 | if (!$this->tokenBootstrapService->hasIdentityWithNameIdAndInstitution($nameId, $institution)) { |
|
| 68 | $output->writeln( |
|
| 69 | sprintf( |
|
| 70 | '<error>An identity with name ID "%s" from institution "%s" does not exist, create it first.</error>', |
|
| 71 | $nameId->getNameId(), |
|
| 72 | $institution->getInstitution() |
|
| 73 | ) |
|
| 74 | ); |
|
| 75 | ||
| 76 | return; |
|
| 77 | } |
|
| 78 | $identity = $this->tokenBootstrapService->findOneByNameIdAndInstitution($nameId, $institution); |
|
| 79 | $output->writeln(sprintf('<comment>Adding a %s SMS token for %s</comment>', $registrationStatus, $identity->commonName)); |
|
| 80 | $this->beginTransaction(); |
|
| 81 | $secondFactorId = Uuid::uuid4()->toString(); |
|
| 82 | ||
| 83 | try { |
|
| 84 | switch ($registrationStatus) { |
|
| 85 | case "unverified": |
|
| 86 | $output->writeln('<comment>Creating an unverified SMS token</comment>'); |
|
| 87 | $this->provePossession($secondFactorId, $identity, $phoneNumber); |
|
| 88 | break; |
|
| 89 | case "verified": |
|
| 90 | $output->writeln('<comment>Creating an unverified SMS token</comment>'); |
|
| 91 | $this->provePossession($secondFactorId, $identity, $phoneNumber); |
|
| 92 | $unverifiedSecondFactor = $this->tokenBootstrapService->findUnverifiedToken($identity->id, 'sms'); |
|
| 93 | if ($mailVerificationRequired) { |
|
| 94 | $output->writeln('<comment>Creating a verified SMS token</comment>'); |
|
| 95 | $this->verifyEmail($identity, $unverifiedSecondFactor); |
|
| 96 | } |
|
| 97 | break; |
|
| 98 | case "vetted": |
|
| 99 | $output->writeln('<comment>Creating an unverified SMS token</comment>'); |
|
| 100 | $this->provePossession($secondFactorId, $identity, $phoneNumber); |
|
| 101 | /** @var UnverifiedSecondFactor $unverifiedSecondFactor */ |
|
| 102 | $unverifiedSecondFactor = $this->tokenBootstrapService->findUnverifiedToken($identity->id, 'sms'); |
|
| 103 | if ($mailVerificationRequired) { |
|
| 104 | $output->writeln('<comment>Creating a verified SMS token</comment>'); |
|
| 105 | $this->verifyEmail($identity, $unverifiedSecondFactor); |
|
| 106 | } |
|
| 107 | $verifiedSecondFactor = $this->tokenBootstrapService->findVerifiedToken($identity->id, 'sms'); |
|
| 108 | $output->writeln('<comment>Vetting the verified SMS token</comment>'); |
|
| 109 | $this->vetSecondFactor( |
|
| 110 | 'sms', |
|
| 111 | $actorId, |
|
| 112 | $identity, |
|
| 113 | $secondFactorId, |
|
| 114 | $verifiedSecondFactor, |
|
| 115 | $phoneNumber |
|
| 116 | ); |
|
| 117 | break; |
|
| 118 | } |
|
| 119 | $this->finishTransaction(); |
|
| 120 | } catch (Exception $e) { |
|
| 121 | $output->writeln( |
|
| 122 | sprintf( |
|
| 123 | '<error>An Error occurred when trying to bootstrap the identity: "%s"</error>', |
|
| 124 | $e->getMessage() |
|
| 125 | ) |
|
| 126 | ); |
|
| 127 | $this->rollback(); |
|
| 128 | throw $e; |
|
| 129 | } |
|
| 130 | $output->writeln( |
|
| 131 | sprintf( |
|
| 132 | '<info>Successfully created identity with UUID %s and %s second factor with UUID %s</info>', |
|
| 133 | $identity->id, |
|
| 134 | $registrationStatus, |
|
| 135 | $secondFactorId |
|
| 136 | ) |
|
| 137 | ); |
|
| 138 | } |
|
| 139 | ||
| 140 | private function provePossession($secondFactorId, $identity, $phoneNumber) |
|
| 141 | { |
|
| 142 | $command = new ProvePhonePossessionCommand(); |
|
| 143 | $command->UUID = (string)Uuid::uuid4(); |
|
| 144 | $command->secondFactorId = $secondFactorId; |
|
| 145 | $command->identityId = $identity->id; |
|
| 146 | $command->phoneNumber = $phoneNumber; |
|
| 147 | $this->process($command); |
|
| 148 | } |
|
| 149 | } |
|
| 150 | ||
| @@ 32-148 (lines=117) @@ | ||
| 29 | use Symfony\Component\Console\Output\OutputInterface; |
|
| 30 | use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken; |
|
| 31 | ||
| 32 | final class BootstrapYubikeySecondFactorCommand extends AbstractBootstrapCommand |
|
| 33 | { |
|
| 34 | protected function configure() |
|
| 35 | { |
|
| 36 | $this |
|
| 37 | ->setDescription('Creates a Yubikey second factor for a specified user') |
|
| 38 | ->addArgument('name-id', InputArgument::REQUIRED, 'The NameID of the identity to create') |
|
| 39 | ->addArgument('institution', InputArgument::REQUIRED, 'The institution of the identity to create') |
|
| 40 | ->addArgument( |
|
| 41 | 'yubikey', |
|
| 42 | InputArgument::REQUIRED, |
|
| 43 | 'The public ID of the Yubikey. Remove the last 32 characters of a Yubikey OTP to acquire this.' |
|
| 44 | ) |
|
| 45 | ->addArgument( |
|
| 46 | 'registration-status', |
|
| 47 | InputArgument::REQUIRED, |
|
| 48 | 'Valid arguments: unverified, verified, vetted' |
|
| 49 | ) |
|
| 50 | ->addArgument('actor-id', InputArgument::REQUIRED, 'The id of the vetting actor'); |
|
| 51 | } |
|
| 52 | ||
| 53 | protected function execute(InputInterface $input, OutputInterface $output) |
|
| 54 | { |
|
| 55 | $this->tokenStorage->setToken( |
|
| 56 | new AnonymousToken('cli.bootstrap-yubikey-token', 'cli', ['ROLE_SS', 'ROLE_RA']) |
|
| 57 | ); |
|
| 58 | $nameId = new NameId($input->getArgument('name-id')); |
|
| 59 | $institutionText = $input->getArgument('institution'); |
|
| 60 | $institution = new Institution($institutionText); |
|
| 61 | $mailVerificationRequired = $this->requiresMailVerification($institutionText); |
|
| 62 | $registrationStatus = $input->getArgument('registration-status'); |
|
| 63 | $yubikey = $input->getArgument('yubikey'); |
|
| 64 | $actorId = $input->getArgument('actor-id'); |
|
| 65 | $this->enrichEventMetadata($actorId); |
|
| 66 | if (!$this->tokenBootstrapService->hasIdentityWithNameIdAndInstitution($nameId, $institution)) { |
|
| 67 | $output->writeln( |
|
| 68 | sprintf( |
|
| 69 | '<error>An identity with name ID "%s" from institution "%s" does not exist, create it first.</error>', |
|
| 70 | $nameId->getNameId(), |
|
| 71 | $institution->getInstitution() |
|
| 72 | ) |
|
| 73 | ); |
|
| 74 | ||
| 75 | return; |
|
| 76 | } |
|
| 77 | $identity = $this->tokenBootstrapService->findOneByNameIdAndInstitution($nameId, $institution); |
|
| 78 | $output->writeln(sprintf('<comment>Adding a %s Yubikey token for %s</comment>', $registrationStatus, $identity->commonName)); |
|
| 79 | $this->beginTransaction(); |
|
| 80 | $secondFactorId = Uuid::uuid4()->toString(); |
|
| 81 | ||
| 82 | try { |
|
| 83 | switch ($registrationStatus) { |
|
| 84 | case "unverified": |
|
| 85 | $output->writeln('<comment>Creating an unverified Yubikey token</comment>'); |
|
| 86 | $this->provePossession($secondFactorId, $identity, $yubikey); |
|
| 87 | break; |
|
| 88 | case "verified": |
|
| 89 | $output->writeln('<comment>Creating an unverified Yubikey token</comment>'); |
|
| 90 | $this->provePossession($secondFactorId, $identity, $yubikey); |
|
| 91 | $unverifiedSecondFactor = $this->tokenBootstrapService->findUnverifiedToken($identity->id, 'yubikey'); |
|
| 92 | if ($mailVerificationRequired) { |
|
| 93 | $output->writeln('<comment>Creating a verified Yubikey token</comment>'); |
|
| 94 | $this->verifyEmail($identity, $unverifiedSecondFactor); |
|
| 95 | } |
|
| 96 | break; |
|
| 97 | case "vetted": |
|
| 98 | $output->writeln('<comment>Creating an unverified Yubikey token</comment>'); |
|
| 99 | $this->provePossession($secondFactorId, $identity, $yubikey); |
|
| 100 | /** @var UnverifiedSecondFactor $unverifiedSecondFactor */ |
|
| 101 | $unverifiedSecondFactor = $this->tokenBootstrapService->findUnverifiedToken($identity->id, 'yubikey'); |
|
| 102 | if ($mailVerificationRequired) { |
|
| 103 | $output->writeln('<comment>Creating a verified Yubikey token</comment>'); |
|
| 104 | $this->verifyEmail($identity, $unverifiedSecondFactor); |
|
| 105 | } |
|
| 106 | $verifiedSecondFactor = $this->tokenBootstrapService->findVerifiedToken($identity->id, 'yubikey'); |
|
| 107 | $output->writeln('<comment>Vetting the verified Yubikey token</comment>'); |
|
| 108 | $this->vetSecondFactor( |
|
| 109 | 'yubikey', |
|
| 110 | $actorId, |
|
| 111 | $identity, |
|
| 112 | $secondFactorId, |
|
| 113 | $verifiedSecondFactor, |
|
| 114 | $yubikey |
|
| 115 | ); |
|
| 116 | break; |
|
| 117 | } |
|
| 118 | $this->finishTransaction(); |
|
| 119 | } catch (Exception $e) { |
|
| 120 | $output->writeln( |
|
| 121 | sprintf( |
|
| 122 | '<error>An Error occurred when trying to bootstrap the identity: "%s"</error>', |
|
| 123 | $e->getMessage() |
|
| 124 | ) |
|
| 125 | ); |
|
| 126 | $this->rollback(); |
|
| 127 | throw $e; |
|
| 128 | } |
|
| 129 | $output->writeln( |
|
| 130 | sprintf( |
|
| 131 | '<info>Successfully created identity with UUID %s and %s second factor with UUID %s</info>', |
|
| 132 | $identity->id, |
|
| 133 | $registrationStatus, |
|
| 134 | $secondFactorId |
|
| 135 | ) |
|
| 136 | ); |
|
| 137 | } |
|
| 138 | ||
| 139 | private function provePossession($secondFactorId, $identity, $phoneNumber) |
|
| 140 | { |
|
| 141 | $command = new ProveYubikeyPossessionCommand(); |
|
| 142 | $command->UUID = (string)Uuid::uuid4(); |
|
| 143 | $command->secondFactorId = $secondFactorId; |
|
| 144 | $command->identityId = $identity->id; |
|
| 145 | $command->yubikeyPublicId = $phoneNumber; |
|
| 146 | $this->process($command); |
|
| 147 | } |
|
| 148 | } |
|
| 149 | ||