Passed
Pull Request — main (#557)
by Johan
10:46 queued 05:30
created

MigrateSecondFactorCommand::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 0

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 0
nc 1
nop 2
dl 0
loc 2
rs 10
c 1
b 0
f 0
1
<?php
2
3
/**
4
 * Copyright 2021 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 InvalidArgumentException;
23
use Surfnet\Stepup\Identity\Value\Institution;
24
use Surfnet\Stepup\Identity\Value\NameId;
25
use Surfnet\StepupMiddleware\ApiBundle\Identity\Entity\Identity;
26
use Surfnet\StepupMiddleware\ApiBundle\Identity\Entity\VettedSecondFactor;
27
use Surfnet\StepupMiddleware\MiddlewareBundle\Service\BootstrapCommandService;
28
use Surfnet\StepupMiddleware\MiddlewareBundle\Service\TransactionHelper;
0 ignored issues
show
Bug introduced by
The type Surfnet\StepupMiddleware...rvice\TransactionHelper was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
29
use Symfony\Component\Console\Attribute\Argument;
30
use Symfony\Component\Console\Attribute\AsCommand;
31
use Symfony\Component\Console\Output\OutputInterface;
32
33
#[AsCommand(
34
    name: 'middleware:migrate:vetted-tokens',
35
    description: 'Migrates the tokens of an identity to a new institution while preserving the old tokens'
36
)
37
]
38
final class MigrateSecondFactorCommand
39
{
40
    public function __construct(private readonly BootstrapCommandService $bootstrapService, private readonly TransactionHelper $transactionHelper)
41
    {
42
    }
43
44
    public function __invoke(
45
        #[Argument(description: 'The old NameID of the identity used as the source of the tokens to move', name: 'old-name-id')]
46
        string $oldNameId,
47
        #[Argument(description: 'The new NameID of the identity to move the tokens to', name: 'new-name-id')]
48
        string $newNameId,
49
        #[Argument(description: 'The institution of the target identity', name: 'target-institution')]
50
        ?string $targetInstitution,
51
        #[Argument(description: 'The e-mail address of the identity to create', name: 'email')]
52
        ?string $email,
53
        OutputInterface $output
54
    ): int {
55
        $sourceNameId = new NameId($oldNameId);
56
        $targetNameId = new NameId($newNameId);
57
58
        $output->writeln(sprintf('<comment>Starting token migration for %s</comment>', $sourceNameId));
59
        $sourceIdentity = $this->bootstrapService->getIdentityByNameId($sourceNameId);
60
61
        if ($sourceIdentity === null) {
62
            throw new InvalidArgumentException("oldNameId could net be resolved to a Identity.");
63
        }
64
65
        $targetIdentity = $this->bootstrapService->getIdentityByNameId($targetNameId);
66
67
        try {
68
            $this->transactionHelper->beginTransaction();
69
70
            // Check if target identity should be created
71
            if (!$targetIdentity instanceof Identity) {
72
                $output->writeln(
73
                    sprintf('<info>Target with NameID %s does not exist, creating new identity</info>', $targetNameId),
74
                );
75
76
                $identityId = $this->createIdentity($targetNameId, $sourceIdentity, $targetInstitution, $email);
77
78
                $output->writeln(
79
                    sprintf('<info>Successfully created identity with UUID %s</info>', $identityId),
80
                );
81
82
83
                $targetIdentity = $this->bootstrapService->getIdentityByNameId($targetNameId);
84
            }
85
86
            // Foreach token, perform the token move command
87
            $sourceVettedSecondFactors = $this->bootstrapService->getVettedSecondFactorsFromIdentity($sourceIdentity);
88
            $targetVettedSecondFactors = $this->bootstrapService->getVettedSecondFactorsFromIdentity($targetIdentity);
0 ignored issues
show
Bug introduced by
It seems like $targetIdentity can also be of type null; however, parameter $identity of Surfnet\StepupMiddleware...ndFactorsFromIdentity() does only seem to accept Surfnet\StepupMiddleware...dentity\Entity\Identity, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

88
            $targetVettedSecondFactors = $this->bootstrapService->getVettedSecondFactorsFromIdentity(/** @scrutinizer ignore-type */ $targetIdentity);
Loading history...
89
            foreach ($sourceVettedSecondFactors as $secondFactor) {
90
                if (!$this->tokenExists($targetVettedSecondFactors, $secondFactor)) {
91
                    $this->bootstrapService->migrateVettedSecondFactor($sourceIdentity, $targetIdentity, $secondFactor);
0 ignored issues
show
Bug introduced by
It seems like $targetIdentity can also be of type null; however, parameter $targetIdentity of Surfnet\StepupMiddleware...ateVettedSecondFactor() does only seem to accept Surfnet\StepupMiddleware...dentity\Entity\Identity, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

91
                    $this->bootstrapService->migrateVettedSecondFactor($sourceIdentity, /** @scrutinizer ignore-type */ $targetIdentity, $secondFactor);
Loading history...
92
                    $output->writeln(sprintf('<comment>Moved token %s</comment>', $secondFactor->id));
93
                } else {
94
                    $output->writeln(
95
                        sprintf('<info>Skipped moving token %s, already present"</info>', $secondFactor->id),
96
                    );
97
                }
98
            }
99
100
            $this->transactionHelper->finishTransaction();
101
        } catch (Exception $e) {
102
            $output->writeln(
103
                sprintf(
104
                    '<error>An Error occurred when trying to move the tokens of identity: "%s"</error>',
105
                    $e->getMessage(),
106
                ),
107
            );
108
            $this->transactionHelper->rollback();
109
            return 1;
110
        }
111
        $output->writeln(
112
            sprintf(
113
                '<info>Successfully moved tokens from identity %s to identity %s</info>',
114
                $sourceIdentity->id,
115
                $targetIdentity->id,
116
            ),
117
        );
118
        return 0;
119
    }
120
121
    /**
122
     * @return string
123
     */
124
    private function createIdentity(NameId $targetNameId, Identity $sourceIdentity, ?string $newInstitution, ?string $newEmail): string
125
    {
126
        if (!$newInstitution || !$newEmail) {
127
            throw new InvalidArgumentException("Missing email and institution");
128
        }
129
130
        $institution = new Institution($newInstitution);
131
132
        $identity = $this->bootstrapService->createIdentity(
133
            $institution,
134
            $targetNameId,
135
            $sourceIdentity->commonName->getCommonName(),
136
            $newEmail,
137
            $sourceIdentity->preferredLocale->getLocale(),
138
        );
139
140
        return $identity->id;
141
    }
142
143
    private function tokenExists(array $targetSecondFactors, VettedSecondFactor $sourceSecondFactor): bool
144
    {
145
        foreach ($targetSecondFactors as $secondFactor) {
146
            if ($secondFactor->isEqual($sourceSecondFactor)) {
147
                return true;
148
            }
149
        }
150
        return false;
151
    }
152
}
153