Completed
Push — feature/fine-grained-authoriza... ( 7000b8...be2531 )
by
unknown
26:33 queued 44s
created

SelfServiceBundle/Service/SecondFactorService.php (6 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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\VerifiedSecondFactorSearchQuery;
23
use Surfnet\StepupMiddlewareClient\Identity\Dto\VettedSecondFactorSearchQuery;
24
use Surfnet\StepupMiddlewareClientBundle\Dto\CollectionDto;
25
use Surfnet\StepupMiddlewareClientBundle\Identity\Command\RevokeOwnSecondFactorCommand;
26
use Surfnet\StepupMiddlewareClientBundle\Identity\Command\VerifyEmailCommand;
27
use Surfnet\StepupMiddlewareClientBundle\Identity\Dto\UnverifiedSecondFactor;
28
use Surfnet\StepupMiddlewareClientBundle\Identity\Dto\UnverifiedSecondFactorCollection;
29
use Surfnet\StepupMiddlewareClientBundle\Identity\Dto\VerifiedSecondFactor;
30
use Surfnet\StepupMiddlewareClientBundle\Identity\Dto\VerifiedSecondFactorCollection;
31
use Surfnet\StepupMiddlewareClientBundle\Identity\Dto\VettedSecondFactor;
32
use Surfnet\StepupMiddlewareClientBundle\Identity\Dto\VettedSecondFactorCollection;
33
use Surfnet\StepupMiddlewareClientBundle\Identity\Service\SecondFactorService as MiddlewareSecondFactorService;
34
use Surfnet\StepupSelfService\SelfServiceBundle\Command\RevokeCommand;
35
use Surfnet\StepupSelfService\SelfServiceBundle\Exception\LogicException;
36
37
/**
38
 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
39
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
40
 */
41
class SecondFactorService
42
{
43
    /**
44
     * @var \Surfnet\StepupMiddlewareClientBundle\Identity\Service\SecondFactorService
45
     */
46
    private $secondFactors;
47
48
    /**
49
     * @var \Surfnet\StepupSelfService\SelfServiceBundle\Service\CommandService
50
     */
51
    private $commandService;
52
53
    /**
54
     * @var \Surfnet\StepupSelfService\SelfServiceBundle\Service\U2fSecondFactorService
55
     */
56
    private $u2fSecondFactorService;
57
58
    /**
59
     * @param MiddlewareSecondFactorService $secondFactors
60
     * @param CommandService                $commandService
61
     * @param U2fSecondFactorService        $u2fSecondFactorService
62
     */
63
    public function __construct(
64
        MiddlewareSecondFactorService $secondFactors,
65
        CommandService $commandService,
66
        U2fSecondFactorService $u2fSecondFactorService
67
    ) {
68
        $this->secondFactors = $secondFactors;
69
        $this->commandService = $commandService;
70
        $this->u2fSecondFactorService = $u2fSecondFactorService;
71
    }
72
73
    /**
74
     * @param string $identityId
75
     * @param string $nonce
76
     * @return bool
77
     */
78
    public function verifyEmail($identityId, $nonce)
79
    {
80
        $command                    = new VerifyEmailCommand();
81
        $command->identityId        = $identityId;
82
        $command->verificationNonce = $nonce;
83
84
        $result = $this->commandService->execute($command);
85
86
        return $result->isSuccessful();
87
    }
88
89
    /**
90
     * @param RevokeCommand $command
91
     * @return bool
92
     */
93
    public function revoke(RevokeCommand $command)
94
    {
95
        /** @var UnverifiedSecondFactor|VerifiedSecondFactor|VettedSecondFactor $secondFactor */
96
        $secondFactor = $command->secondFactor;
97
98
        $apiCommand = new RevokeOwnSecondFactorCommand();
99
        $apiCommand->identityId = $command->identity->id;
100
        $apiCommand->secondFactorId = $secondFactor->id;
101
102
        $result = $this->commandService->execute($apiCommand);
103
104
        if ($secondFactor->type === 'u2f') {
105
            $this->u2fSecondFactorService->revokeRegistration(
106
                $command->identity,
107
                $secondFactor->secondFactorIdentifier
108
            );
109
        }
110
111
        return $result->isSuccessful();
112
    }
113
114
    /**
115
     * Returns whether the given registrant has registered second factors with Step-up. The state of the second factor
116
     * is irrelevant.
117
     *
118
     * @param string $identityId
119
     * @param string $institution
120
     * @return bool
121
     */
122
    public function doSecondFactorsExistForIdentity($identityId, $institution)
123
    {
124
        $unverifiedSecondFactors = $this->findUnverifiedByIdentity($identityId);
125
        $verifiedSecondFactors = $this->findVerifiedByIdentity($identityId, $institution);
126
        $vettedSecondFactors = $this->findVettedByIdentity($identityId);
127
128
        return $unverifiedSecondFactors->getTotalItems() +
129
               $verifiedSecondFactors->getTotalItems() +
130
               $vettedSecondFactors->getTotalItems() > 0;
131
    }
132
133
    public function identityHasSecondFactorOfStateWithId($identityId, $state, $secondFactorId, $institution)
134
    {
135
        switch ($state) {
136
            case 'unverified':
137
                $secondFactors = $this->findUnverifiedByIdentity($identityId);
138
                break;
139
            case 'verified':
140
                $secondFactors = $this->findVerifiedByIdentity($identityId, $institution);
141
                break;
142
            case 'vetted':
143
                $secondFactors = $this->findVettedByIdentity($identityId);
144
                break;
145
            default:
146
                throw new LogicException(sprintf('Invalid second factor state "%s" given.', $state));
147
        }
148
149
        if (count($secondFactors->getElements()) === 0) {
150
            return false;
151
        }
152
153
        foreach ($secondFactors->getElements() as $secondFactor) {
154
            if ($secondFactor->id === $secondFactorId) {
155
                return true;
156
            }
157
        }
158
159
        return false;
160
    }
161
162
    /**
163
     * Returns the given registrant's unverified second factors.
164
     *
165
     * @param string $identityId
166
     * @return UnverifiedSecondFactorCollection
0 ignored issues
show
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...
167
     */
168
    public function findUnverifiedByIdentity($identityId)
169
    {
170
        return $this->secondFactors->searchUnverified(
171
            (new UnverifiedSecondFactorSearchQuery())->setIdentityId($identityId)
172
        );
173
    }
174
175
    /**
176
     * Returns the given registrant's verified second factors.
177
     *
178
     * @param string $identityId
179
     * @param string $actorInstitution
180
     * @return VerifiedSecondFactorCollection
0 ignored issues
show
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, $actorInstitution)
183
    {
184
        $query = new VerifiedSecondFactorSearchQuery();
185
        $query->setIdentityId($identityId);
186
        // In self service the actor equals the identity of the user.
187
        $query->setActorId($identityId);
188
        $query->setActorInstitution($actorInstitution);
189
        $query->setInstitution($actorInstitution);
190
        // Actor and identity are equal in SelfService.
191
        $query->setActorId($identityId);
192
        return $this->secondFactors->searchVerified($query);
193
    }
194
195
    /**
196
     * Returns the given registrant's verified second factors.
197
     *
198
     * @param string $identityId
199
     * @return VettedSecondFactorCollection
0 ignored issues
show
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...
200
     */
201
    public function findVettedByIdentity($identityId)
202
    {
203
        return $this->secondFactors->searchVetted(
204
            (new VettedSecondFactorSearchQuery())->setIdentityId($identityId)
205
        );
206
    }
207
208
    /**
209
     * @param string $secondFactorId
210
     * @return null|UnverifiedSecondFactor
211
     */
212
    public function findOneUnverified($secondFactorId)
213
    {
214
        return $this->secondFactors->getUnverified($secondFactorId);
215
    }
216
217
    /**
218
     * @param string $secondFactorId
219
     * @return null|VerifiedSecondFactor
220
     */
221
    public function findOneVerified($secondFactorId)
222
    {
223
        return $this->secondFactors->getVerified($secondFactorId);
224
    }
225
226
    /**
227
     * @param string $secondFactorId
228
     * @return null|VettedSecondFactor
229
     */
230
    public function findOneVetted($secondFactorId)
231
    {
232
        return $this->secondFactors->getVetted($secondFactorId);
233
    }
234
235
    /**
236
     * @param string $identityId
237
     * @param string $verificationNonce
238
     * @return UnverifiedSecondFactor|null
239
     */
240
    public function findUnverifiedByVerificationNonce($identityId, $verificationNonce)
241
    {
242
        $secondFactors = $this->secondFactors->searchUnverified(
243
            (new UnverifiedSecondFactorSearchQuery())
244
                ->setIdentityId($identityId)
245
                ->setVerificationNonce($verificationNonce)
246
        );
247
248
        $elements = $secondFactors->getElements();
249
250
        switch (count($elements)) {
251
            case 0:
252
                return null;
253
            case 1:
254
                return reset($elements);
255
            default:
256
                throw new LogicException('There cannot be more than one unverified second factor with the same nonce');
257
        }
258
    }
259
260
    /**
261
     * @param array $allSecondFactors
262
     * @param UnverifiedSecondFactorCollection $unverifiedCollection
263
     * @param VerifiedSecondFactorCollection $verifiedCollection
264
     * @param VettedSecondFactorCollection $vettedCollection
265
     * @return array
266
     */
267
    private function determineAvailable(
268
        array $allSecondFactors,
269
        UnverifiedSecondFactorCollection $unverifiedCollection,
270
        VerifiedSecondFactorCollection $verifiedCollection,
271
        VettedSecondFactorCollection $vettedCollection
272
    ) {
273
        $allSecondFactors = $this->filterAvailableSecondFactors($allSecondFactors, $unverifiedCollection);
274
        $allSecondFactors = $this->filterAvailableSecondFactors($allSecondFactors, $verifiedCollection);
275
        $allSecondFactors = $this->filterAvailableSecondFactors($allSecondFactors, $vettedCollection);
276
        return $allSecondFactors;
277
    }
278
279
    /**
280
     * @param array $allSecondFactors
281
     * @param CollectionDto $collection
282
     * @return array
283
     */
284
    private function filterAvailableSecondFactors(array $allSecondFactors, CollectionDto $collection)
285
    {
286
        foreach ($collection->getElements() as $secondFactor) {
287
            $keyFound = array_search($secondFactor->type, $allSecondFactors);
288
            if (is_numeric($keyFound)) {
289
                unset($allSecondFactors[$keyFound]);
290
            }
291
        }
292
        return $allSecondFactors;
293
    }
294
295
    /**
296
     * @param $identity
297
     * @param string $institution
298
     * @param $allSecondFactors
299
     * @param $allowedSecondFactors
300
     * @param $maximumNumberOfRegistrations
301
     * @return SecondFactorTypeCollection
302
     */
303
    public function getSecondFactorsForIdentity(
304
        $identity,
305
        $institution,
306
        $allSecondFactors,
307
        $allowedSecondFactors,
308
        $maximumNumberOfRegistrations
309
    ) {
310
        $unverified = $this->findUnverifiedByIdentity($identity->id);
311
        $verified = $this->findVerifiedByIdentity($identity->id, $institution);
312
        $vetted = $this->findVettedByIdentity($identity->id);
313
        // Determine which Second Factors are still available for registration.
314
        $available = $this->determineAvailable($allSecondFactors, $unverified, $verified, $vetted);
0 ignored issues
show
It seems like $unverified defined by $this->findUnverifiedByIdentity($identity->id) on line 310 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...
It seems like $verified defined by $this->findVerifiedByIde...tity->id, $institution) on line 311 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...
It seems like $vetted defined by $this->findVettedByIdentity($identity->id) on line 312 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...
315
316
        if (!empty($allowedSecondFactors)) {
317
            $available = array_intersect(
318
                $available,
319
                $allowedSecondFactors
320
            );
321
        }
322
323
        $collection = new SecondFactorTypeCollection();
324
        $collection->unverified = $unverified;
325
        $collection->verified   = $verified;
326
        $collection->vetted     = $vetted;
327
        $collection->available  = array_combine($available, $available);
328
        $collection->maxNumberOfRegistrations = $maximumNumberOfRegistrations;
329
330
        return $collection;
331
    }
332
}
333