@@ 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 SMS token: "%s"</error>', |
|
124 | $e->getMessage() |
|
125 | ) |
|
126 | ); |
|
127 | $this->rollback(); |
|
128 | throw $e; |
|
129 | } |
|
130 | $output->writeln( |
|
131 | sprintf( |
|
132 | '<info>Successfully registered a SMS token 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 Yubikey token: "%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 |