Completed
Push — feature/improve-input-validati... ( 8f7b6e )
by Michiel
02:14
created

BootstrapGsspSecondFactorCommand   A

Complexity

Total Complexity 10

Size/Duplication

Total Lines 127
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 11

Importance

Changes 0
Metric Value
wmc 10
lcom 1
cbo 11
dl 0
loc 127
rs 10
c 0
b 0
f 0

3 Methods

Rating   Name   Duplication   Size   Complexity  
A configure() 0 23 1
C execute() 0 89 8
A provePossession() 0 10 1
1
<?php
2
3
/**
4
 * Copyright 2020 SURFnet bv
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.
8
 * You may obtain a copy of the License at
9
 *
10
 *     http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS,
14
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 * See the License for the specific language governing permissions and
16
 * limitations under the License.
17
 */
18
19
namespace Surfnet\StepupMiddleware\MiddlewareBundle\Console\Command;
20
21
use Exception;
22
use Rhumsaa\Uuid\Uuid;
23
use Surfnet\Stepup\Identity\Value\Institution;
24
use Surfnet\Stepup\Identity\Value\NameId;
25
use Surfnet\StepupMiddleware\ApiBundle\Identity\Entity\UnverifiedSecondFactor;
26
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\ProveGssfPossessionCommand;
27
use Symfony\Component\Console\Input\InputArgument;
28
use Symfony\Component\Console\Input\InputInterface;
29
use Symfony\Component\Console\Output\OutputInterface;
30
use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken;
31
32
final class BootstrapGsspSecondFactorCommand extends AbstractBootstrapCommand
33
{
34
    protected function configure()
35
    {
36
        $this
37
            ->setDescription('Creates a Generic SAML Second Factor (GSSF) 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
                'gssp-token-type',
42
                InputArgument::REQUIRED,
43
                'The GSSP token type as defined in the GSSP config, for example tiqr or webauthn'
44
            )
45
            ->addArgument(
46
                'gssp-token-identifier',
47
                InputArgument::REQUIRED,
48
                'The identifier of the token as registered at the GSSP'
49
            )
50
            ->addArgument(
51
                'registration-status',
52
                InputArgument::REQUIRED,
53
                'Valid arguments: unverified, verified, vetted'
54
            )
55
            ->addArgument('actor-id', InputArgument::REQUIRED, 'The id of the vetting actor');
56
    }
57
58
    protected function execute(InputInterface $input, OutputInterface $output)
59
    {
60
        $registrationStatus = $input->getArgument('registration-status');
61
        $this->validRegistrationStatus($registrationStatus);
0 ignored issues
show
Bug introduced by
It seems like $registrationStatus defined by $input->getArgument('registration-status') on line 60 can also be of type array<integer,string> or null; however, Surfnet\StepupMiddleware...lidRegistrationStatus() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
62
63
        $this->tokenStorage->setToken(
64
            new AnonymousToken('cli.bootstrap-gssp-token', 'cli', ['ROLE_SS', 'ROLE_RA'])
0 ignored issues
show
Documentation introduced by
array('ROLE_SS', 'ROLE_RA') is of type array<integer,string,{"0":"string","1":"string"}>, but the function expects a array<integer,object<Sym...curity\Core\Role\Role>>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
65
        );
66
        $nameId = new NameId($input->getArgument('name-id'));
67
        $institutionText = $input->getArgument('institution');
68
        $institution = new Institution($institutionText);
0 ignored issues
show
Bug introduced by
It seems like $institutionText defined by $input->getArgument('institution') on line 67 can also be of type array<integer,string> or null; however, Surfnet\Stepup\Identity\...titution::__construct() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
69
        $mailVerificationRequired = $this->requiresMailVerification($institutionText);
70
        $tokenType = $input->getArgument('gssp-token-type');
71
        $tokenIdentifier = $input->getArgument('gssp-token-identifier');
72
        $actorId = $input->getArgument('actor-id');
73
        $this->enrichEventMetadata($actorId);
74
        if (!$this->tokenBootstrapService->hasIdentityWithNameIdAndInstitution($nameId, $institution)) {
75
            $output->writeln(
76
                sprintf(
77
                    '<error>An identity with name ID "%s" from institution "%s" does not exist, create it first.</error>',
78
                    $nameId->getNameId(),
79
                    $institution->getInstitution()
80
                )
81
            );
82
83
            return;
84
        }
85
        $identity = $this->tokenBootstrapService->findOneByNameIdAndInstitution($nameId, $institution);
86
        $output->writeln(sprintf('<comment>Adding a %s %s GSSP token for %s</comment>', $registrationStatus, $tokenType, $identity->commonName));
87
        $this->beginTransaction();
88
        $secondFactorId = Uuid::uuid4()->toString();
89
90
        try {
91
            switch ($registrationStatus) {
92
                case "unverified":
93
                    $output->writeln(sprintf('<comment>Creating an unverified %s token</comment>', $tokenType));
94
                    $this->provePossession($secondFactorId, $identity, $tokenType, $tokenIdentifier);
95
                    break;
96
                case "verified":
97
                    $output->writeln(sprintf('<comment>Creating an unverified %s token</comment>', $tokenType));
98
                    $this->provePossession($secondFactorId, $identity, $tokenType, $tokenIdentifier);
99
                    $unverifiedSecondFactor = $this->tokenBootstrapService->findUnverifiedToken($identity->id, $tokenType);
100
                    if ($mailVerificationRequired) {
101
                        $output->writeln(sprintf('<comment>Creating an verified %s token</comment>', $tokenType));
102
                        $this->verifyEmail($identity, $unverifiedSecondFactor);
103
                    }
104
                    break;
105
                case "vetted":
106
                    $output->writeln(sprintf('<comment>Creating an unverified %s token</comment>', $tokenType));
107
                    $this->provePossession($secondFactorId, $identity, $tokenType, $tokenIdentifier);
108
                    /** @var UnverifiedSecondFactor $unverifiedSecondFactor */
109
                    $unverifiedSecondFactor = $this->tokenBootstrapService->findUnverifiedToken($identity->id, $tokenType);
110
                    if ($mailVerificationRequired) {
111
                        $output->writeln(sprintf('<comment>Creating an verified %s token</comment>', $tokenType));
112
                        $this->verifyEmail($identity, $unverifiedSecondFactor);
113
                    }
114
                    $verifiedSecondFactor = $this->tokenBootstrapService->findVerifiedToken($identity->id, $tokenType);
115
                    $output->writeln(sprintf('<comment>Vetting the verified %s token</comment>', $tokenType));
116
                    $this->vetSecondFactor(
117
                        $tokenType,
118
                        $actorId,
119
                        $identity,
120
                        $secondFactorId,
121
                        $verifiedSecondFactor,
122
                        $tokenIdentifier
123
                    );
124
                    break;
125
            }
126
            $this->finishTransaction();
127
        } catch (Exception $e) {
128
            $output->writeln(
129
                sprintf(
130
                    '<error>An Error occurred when trying to bootstrap the %s token: "%s"</error>',
131
                    $tokenType,
132
                    $e->getMessage()
133
                )
134
            );
135
            $this->rollback();
136
            throw $e;
137
        }
138
        $output->writeln(
139
            sprintf(
140
                '<info>Successfully %s %s second factor with UUID %s</info>',
141
                $registrationStatus,
142
                $tokenType,
143
                $secondFactorId
144
            )
145
        );
146
    }
147
148
    private function provePossession($secondFactorId, $identity, $tokenType, $tokenIdentifier)
149
    {
150
        $command = new ProveGssfPossessionCommand();
151
        $command->UUID = (string)Uuid::uuid4();
152
        $command->secondFactorId = $secondFactorId;
153
        $command->identityId = $identity->id;
154
        $command->stepupProvider = $tokenType;
155
        $command->gssfId = $tokenIdentifier;
156
        $this->process($command);
157
    }
158
}
159