Completed
Pull Request — develop (#227)
by Michiel
04:22 queued 02:14
created

SecondFactorService::findOneUnverified()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
3
/**
4
 * Copyright 2014 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\StepupSelfService\SelfServiceBundle\Service;
20
21
use Surfnet\StepupMiddlewareClient\Identity\Dto\UnverifiedSecondFactorSearchQuery;
22
use Surfnet\StepupMiddlewareClient\Identity\Dto\VerifiedSecondFactorOfIdentitySearchQuery;
23
use Surfnet\StepupMiddlewareClient\Identity\Dto\VettedSecondFactorSearchQuery;
24
use Surfnet\StepupMiddlewareClientBundle\Dto\CollectionDto;
25
use Surfnet\StepupMiddlewareClientBundle\Identity\Command\SelfVetSecondFactorCommand;
26
use Surfnet\StepupMiddlewareClientBundle\Identity\Command\RevokeOwnSecondFactorCommand;
27
use Surfnet\StepupMiddlewareClientBundle\Identity\Command\VerifyEmailCommand;
28
use Surfnet\StepupMiddlewareClientBundle\Identity\Dto\UnverifiedSecondFactor;
29
use Surfnet\StepupMiddlewareClientBundle\Identity\Dto\UnverifiedSecondFactorCollection;
30
use Surfnet\StepupMiddlewareClientBundle\Identity\Dto\VerifiedSecondFactor;
31
use Surfnet\StepupMiddlewareClientBundle\Identity\Dto\VerifiedSecondFactorCollection;
32
use Surfnet\StepupMiddlewareClientBundle\Identity\Dto\VettedSecondFactor;
33
use Surfnet\StepupMiddlewareClientBundle\Identity\Dto\VettedSecondFactorCollection;
34
use Surfnet\StepupMiddlewareClientBundle\Identity\Service\SecondFactorService as MiddlewareSecondFactorService;
35
use Surfnet\StepupSelfService\SelfServiceBundle\Command\SelfVetCommand;
36
use Surfnet\StepupSelfService\SelfServiceBundle\Command\RevokeCommand;
37
use Surfnet\StepupSelfService\SelfServiceBundle\Exception\LogicException;
38
39
/**
40
 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
41
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
42
 */
43
class SecondFactorService
44
{
45
    /**
46
     * @var \Surfnet\StepupMiddlewareClientBundle\Identity\Service\SecondFactorService
47
     */
48
    private $secondFactors;
49
50
    /**
51
     * @var \Surfnet\StepupSelfService\SelfServiceBundle\Service\CommandService
52
     */
53
    private $commandService;
54
55
    /**
56
     * @param MiddlewareSecondFactorService $secondFactors
57
     * @param CommandService                $commandService
58
     */
59
    public function __construct(
60
        MiddlewareSecondFactorService $secondFactors,
61
        CommandService $commandService
62
    ) {
63
        $this->secondFactors = $secondFactors;
64
        $this->commandService = $commandService;
65
    }
66
67
    /**
68
     * @param string $identityId
69
     * @param string $nonce
70
     * @return bool
71
     */
72
    public function verifyEmail($identityId, $nonce)
73
    {
74
        $command                    = new VerifyEmailCommand();
75
        $command->identityId        = $identityId;
76
        $command->verificationNonce = $nonce;
77
78
        $result = $this->commandService->execute($command);
79
80
        return $result->isSuccessful();
81
    }
82
83
    /**
84
     * @param RevokeCommand $command
85
     * @return bool
86
     */
87
    public function revoke(RevokeCommand $command)
88
    {
89
        /** @var UnverifiedSecondFactor|VerifiedSecondFactor|VettedSecondFactor $secondFactor */
90
        $secondFactor = $command->secondFactor;
91
92
        $apiCommand = new RevokeOwnSecondFactorCommand();
93
        $apiCommand->identityId = $command->identity->id;
94
        $apiCommand->secondFactorId = $secondFactor->id;
95
96
        $result = $this->commandService->execute($apiCommand);
97
98
        return $result->isSuccessful();
99
    }
100
101
    public function selfVet(SelfVetCommand $command): bool
102
    {
103
        $apiCommand = new SelfVetSecondFactorCommand();
104
        $apiCommand->identityId = $command->identity->id;
105
        $apiCommand->registrationCode = $command->secondFactor->registrationCode;
106
        $apiCommand->secondFactorIdentifier = $command->secondFactor->id;
107
        $apiCommand->secondFactorId = $command->secondFactor->secondFactorIdentifier;
108
        $apiCommand->secondFactorType = $command->secondFactor->type;
109
        $apiCommand->authorityId = $command->identity->id;
110
        $apiCommand->authoringSecondFactorIdentifier = $command->authoringLoa;
111
112
        $result = $this->commandService->execute($apiCommand);
113
        return $result->isSuccessful();
114
    }
115
116
    /**
117
     * Returns whether the given registrant has registered second factors with Step-up. The state of the second factor
118
     * is irrelevant.
119
     *
120
     * @param string $identityId
121
     * @return bool
122
     */
123
    public function doSecondFactorsExistForIdentity($identityId)
124
    {
125
        $unverifiedSecondFactors = $this->findUnverifiedByIdentity($identityId);
126
        $verifiedSecondFactors = $this->findVerifiedByIdentity($identityId);
127
        $vettedSecondFactors = $this->findVettedByIdentity($identityId);
128
129
        return $unverifiedSecondFactors->getTotalItems() +
130
               $verifiedSecondFactors->getTotalItems() +
131
               $vettedSecondFactors->getTotalItems() > 0;
132
    }
133
134
    public function identityHasSecondFactorOfStateWithId($identityId, $state, $secondFactorId)
135
    {
136
        switch ($state) {
137
            case 'unverified':
138
                $secondFactors = $this->findUnverifiedByIdentity($identityId);
139
                break;
140
            case 'verified':
141
                $secondFactors = $this->findVerifiedByIdentity($identityId);
142
                break;
143
            case 'vetted':
144
                $secondFactors = $this->findVettedByIdentity($identityId);
145
                break;
146
            default:
147
                throw new LogicException(sprintf('Invalid second factor state "%s" given.', $state));
148
        }
149
150
        if (count($secondFactors->getElements()) === 0) {
151
            return false;
152
        }
153
154
        foreach ($secondFactors->getElements() as $secondFactor) {
155
            if ($secondFactor->id === $secondFactorId) {
156
                return true;
157
            }
158
        }
159
160
        return false;
161
    }
162
163
    /**
164
     * Returns the given registrant's unverified second factors.
165
     *
166
     * @param string $identityId
167
     * @return UnverifiedSecondFactorCollection
0 ignored issues
show
Documentation introduced by
Should the return type not be UnverifiedSecondFactorCollection|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
168
     */
169
    public function findUnverifiedByIdentity($identityId)
170
    {
171
        return $this->secondFactors->searchUnverified(
172
            (new UnverifiedSecondFactorSearchQuery())->setIdentityId($identityId)
173
        );
174
    }
175
176
    /**
177
     * Returns the given registrant's verified second factors.
178
     *
179
     * @param string $identityId
180
     * @return VerifiedSecondFactorCollection
0 ignored issues
show
Documentation introduced by
Should the return type not be VerifiedSecondFactorCollection|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
181
     */
182
    public function findVerifiedByIdentity($identityId)
183
    {
184
        $query = new VerifiedSecondFactorOfIdentitySearchQuery();
185
        $query->setIdentityId($identityId);
186
        return $this->secondFactors->searchOwnVerified($query);
187
    }
188
189
    /**
190
     * Returns the given registrant's verified second factors.
191
     *
192
     * @param string $identityId
193
     * @return VettedSecondFactorCollection
0 ignored issues
show
Documentation introduced by
Should the return type not be VettedSecondFactorCollection|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
194
     */
195
    public function findVettedByIdentity($identityId)
196
    {
197
        return $this->secondFactors->searchVetted(
198
            (new VettedSecondFactorSearchQuery())->setIdentityId($identityId)
199
        );
200
    }
201
202
    /**
203
     * @param string $secondFactorId
204
     * @return null|UnverifiedSecondFactor
205
     */
206
    public function findOneUnverified($secondFactorId)
207
    {
208
        return $this->secondFactors->getUnverified($secondFactorId);
209
    }
210
211
    /**
212
     * @param string $secondFactorId
213
     * @return null|VerifiedSecondFactor
214
     */
215
    public function findOneVerified($secondFactorId)
216
    {
217
        return $this->secondFactors->getVerified($secondFactorId);
218
    }
219
220
    /**
221
     * @param string $secondFactorId
222
     * @return null|VettedSecondFactor
223
     */
224
    public function findOneVetted($secondFactorId)
225
    {
226
        return $this->secondFactors->getVetted($secondFactorId);
227
    }
228
229
    /**
230
     * @param string $identityId
231
     * @param string $verificationNonce
232
     * @return UnverifiedSecondFactor|null
233
     */
234
    public function findUnverifiedByVerificationNonce($identityId, $verificationNonce)
235
    {
236
        $secondFactors = $this->secondFactors->searchUnverified(
237
            (new UnverifiedSecondFactorSearchQuery())
238
                ->setIdentityId($identityId)
239
                ->setVerificationNonce($verificationNonce)
240
        );
241
242
        $elements = $secondFactors->getElements();
243
244
        switch (count($elements)) {
245
            case 0:
246
                return null;
247
            case 1:
248
                return reset($elements);
249
            default:
250
                throw new LogicException('There cannot be more than one unverified second factor with the same nonce');
251
        }
252
    }
253
254
    /**
255
     * @param array $allSecondFactors
256
     * @param UnverifiedSecondFactorCollection $unverifiedCollection
257
     * @param VerifiedSecondFactorCollection $verifiedCollection
258
     * @param VettedSecondFactorCollection $vettedCollection
259
     * @return array
260
     */
261
    private function determineAvailable(
262
        array $allSecondFactors,
263
        UnverifiedSecondFactorCollection $unverifiedCollection,
264
        VerifiedSecondFactorCollection $verifiedCollection,
265
        VettedSecondFactorCollection $vettedCollection
266
    ) {
267
        $allSecondFactors = $this->filterAvailableSecondFactors($allSecondFactors, $unverifiedCollection);
268
        $allSecondFactors = $this->filterAvailableSecondFactors($allSecondFactors, $verifiedCollection);
269
        $allSecondFactors = $this->filterAvailableSecondFactors($allSecondFactors, $vettedCollection);
270
        return $allSecondFactors;
271
    }
272
273
    /**
274
     * @param array $allSecondFactors
275
     * @param CollectionDto $collection
276
     * @return array
277
     */
278
    private function filterAvailableSecondFactors(array $allSecondFactors, CollectionDto $collection)
279
    {
280
        foreach ($collection->getElements() as $secondFactor) {
281
            $keyFound = array_search($secondFactor->type, $allSecondFactors);
282
            if (is_numeric($keyFound)) {
283
                unset($allSecondFactors[$keyFound]);
284
            }
285
        }
286
        return $allSecondFactors;
287
    }
288
289
    /**
290
     * @param $identity
291
     * @param $allSecondFactors
292
     * @param $allowedSecondFactors
293
     * @param $maximumNumberOfRegistrations
294
     * @return SecondFactorTypeCollection
295
     */
296
    public function getSecondFactorsForIdentity(
297
        $identity,
298
        $allSecondFactors,
299
        $allowedSecondFactors,
300
        $maximumNumberOfRegistrations
301
    ) {
302
        $unverified = $this->findUnverifiedByIdentity($identity->id);
303
        $verified = $this->findVerifiedByIdentity($identity->id);
304
        $vetted = $this->findVettedByIdentity($identity->id);
305
        // Determine which Second Factors are still available for registration.
306
        $available = $this->determineAvailable($allSecondFactors, $unverified, $verified, $vetted);
0 ignored issues
show
Bug introduced by
It seems like $unverified defined by $this->findUnverifiedByIdentity($identity->id) on line 302 can be null; however, Surfnet\StepupSelfServic...e::determineAvailable() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
Bug introduced by
It seems like $verified defined by $this->findVerifiedByIdentity($identity->id) on line 303 can be null; however, Surfnet\StepupSelfServic...e::determineAvailable() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
Bug introduced by
It seems like $vetted defined by $this->findVettedByIdentity($identity->id) on line 304 can be null; however, Surfnet\StepupSelfServic...e::determineAvailable() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
307
308
        if (!empty($allowedSecondFactors)) {
309
            $available = array_intersect(
310
                $available,
311
                $allowedSecondFactors
312
            );
313
        }
314
315
        $collection = new SecondFactorTypeCollection();
316
        $collection->unverified = $unverified;
317
        $collection->verified   = $verified;
318
        $collection->vetted     = $vetted;
319
        $collection->available  = array_combine($available, $available);
320
        $collection->maxNumberOfRegistrations = $maximumNumberOfRegistrations;
321
322
        return $collection;
323
    }
324
}
325