Completed
Pull Request — feature/fine-grained-authoriza... (#158)
by Michiel
60:26 queued 41:50
created

SecondFactorService   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 287
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 5

Importance

Changes 0
Metric Value
wmc 27
lcom 2
cbo 5
dl 0
loc 287
rs 10
c 0
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 1
A verifyEmail() 0 10 1
A revoke() 0 20 2
A doSecondFactorsExistForIdentity() 0 10 1
B identityHasSecondFactorOfStateWithId() 0 28 7
A findUnverifiedByIdentity() 0 6 1
A findVerifiedByIdentity() 0 7 1
A findVettedByIdentity() 0 6 1
A findOneUnverified() 0 4 1
A findOneVerified() 0 4 1
A findOneVetted() 0 4 1
A findUnverifiedByVerificationNonce() 0 19 3
A determineAvailable() 0 11 1
A filterAvailableSecondFactors() 0 10 3
A getSecondFactorsForIdentity() 0 29 2
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
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
181
     */
182
    public function findVerifiedByIdentity($identityId, $actorInstitution)
183
    {
184
        $query = new VerifiedSecondFactorSearchQuery();
185
        $query->setIdentityId($identityId);
186
        $query->setActorInstitution($actorInstitution);
187
        return $this->secondFactors->searchVerified($query);
188
    }
189
190
    /**
191
     * Returns the given registrant's verified second factors.
192
     *
193
     * @param string $identityId
194
     * @return VettedSecondFactorCollection
195
     */
196
    public function findVettedByIdentity($identityId)
197
    {
198
        return $this->secondFactors->searchVetted(
199
            (new VettedSecondFactorSearchQuery())->setIdentityId($identityId)
200
        );
201
    }
202
203
    /**
204
     * @param string $secondFactorId
205
     * @return null|UnverifiedSecondFactor
206
     */
207
    public function findOneUnverified($secondFactorId)
208
    {
209
        return $this->secondFactors->getUnverified($secondFactorId);
210
    }
211
212
    /**
213
     * @param string $secondFactorId
214
     * @return null|VerifiedSecondFactor
215
     */
216
    public function findOneVerified($secondFactorId)
217
    {
218
        return $this->secondFactors->getVerified($secondFactorId);
219
    }
220
221
    /**
222
     * @param string $secondFactorId
223
     * @return null|VettedSecondFactor
224
     */
225
    public function findOneVetted($secondFactorId)
226
    {
227
        return $this->secondFactors->getVetted($secondFactorId);
228
    }
229
230
    /**
231
     * @param string $identityId
232
     * @param string $verificationNonce
233
     * @return UnverifiedSecondFactor|null
234
     */
235
    public function findUnverifiedByVerificationNonce($identityId, $verificationNonce)
236
    {
237
        $secondFactors = $this->secondFactors->searchUnverified(
238
            (new UnverifiedSecondFactorSearchQuery())
239
                ->setIdentityId($identityId)
240
                ->setVerificationNonce($verificationNonce)
241
        );
242
243
        $elements = $secondFactors->getElements();
244
245
        switch (count($elements)) {
246
            case 0:
247
                return null;
248
            case 1:
249
                return reset($elements);
250
            default:
251
                throw new LogicException('There cannot be more than one unverified second factor with the same nonce');
252
        }
253
    }
254
255
    /**
256
     * @param array $allSecondFactors
257
     * @param UnverifiedSecondFactorCollection $unverifiedCollection
258
     * @param VerifiedSecondFactorCollection $verifiedCollection
259
     * @param VettedSecondFactorCollection $vettedCollection
260
     * @return array
261
     */
262
    private function determineAvailable(
263
        array $allSecondFactors,
264
        UnverifiedSecondFactorCollection $unverifiedCollection,
265
        VerifiedSecondFactorCollection $verifiedCollection,
266
        VettedSecondFactorCollection $vettedCollection
267
    ) {
268
        $allSecondFactors = $this->filterAvailableSecondFactors($allSecondFactors, $unverifiedCollection);
269
        $allSecondFactors = $this->filterAvailableSecondFactors($allSecondFactors, $verifiedCollection);
270
        $allSecondFactors = $this->filterAvailableSecondFactors($allSecondFactors, $vettedCollection);
271
        return $allSecondFactors;
272
    }
273
274
    /**
275
     * @param array $allSecondFactors
276
     * @param CollectionDto $collection
277
     * @return array
278
     */
279
    private function filterAvailableSecondFactors(array $allSecondFactors, CollectionDto $collection)
280
    {
281
        foreach ($collection->getElements() as $secondFactor) {
282
            $keyFound = array_search($secondFactor->type, $allSecondFactors);
283
            if (is_numeric($keyFound)) {
284
                unset($allSecondFactors[$keyFound]);
285
            }
286
        }
287
        return $allSecondFactors;
288
    }
289
290
    /**
291
     * @param $identity
292
     * @param string $institution
293
     * @param $allSecondFactors
294
     * @param $allowedSecondFactors
295
     * @param $maximumNumberOfRegistrations
296
     * @return SecondFactorTypeCollection
297
     */
298
    public function getSecondFactorsForIdentity(
299
        $identity,
300
        $institution,
301
        $allSecondFactors,
302
        $allowedSecondFactors,
303
        $maximumNumberOfRegistrations
304
    ) {
305
        $unverified = $this->findUnverifiedByIdentity($identity->id);
306
        $verified = $this->findVerifiedByIdentity($identity->id, $institution);
307
        $vetted = $this->findVettedByIdentity($identity->id);
308
        // Determine which Second Factors are still available for registration.
309
        $available = $this->determineAvailable($allSecondFactors, $unverified, $verified, $vetted);
310
311
        if (!empty($allowedSecondFactors)) {
312
            $available = array_intersect(
313
                $available,
314
                $allowedSecondFactors
315
            );
316
        }
317
318
        $collection = new SecondFactorTypeCollection();
319
        $collection->unverified = $unverified;
320
        $collection->verified   = $verified;
321
        $collection->vetted     = $vetted;
322
        $collection->available  = array_combine($available, $available);
323
        $collection->maxNumberOfRegistrations = $maximumNumberOfRegistrations;
324
325
        return $collection;
326
    }
327
}
328