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\Stepup\Identity; |
20
|
|
|
|
21
|
|
|
use Broadway\EventSourcing\EventSourcedAggregateRoot; |
22
|
|
|
use Surfnet\Stepup\Exception\DomainException; |
23
|
|
|
use Surfnet\Stepup\Identity\Api\Identity as IdentityApi; |
24
|
|
|
use Surfnet\Stepup\Identity\Entity\RegistrationAuthority; |
25
|
|
|
use Surfnet\Stepup\Identity\Entity\SecondFactorCollection; |
26
|
|
|
use Surfnet\Stepup\Identity\Entity\UnverifiedSecondFactor; |
27
|
|
|
use Surfnet\Stepup\Identity\Entity\VerifiedSecondFactor; |
28
|
|
|
use Surfnet\Stepup\Identity\Entity\VettedSecondFactor; |
29
|
|
|
use Surfnet\Stepup\Identity\Event\AppointedAsRaaEvent; |
30
|
|
|
use Surfnet\Stepup\Identity\Event\AppointedAsRaEvent; |
31
|
|
|
use Surfnet\Stepup\Identity\Event\CompliedWithUnverifiedSecondFactorRevocationEvent; |
32
|
|
|
use Surfnet\Stepup\Identity\Event\CompliedWithVerifiedSecondFactorRevocationEvent; |
33
|
|
|
use Surfnet\Stepup\Identity\Event\CompliedWithVettedSecondFactorRevocationEvent; |
34
|
|
|
use Surfnet\Stepup\Identity\Event\EmailVerifiedEvent; |
35
|
|
|
use Surfnet\Stepup\Identity\Event\GssfPossessionProvenEvent; |
36
|
|
|
use Surfnet\Stepup\Identity\Event\IdentityAccreditedAsRaaEvent; |
37
|
|
|
use Surfnet\Stepup\Identity\Event\IdentityAccreditedAsRaEvent; |
38
|
|
|
use Surfnet\Stepup\Identity\Event\IdentityCreatedEvent; |
39
|
|
|
use Surfnet\Stepup\Identity\Event\IdentityEmailChangedEvent; |
40
|
|
|
use Surfnet\Stepup\Identity\Event\IdentityForgottenEvent; |
41
|
|
|
use Surfnet\Stepup\Identity\Event\IdentityRenamedEvent; |
42
|
|
|
use Surfnet\Stepup\Identity\Event\LocalePreferenceExpressedEvent; |
43
|
|
|
use Surfnet\Stepup\Identity\Event\PhonePossessionProvenEvent; |
44
|
|
|
use Surfnet\Stepup\Identity\Event\RegistrationAuthorityInformationAmendedEvent; |
45
|
|
|
use Surfnet\Stepup\Identity\Event\RegistrationAuthorityRetractedEvent; |
46
|
|
|
use Surfnet\Stepup\Identity\Event\SecondFactorVettedEvent; |
47
|
|
|
use Surfnet\Stepup\Identity\Event\U2fDevicePossessionProvenEvent; |
48
|
|
|
use Surfnet\Stepup\Identity\Event\UnverifiedSecondFactorRevokedEvent; |
49
|
|
|
use Surfnet\Stepup\Identity\Event\VerifiedSecondFactorRevokedEvent; |
50
|
|
|
use Surfnet\Stepup\Identity\Event\VettedSecondFactorRevokedEvent; |
51
|
|
|
use Surfnet\Stepup\Identity\Event\YubikeyPossessionProvenEvent; |
52
|
|
|
use Surfnet\Stepup\Identity\Event\YubikeySecondFactorBootstrappedEvent; |
53
|
|
|
use Surfnet\Stepup\Identity\Value\CommonName; |
54
|
|
|
use Surfnet\Stepup\Identity\Value\ContactInformation; |
55
|
|
|
use Surfnet\Stepup\Identity\Value\DocumentNumber; |
56
|
|
|
use Surfnet\Stepup\Identity\Value\Email; |
57
|
|
|
use Surfnet\Stepup\Identity\Value\EmailVerificationWindow; |
58
|
|
|
use Surfnet\Stepup\Identity\Value\GssfId; |
59
|
|
|
use Surfnet\Stepup\Identity\Value\IdentityId; |
60
|
|
|
use Surfnet\Stepup\Identity\Value\Institution; |
61
|
|
|
use Surfnet\Stepup\Identity\Value\Locale; |
62
|
|
|
use Surfnet\Stepup\Identity\Value\Location; |
63
|
|
|
use Surfnet\Stepup\Identity\Value\NameId; |
64
|
|
|
use Surfnet\Stepup\Identity\Value\PhoneNumber; |
65
|
|
|
use Surfnet\Stepup\Identity\Value\RegistrationAuthorityRole; |
66
|
|
|
use Surfnet\Stepup\Identity\Value\SecondFactorId; |
67
|
|
|
use Surfnet\Stepup\Identity\Value\SecondFactorIdentifier; |
68
|
|
|
use Surfnet\Stepup\Identity\Value\StepupProvider; |
69
|
|
|
use Surfnet\Stepup\Identity\Value\U2fKeyHandle; |
70
|
|
|
use Surfnet\Stepup\Identity\Value\YubikeyPublicId; |
71
|
|
|
use Surfnet\Stepup\Token\TokenGenerator; |
72
|
|
|
use Surfnet\StepupBundle\Value\SecondFactorType; |
73
|
|
|
|
74
|
|
|
/** |
75
|
|
|
* @SuppressWarnings(PHPMD.CouplingBetweenObjects) |
76
|
|
|
* @SuppressWarnings(PHPMD.TooManyMethods) |
77
|
|
|
* @SuppressWarnings(PHPMD.TooManyPublicMethods) |
78
|
|
|
* @SuppressWarnings(PHPMD.ExcessiveClassComplexity) |
79
|
|
|
*/ |
80
|
|
|
class Identity extends EventSourcedAggregateRoot implements IdentityApi |
81
|
|
|
{ |
82
|
|
|
/** |
83
|
|
|
* @var IdentityId |
84
|
|
|
*/ |
85
|
|
|
private $id; |
86
|
|
|
|
87
|
|
|
/** |
88
|
|
|
* @var Institution |
89
|
|
|
*/ |
90
|
|
|
private $institution; |
91
|
|
|
|
92
|
|
|
/** |
93
|
|
|
* @var NameId |
94
|
|
|
*/ |
95
|
|
|
private $nameId; |
96
|
|
|
|
97
|
|
|
/** |
98
|
|
|
* @var \Surfnet\Stepup\Identity\Value\CommonName |
99
|
|
|
*/ |
100
|
|
|
private $commonName; |
101
|
|
|
|
102
|
|
|
/** |
103
|
|
|
* @var \Surfnet\Stepup\Identity\Value\Email |
104
|
|
|
*/ |
105
|
|
|
private $email; |
106
|
|
|
|
107
|
|
|
/** |
108
|
|
|
* @var SecondFactorCollection|UnverifiedSecondFactor[] |
109
|
|
|
*/ |
110
|
|
|
private $unverifiedSecondFactors; |
111
|
|
|
|
112
|
|
|
/** |
113
|
|
|
* @var SecondFactorCollection|VerifiedSecondFactor[] |
114
|
|
|
*/ |
115
|
|
|
private $verifiedSecondFactors; |
116
|
|
|
|
117
|
|
|
/** |
118
|
|
|
* @var SecondFactorCollection|VettedSecondFactor[] |
119
|
|
|
*/ |
120
|
|
|
private $vettedSecondFactors; |
121
|
|
|
|
122
|
|
|
/** |
123
|
|
|
* @var RegistrationAuthority |
124
|
|
|
*/ |
125
|
|
|
private $registrationAuthority; |
126
|
|
|
|
127
|
|
|
/** |
128
|
|
|
* @var Locale |
129
|
|
|
*/ |
130
|
|
|
private $preferredLocale; |
131
|
|
|
|
132
|
|
|
/** |
133
|
|
|
* @var boolean |
134
|
|
|
*/ |
135
|
|
|
private $forgotten; |
136
|
|
|
|
137
|
|
|
public static function create( |
138
|
|
|
IdentityId $id, |
139
|
|
|
Institution $institution, |
140
|
|
|
NameId $nameId, |
141
|
|
|
CommonName $commonName, |
142
|
|
|
Email $email, |
143
|
|
|
Locale $preferredLocale |
144
|
|
|
) { |
145
|
|
|
$identity = new self(); |
146
|
|
|
$identity->apply(new IdentityCreatedEvent($id, $institution, $nameId, $commonName, $email, $preferredLocale)); |
147
|
|
|
|
148
|
|
|
return $identity; |
149
|
|
|
} |
150
|
|
|
|
151
|
|
|
final public function __construct() |
152
|
|
|
{ |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
public function rename(CommonName $commonName) |
156
|
|
|
{ |
157
|
|
|
$this->assertNotForgotten(); |
158
|
|
|
|
159
|
|
|
if ($this->commonName->equals($commonName)) { |
160
|
|
|
return; |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
$this->commonName = $commonName; |
164
|
|
|
$this->apply(new IdentityRenamedEvent($this->id, $this->institution, $commonName)); |
165
|
|
|
} |
166
|
|
|
|
167
|
|
|
public function changeEmail(Email $email) |
168
|
|
|
{ |
169
|
|
|
$this->assertNotForgotten(); |
170
|
|
|
|
171
|
|
|
if ($this->email->equals($email)) { |
172
|
|
|
return; |
173
|
|
|
} |
174
|
|
|
|
175
|
|
|
$this->email = $email; |
176
|
|
|
$this->apply(new IdentityEmailChangedEvent($this->id, $this->institution, $email)); |
177
|
|
|
} |
178
|
|
|
|
179
|
|
|
public function bootstrapYubikeySecondFactor(SecondFactorId $secondFactorId, YubikeyPublicId $yubikeyPublicId) |
180
|
|
|
{ |
181
|
|
|
$this->assertNotForgotten(); |
182
|
|
|
$this->assertUserMayAddSecondFactor(); |
183
|
|
|
|
184
|
|
|
$this->apply( |
185
|
|
|
new YubikeySecondFactorBootstrappedEvent( |
186
|
|
|
$this->id, |
187
|
|
|
$this->nameId, |
188
|
|
|
$this->institution, |
189
|
|
|
$this->commonName, |
190
|
|
|
$this->email, |
191
|
|
|
$this->preferredLocale, |
192
|
|
|
$secondFactorId, |
193
|
|
|
$yubikeyPublicId |
194
|
|
|
) |
195
|
|
|
); |
196
|
|
|
} |
197
|
|
|
|
198
|
|
View Code Duplication |
public function provePossessionOfYubikey( |
|
|
|
|
199
|
|
|
SecondFactorId $secondFactorId, |
200
|
|
|
YubikeyPublicId $yubikeyPublicId, |
201
|
|
|
EmailVerificationWindow $emailVerificationWindow |
202
|
|
|
) { |
203
|
|
|
$this->assertNotForgotten(); |
204
|
|
|
$this->assertUserMayAddSecondFactor(); |
205
|
|
|
|
206
|
|
|
$this->apply( |
207
|
|
|
new YubikeyPossessionProvenEvent( |
208
|
|
|
$this->id, |
209
|
|
|
$this->institution, |
210
|
|
|
$secondFactorId, |
211
|
|
|
$yubikeyPublicId, |
212
|
|
|
$emailVerificationWindow, |
213
|
|
|
TokenGenerator::generateNonce(), |
214
|
|
|
$this->commonName, |
215
|
|
|
$this->email, |
216
|
|
|
$this->preferredLocale |
217
|
|
|
) |
218
|
|
|
); |
219
|
|
|
} |
220
|
|
|
|
221
|
|
View Code Duplication |
public function provePossessionOfPhone( |
|
|
|
|
222
|
|
|
SecondFactorId $secondFactorId, |
223
|
|
|
PhoneNumber $phoneNumber, |
224
|
|
|
EmailVerificationWindow $emailVerificationWindow |
225
|
|
|
) { |
226
|
|
|
$this->assertNotForgotten(); |
227
|
|
|
$this->assertUserMayAddSecondFactor(); |
228
|
|
|
|
229
|
|
|
$this->apply( |
230
|
|
|
new PhonePossessionProvenEvent( |
231
|
|
|
$this->id, |
232
|
|
|
$this->institution, |
233
|
|
|
$secondFactorId, |
234
|
|
|
$phoneNumber, |
235
|
|
|
$emailVerificationWindow, |
236
|
|
|
TokenGenerator::generateNonce(), |
237
|
|
|
$this->commonName, |
238
|
|
|
$this->email, |
239
|
|
|
$this->preferredLocale |
240
|
|
|
) |
241
|
|
|
); |
242
|
|
|
} |
243
|
|
|
|
244
|
|
View Code Duplication |
public function provePossessionOfGssf( |
|
|
|
|
245
|
|
|
SecondFactorId $secondFactorId, |
246
|
|
|
StepupProvider $provider, |
247
|
|
|
GssfId $gssfId, |
248
|
|
|
EmailVerificationWindow $emailVerificationWindow |
249
|
|
|
) { |
250
|
|
|
$this->assertNotForgotten(); |
251
|
|
|
$this->assertUserMayAddSecondFactor(); |
252
|
|
|
|
253
|
|
|
$this->apply( |
254
|
|
|
new GssfPossessionProvenEvent( |
255
|
|
|
$this->id, |
256
|
|
|
$this->institution, |
257
|
|
|
$secondFactorId, |
258
|
|
|
$provider, |
259
|
|
|
$gssfId, |
260
|
|
|
$emailVerificationWindow, |
261
|
|
|
TokenGenerator::generateNonce(), |
262
|
|
|
$this->commonName, |
263
|
|
|
$this->email, |
264
|
|
|
$this->preferredLocale |
265
|
|
|
) |
266
|
|
|
); |
267
|
|
|
} |
268
|
|
|
|
269
|
|
View Code Duplication |
public function provePossessionOfU2fDevice( |
|
|
|
|
270
|
|
|
SecondFactorId $secondFactorId, |
271
|
|
|
U2fKeyHandle $keyHandle, |
272
|
|
|
EmailVerificationWindow $emailVerificationWindow |
273
|
|
|
) { |
274
|
|
|
$this->assertNotForgotten(); |
275
|
|
|
$this->assertUserMayAddSecondFactor(); |
276
|
|
|
|
277
|
|
|
$this->apply( |
278
|
|
|
new U2fDevicePossessionProvenEvent( |
279
|
|
|
$this->id, |
280
|
|
|
$this->institution, |
281
|
|
|
$secondFactorId, |
282
|
|
|
$keyHandle, |
283
|
|
|
$emailVerificationWindow, |
284
|
|
|
TokenGenerator::generateNonce(), |
285
|
|
|
$this->commonName, |
286
|
|
|
$this->email, |
287
|
|
|
$this->preferredLocale |
288
|
|
|
) |
289
|
|
|
); |
290
|
|
|
} |
291
|
|
|
|
292
|
|
|
public function verifyEmail($verificationNonce) |
293
|
|
|
{ |
294
|
|
|
$this->assertNotForgotten(); |
295
|
|
|
|
296
|
|
|
$secondFactorToVerify = null; |
297
|
|
|
foreach ($this->unverifiedSecondFactors as $secondFactor) { |
298
|
|
|
/** @var Entity\UnverifiedSecondFactor $secondFactor */ |
299
|
|
|
if ($secondFactor->hasNonce($verificationNonce)) { |
300
|
|
|
$secondFactorToVerify = $secondFactor; |
301
|
|
|
} |
302
|
|
|
} |
303
|
|
|
|
304
|
|
|
if (!$secondFactorToVerify) { |
305
|
|
|
throw new DomainException( |
306
|
|
|
'Cannot verify second factor, no unverified second factor can be verified using the given nonce' |
307
|
|
|
); |
308
|
|
|
} |
309
|
|
|
|
310
|
|
|
/** @var Entity\UnverifiedSecondFactor $secondFactorToVerify */ |
311
|
|
|
if (!$secondFactorToVerify->canBeVerifiedNow()) { |
312
|
|
|
throw new DomainException('Cannot verify second factor, the verification window is closed.'); |
313
|
|
|
} |
314
|
|
|
|
315
|
|
|
$secondFactorToVerify->verifyEmail(); |
316
|
|
|
} |
317
|
|
|
|
318
|
|
|
public function vetSecondFactor( |
319
|
|
|
IdentityApi $registrant, |
320
|
|
|
SecondFactorId $registrantsSecondFactorId, |
321
|
|
|
SecondFactorType $registrantsSecondFactorType, |
322
|
|
|
SecondFactorIdentifier $registrantsSecondFactorIdentifier, |
|
|
|
|
323
|
|
|
$registrationCode, |
324
|
|
|
DocumentNumber $documentNumber, |
325
|
|
|
$identityVerified |
326
|
|
|
) { |
327
|
|
|
$this->assertNotForgotten(); |
328
|
|
|
|
329
|
|
|
/** @var VettedSecondFactor|null $secondFactorWithHighestLoa */ |
330
|
|
|
$secondFactorWithHighestLoa = $this->vettedSecondFactors->getSecondFactorWithHighestLoa(); |
331
|
|
|
$registrantsSecondFactor = $registrant->getVerifiedSecondFactor($registrantsSecondFactorId); |
332
|
|
|
|
333
|
|
|
if ($registrantsSecondFactor === null) { |
334
|
|
|
throw new DomainException( |
335
|
|
|
sprintf('Registrant second factor with ID %s does not exist', $registrantsSecondFactorId) |
336
|
|
|
); |
337
|
|
|
} |
338
|
|
|
|
339
|
|
|
if (!$secondFactorWithHighestLoa->hasEqualOrHigherLoaComparedTo($registrantsSecondFactor)) { |
340
|
|
|
throw new DomainException("Authority does not have the required LoA to vet the registrant's second factor"); |
341
|
|
|
} |
342
|
|
|
|
343
|
|
|
if (!$identityVerified) { |
344
|
|
|
throw new DomainException('Will not vet second factor when physical identity has not been verified.'); |
345
|
|
|
} |
346
|
|
|
|
347
|
|
|
$registrant->complyWithVettingOfSecondFactor( |
348
|
|
|
$registrantsSecondFactorId, |
349
|
|
|
$registrantsSecondFactorType, |
350
|
|
|
$registrantsSecondFactorIdentifier, |
351
|
|
|
$registrationCode, |
352
|
|
|
$documentNumber |
353
|
|
|
); |
354
|
|
|
} |
355
|
|
|
|
356
|
|
|
public function complyWithVettingOfSecondFactor( |
357
|
|
|
SecondFactorId $secondFactorId, |
358
|
|
|
SecondFactorType $secondFactorType, |
359
|
|
|
SecondFactorIdentifier $secondFactorIdentifier, |
360
|
|
|
$registrationCode, |
361
|
|
|
DocumentNumber $documentNumber |
362
|
|
|
) { |
363
|
|
|
$this->assertNotForgotten(); |
364
|
|
|
|
365
|
|
|
$secondFactorToVet = null; |
366
|
|
|
foreach ($this->verifiedSecondFactors as $secondFactor) { |
367
|
|
|
/** @var VerifiedSecondFactor $secondFactor */ |
368
|
|
|
if ($secondFactor->hasRegistrationCodeAndIdentifier($registrationCode, $secondFactorIdentifier)) { |
369
|
|
|
$secondFactorToVet = $secondFactor; |
370
|
|
|
} |
371
|
|
|
} |
372
|
|
|
|
373
|
|
|
if (!$secondFactorToVet) { |
374
|
|
|
throw new DomainException( |
375
|
|
|
'Cannot vet second factor, no verified second factor can be vetted using the given registration code ' . |
376
|
|
|
'and second factor identifier' |
377
|
|
|
); |
378
|
|
|
} |
379
|
|
|
|
380
|
|
|
if (!$secondFactorToVet->canBeVettedNow()) { |
381
|
|
|
throw new DomainException('Cannot vet second factor, the registration window is closed.'); |
382
|
|
|
} |
383
|
|
|
|
384
|
|
|
$secondFactorToVet->vet($documentNumber); |
385
|
|
|
} |
386
|
|
|
|
387
|
|
|
public function revokeSecondFactor(SecondFactorId $secondFactorId) |
388
|
|
|
{ |
389
|
|
|
$this->assertNotForgotten(); |
390
|
|
|
|
391
|
|
|
/** @var UnverifiedSecondFactor|null $unverifiedSecondFactor */ |
392
|
|
|
$unverifiedSecondFactor = $this->unverifiedSecondFactors->get((string) $secondFactorId); |
393
|
|
|
/** @var VerifiedSecondFactor|null $verifiedSecondFactor */ |
394
|
|
|
$verifiedSecondFactor = $this->verifiedSecondFactors->get((string) $secondFactorId); |
395
|
|
|
/** @var VettedSecondFactor|null $vettedSecondFactor */ |
396
|
|
|
$vettedSecondFactor = $this->vettedSecondFactors->get((string) $secondFactorId); |
397
|
|
|
|
398
|
|
|
if (!$unverifiedSecondFactor && !$verifiedSecondFactor && !$vettedSecondFactor) { |
399
|
|
|
throw new DomainException('Cannot revoke second factor: no second factor with given id exists.'); |
400
|
|
|
} |
401
|
|
|
|
402
|
|
|
if ($unverifiedSecondFactor) { |
403
|
|
|
$unverifiedSecondFactor->revoke(); |
404
|
|
|
|
405
|
|
|
return; |
406
|
|
|
} |
407
|
|
|
|
408
|
|
|
if ($verifiedSecondFactor) { |
409
|
|
|
$verifiedSecondFactor->revoke(); |
410
|
|
|
|
411
|
|
|
return; |
412
|
|
|
} |
413
|
|
|
|
414
|
|
|
$vettedSecondFactor->revoke(); |
415
|
|
|
} |
416
|
|
|
|
417
|
|
|
public function complyWithSecondFactorRevocation(SecondFactorId $secondFactorId, IdentityId $authorityId) |
418
|
|
|
{ |
419
|
|
|
$this->assertNotForgotten(); |
420
|
|
|
|
421
|
|
|
/** @var UnverifiedSecondFactor|null $unverifiedSecondFactor */ |
422
|
|
|
$unverifiedSecondFactor = $this->unverifiedSecondFactors->get((string) $secondFactorId); |
423
|
|
|
/** @var VerifiedSecondFactor|null $verifiedSecondFactor */ |
424
|
|
|
$verifiedSecondFactor = $this->verifiedSecondFactors->get((string) $secondFactorId); |
425
|
|
|
/** @var VettedSecondFactor|null $vettedSecondFactor */ |
426
|
|
|
$vettedSecondFactor = $this->vettedSecondFactors->get((string) $secondFactorId); |
427
|
|
|
|
428
|
|
|
if (!$unverifiedSecondFactor && !$verifiedSecondFactor && !$vettedSecondFactor) { |
429
|
|
|
throw new DomainException('Cannot revoke second factor: no second factor with given id exists.'); |
430
|
|
|
} |
431
|
|
|
|
432
|
|
|
if ($unverifiedSecondFactor) { |
433
|
|
|
$unverifiedSecondFactor->complyWithRevocation($authorityId); |
434
|
|
|
|
435
|
|
|
return; |
436
|
|
|
} |
437
|
|
|
|
438
|
|
|
if ($verifiedSecondFactor) { |
439
|
|
|
$verifiedSecondFactor->complyWithRevocation($authorityId); |
440
|
|
|
|
441
|
|
|
return; |
442
|
|
|
} |
443
|
|
|
|
444
|
|
|
$vettedSecondFactor->complyWithRevocation($authorityId); |
445
|
|
|
} |
446
|
|
|
|
447
|
|
|
/** |
448
|
|
|
* @param Institution $institution |
449
|
|
|
* @param RegistrationAuthorityRole $role |
450
|
|
|
* @param Location $location |
451
|
|
|
* @param ContactInformation $contactInformation |
452
|
|
|
* @return void |
453
|
|
|
*/ |
454
|
|
|
public function accreditWith( |
455
|
|
|
RegistrationAuthorityRole $role, |
456
|
|
|
Institution $institution, |
457
|
|
|
Location $location, |
458
|
|
|
ContactInformation $contactInformation |
459
|
|
|
) { |
460
|
|
|
$this->assertNotForgotten(); |
461
|
|
|
|
462
|
|
|
if (!$this->institution->equals($institution)) { |
463
|
|
|
throw new DomainException('An Identity may only be accredited within its own institution'); |
464
|
|
|
} |
465
|
|
|
|
466
|
|
|
if (!$this->vettedSecondFactors->count()) { |
467
|
|
|
throw new DomainException( |
468
|
|
|
'An Identity must have at least one vetted second factor before it can be accredited' |
469
|
|
|
); |
470
|
|
|
} |
471
|
|
|
|
472
|
|
|
if ($this->registrationAuthority) { |
473
|
|
|
throw new DomainException('Cannot accredit Identity as it has already been accredited'); |
474
|
|
|
} |
475
|
|
|
|
476
|
|
|
if ($role->equals(new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RA))) { |
477
|
|
|
$this->apply(new IdentityAccreditedAsRaEvent( |
478
|
|
|
$this->id, |
479
|
|
|
$this->nameId, |
480
|
|
|
$this->institution, |
481
|
|
|
$role, |
482
|
|
|
$location, |
483
|
|
|
$contactInformation |
484
|
|
|
)); |
485
|
|
View Code Duplication |
} elseif ($role->equals(new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RAA))) { |
|
|
|
|
486
|
|
|
$this->apply(new IdentityAccreditedAsRaaEvent( |
487
|
|
|
$this->id, |
488
|
|
|
$this->nameId, |
489
|
|
|
$this->institution, |
490
|
|
|
$role, |
491
|
|
|
$location, |
492
|
|
|
$contactInformation |
493
|
|
|
)); |
494
|
|
|
} else { |
495
|
|
|
throw new DomainException('An Identity can only be accredited with either the RA or RAA role'); |
496
|
|
|
} |
497
|
|
|
} |
498
|
|
|
|
499
|
|
|
public function amendRegistrationAuthorityInformation(Location $location, ContactInformation $contactInformation) |
500
|
|
|
{ |
501
|
|
|
$this->assertNotForgotten(); |
502
|
|
|
|
503
|
|
|
if (!$this->registrationAuthority) { |
504
|
|
|
throw new DomainException( |
505
|
|
|
'Cannot amend registration authority information: identity is not a registration authority' |
506
|
|
|
); |
507
|
|
|
} |
508
|
|
|
|
509
|
|
|
$this->apply( |
510
|
|
|
new RegistrationAuthorityInformationAmendedEvent( |
511
|
|
|
$this->id, |
512
|
|
|
$this->institution, |
513
|
|
|
$this->nameId, |
514
|
|
|
$location, |
515
|
|
|
$contactInformation |
516
|
|
|
) |
517
|
|
|
); |
518
|
|
|
} |
519
|
|
|
|
520
|
|
|
public function appointAs(RegistrationAuthorityRole $role) |
521
|
|
|
{ |
522
|
|
|
$this->assertNotForgotten(); |
523
|
|
|
|
524
|
|
|
if (!$this->registrationAuthority) { |
525
|
|
|
throw new DomainException( |
526
|
|
|
'Cannot appoint as different RegistrationAuthorityRole: identity is not a registration authority' |
527
|
|
|
); |
528
|
|
|
} |
529
|
|
|
|
530
|
|
|
if ($this->registrationAuthority->isAppointedAs($role)) { |
531
|
|
|
return; |
532
|
|
|
} |
533
|
|
|
|
534
|
|
|
if ($role->equals(new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RA))) { |
535
|
|
|
$this->apply(new AppointedAsRaEvent($this->id, $this->institution, $this->nameId)); |
536
|
|
View Code Duplication |
} elseif ($role->equals(new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RAA))) { |
|
|
|
|
537
|
|
|
$this->apply(new AppointedAsRaaEvent($this->id, $this->institution, $this->nameId)); |
538
|
|
|
} else { |
539
|
|
|
throw new DomainException('An Identity can only be appointed as either RA or RAA'); |
540
|
|
|
} |
541
|
|
|
} |
542
|
|
|
|
543
|
|
|
public function retractRegistrationAuthority() |
544
|
|
|
{ |
545
|
|
|
$this->assertNotForgotten(); |
546
|
|
|
|
547
|
|
|
if (!$this->registrationAuthority) { |
548
|
|
|
throw new DomainException( |
549
|
|
|
'Cannot Retract Registration Authority as the Identity is not a registration authority' |
550
|
|
|
); |
551
|
|
|
} |
552
|
|
|
|
553
|
|
|
$this->apply(new RegistrationAuthorityRetractedEvent( |
554
|
|
|
$this->id, |
555
|
|
|
$this->institution, |
556
|
|
|
$this->nameId, |
557
|
|
|
$this->commonName, |
558
|
|
|
$this->email |
559
|
|
|
)); |
560
|
|
|
} |
561
|
|
|
|
562
|
|
|
public function expressPreferredLocale(Locale $preferredLocale) |
563
|
|
|
{ |
564
|
|
|
$this->assertNotForgotten(); |
565
|
|
|
|
566
|
|
|
if ($this->preferredLocale === $preferredLocale) { |
567
|
|
|
return; |
568
|
|
|
} |
569
|
|
|
|
570
|
|
|
$this->apply(new LocalePreferenceExpressedEvent($this->id, $this->institution, $preferredLocale)); |
571
|
|
|
} |
572
|
|
|
|
573
|
|
|
public function forget() |
574
|
|
|
{ |
575
|
|
|
$this->assertNotForgotten(); |
576
|
|
|
|
577
|
|
|
if ($this->registrationAuthority) { |
578
|
|
|
throw new DomainException('Cannot forget an identity that is currently accredited as an RA(A)'); |
579
|
|
|
} |
580
|
|
|
|
581
|
|
|
$this->apply(new IdentityForgottenEvent($this->id, $this->institution)); |
582
|
|
|
} |
583
|
|
|
|
584
|
|
|
protected function applyIdentityCreatedEvent(IdentityCreatedEvent $event) |
585
|
|
|
{ |
586
|
|
|
$this->id = $event->identityId; |
587
|
|
|
$this->institution = $event->identityInstitution; |
588
|
|
|
$this->nameId = $event->nameId; |
589
|
|
|
$this->commonName = $event->commonName; |
590
|
|
|
$this->email = $event->email; |
591
|
|
|
$this->preferredLocale = $event->preferredLocale; |
592
|
|
|
$this->forgotten = false; |
593
|
|
|
|
594
|
|
|
$this->unverifiedSecondFactors = new SecondFactorCollection(); |
595
|
|
|
$this->verifiedSecondFactors = new SecondFactorCollection(); |
596
|
|
|
$this->vettedSecondFactors = new SecondFactorCollection(); |
597
|
|
|
} |
598
|
|
|
|
599
|
|
|
public function applyIdentityRenamedEvent(IdentityRenamedEvent $event) |
600
|
|
|
{ |
601
|
|
|
$this->commonName = $event->commonName; |
602
|
|
|
} |
603
|
|
|
|
604
|
|
|
public function applyIdentityEmailChangedEvent(IdentityEmailChangedEvent $event) |
605
|
|
|
{ |
606
|
|
|
$this->email = $event->email; |
607
|
|
|
} |
608
|
|
|
|
609
|
|
|
protected function applyYubikeySecondFactorBootstrappedEvent(YubikeySecondFactorBootstrappedEvent $event) |
610
|
|
|
{ |
611
|
|
|
$secondFactor = VettedSecondFactor::create( |
612
|
|
|
$event->secondFactorId, |
613
|
|
|
$this, |
614
|
|
|
new SecondFactorType('yubikey'), |
615
|
|
|
$event->yubikeyPublicId |
616
|
|
|
); |
617
|
|
|
|
618
|
|
|
$this->vettedSecondFactors->set((string) $secondFactor->getId(), $secondFactor); |
619
|
|
|
} |
620
|
|
|
|
621
|
|
View Code Duplication |
protected function applyYubikeyPossessionProvenEvent(YubikeyPossessionProvenEvent $event) |
|
|
|
|
622
|
|
|
{ |
623
|
|
|
$secondFactor = UnverifiedSecondFactor::create( |
624
|
|
|
$event->secondFactorId, |
625
|
|
|
$this, |
626
|
|
|
new SecondFactorType('yubikey'), |
627
|
|
|
$event->yubikeyPublicId, |
628
|
|
|
$event->emailVerificationWindow, |
629
|
|
|
$event->emailVerificationNonce |
630
|
|
|
); |
631
|
|
|
|
632
|
|
|
$this->unverifiedSecondFactors->set((string) $secondFactor->getId(), $secondFactor); |
633
|
|
|
} |
634
|
|
|
|
635
|
|
View Code Duplication |
protected function applyPhonePossessionProvenEvent(PhonePossessionProvenEvent $event) |
|
|
|
|
636
|
|
|
{ |
637
|
|
|
$secondFactor = UnverifiedSecondFactor::create( |
638
|
|
|
$event->secondFactorId, |
639
|
|
|
$this, |
640
|
|
|
new SecondFactorType('sms'), |
641
|
|
|
$event->phoneNumber, |
642
|
|
|
$event->emailVerificationWindow, |
643
|
|
|
$event->emailVerificationNonce |
644
|
|
|
); |
645
|
|
|
|
646
|
|
|
$this->unverifiedSecondFactors->set((string) $secondFactor->getId(), $secondFactor); |
647
|
|
|
} |
648
|
|
|
|
649
|
|
View Code Duplication |
protected function applyGssfPossessionProvenEvent(GssfPossessionProvenEvent $event) |
|
|
|
|
650
|
|
|
{ |
651
|
|
|
$secondFactor = UnverifiedSecondFactor::create( |
652
|
|
|
$event->secondFactorId, |
653
|
|
|
$this, |
654
|
|
|
new SecondFactorType((string) $event->stepupProvider), |
655
|
|
|
$event->gssfId, |
656
|
|
|
$event->emailVerificationWindow, |
657
|
|
|
$event->emailVerificationNonce |
658
|
|
|
); |
659
|
|
|
|
660
|
|
|
$this->unverifiedSecondFactors->set((string) $secondFactor->getId(), $secondFactor); |
661
|
|
|
} |
662
|
|
|
|
663
|
|
View Code Duplication |
protected function applyU2fDevicePossessionProvenEvent(U2fDevicePossessionProvenEvent $event) |
|
|
|
|
664
|
|
|
{ |
665
|
|
|
$secondFactor = UnverifiedSecondFactor::create( |
666
|
|
|
$event->secondFactorId, |
667
|
|
|
$this, |
668
|
|
|
new SecondFactorType('u2f'), |
669
|
|
|
$event->keyHandle, |
670
|
|
|
$event->emailVerificationWindow, |
671
|
|
|
$event->emailVerificationNonce |
672
|
|
|
); |
673
|
|
|
|
674
|
|
|
$this->unverifiedSecondFactors->set((string) $secondFactor->getId(), $secondFactor); |
675
|
|
|
} |
676
|
|
|
|
677
|
|
|
protected function applyEmailVerifiedEvent(EmailVerifiedEvent $event) |
678
|
|
|
{ |
679
|
|
|
$secondFactorId = (string) $event->secondFactorId; |
680
|
|
|
|
681
|
|
|
/** @var UnverifiedSecondFactor $unverified */ |
682
|
|
|
$unverified = $this->unverifiedSecondFactors->get($secondFactorId); |
683
|
|
|
$verified = $unverified->asVerified($event->registrationRequestedAt, $event->registrationCode); |
684
|
|
|
|
685
|
|
|
$this->unverifiedSecondFactors->remove($secondFactorId); |
686
|
|
|
$this->verifiedSecondFactors->set($secondFactorId, $verified); |
687
|
|
|
} |
688
|
|
|
|
689
|
|
|
protected function applySecondFactorVettedEvent(SecondFactorVettedEvent $event) |
690
|
|
|
{ |
691
|
|
|
$secondFactorId = (string) $event->secondFactorId; |
692
|
|
|
|
693
|
|
|
/** @var VerifiedSecondFactor $verified */ |
694
|
|
|
$verified = $this->verifiedSecondFactors->get($secondFactorId); |
695
|
|
|
$vetted = $verified->asVetted(); |
696
|
|
|
|
697
|
|
|
$this->verifiedSecondFactors->remove($secondFactorId); |
698
|
|
|
$this->vettedSecondFactors->set($secondFactorId, $vetted); |
699
|
|
|
} |
700
|
|
|
|
701
|
|
|
protected function applyUnverifiedSecondFactorRevokedEvent(UnverifiedSecondFactorRevokedEvent $event) |
702
|
|
|
{ |
703
|
|
|
$this->unverifiedSecondFactors->remove((string) $event->secondFactorId); |
704
|
|
|
} |
705
|
|
|
|
706
|
|
|
protected function applyCompliedWithUnverifiedSecondFactorRevocationEvent( |
707
|
|
|
CompliedWithUnverifiedSecondFactorRevocationEvent $event |
708
|
|
|
) { |
709
|
|
|
$this->unverifiedSecondFactors->remove((string) $event->secondFactorId); |
710
|
|
|
} |
711
|
|
|
|
712
|
|
|
protected function applyVerifiedSecondFactorRevokedEvent(VerifiedSecondFactorRevokedEvent $event) |
713
|
|
|
{ |
714
|
|
|
$this->verifiedSecondFactors->remove((string) $event->secondFactorId); |
715
|
|
|
} |
716
|
|
|
|
717
|
|
|
protected function applyCompliedWithVerifiedSecondFactorRevocationEvent( |
718
|
|
|
CompliedWithVerifiedSecondFactorRevocationEvent $event |
719
|
|
|
) { |
720
|
|
|
$this->verifiedSecondFactors->remove((string) $event->secondFactorId); |
721
|
|
|
} |
722
|
|
|
|
723
|
|
|
protected function applyVettedSecondFactorRevokedEvent(VettedSecondFactorRevokedEvent $event) |
724
|
|
|
{ |
725
|
|
|
$this->vettedSecondFactors->remove((string) $event->secondFactorId); |
726
|
|
|
} |
727
|
|
|
|
728
|
|
|
protected function applyCompliedWithVettedSecondFactorRevocationEvent( |
729
|
|
|
CompliedWithVettedSecondFactorRevocationEvent $event |
730
|
|
|
) { |
731
|
|
|
$this->vettedSecondFactors->remove((string) $event->secondFactorId); |
732
|
|
|
} |
733
|
|
|
|
734
|
|
|
protected function applyIdentityAccreditedAsRaEvent(IdentityAccreditedAsRaEvent $event) |
735
|
|
|
{ |
736
|
|
|
$this->registrationAuthority = RegistrationAuthority::accreditWith( |
737
|
|
|
$event->registrationAuthorityRole, |
738
|
|
|
$event->location, |
739
|
|
|
$event->contactInformation |
740
|
|
|
); |
741
|
|
|
} |
742
|
|
|
|
743
|
|
|
protected function applyIdentityAccreditedAsRaaEvent(IdentityAccreditedAsRaaEvent $event) |
744
|
|
|
{ |
745
|
|
|
$this->registrationAuthority = RegistrationAuthority::accreditWith( |
746
|
|
|
$event->registrationAuthorityRole, |
747
|
|
|
$event->location, |
748
|
|
|
$event->contactInformation |
749
|
|
|
); |
750
|
|
|
} |
751
|
|
|
|
752
|
|
|
protected function applyRegistrationAuthorityInformationAmendedEvent( |
753
|
|
|
RegistrationAuthorityInformationAmendedEvent $event |
754
|
|
|
) { |
755
|
|
|
$this->registrationAuthority->amendInformation($event->location, $event->contactInformation); |
756
|
|
|
} |
757
|
|
|
|
758
|
|
|
protected function applyAppointedAsRaEvent(AppointedAsRaEvent $event) |
|
|
|
|
759
|
|
|
{ |
760
|
|
|
$this->registrationAuthority->appointAs(new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RA)); |
761
|
|
|
} |
762
|
|
|
|
763
|
|
|
protected function applyAppointedAsRaaEvent(AppointedAsRaaEvent $event) |
|
|
|
|
764
|
|
|
{ |
765
|
|
|
$this->registrationAuthority->appointAs(new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RAA)); |
766
|
|
|
} |
767
|
|
|
|
768
|
|
|
protected function applyRegistrationAuthorityRetractedEvent(RegistrationAuthorityRetractedEvent $event) |
|
|
|
|
769
|
|
|
{ |
770
|
|
|
$this->registrationAuthority = null; |
771
|
|
|
} |
772
|
|
|
|
773
|
|
|
protected function applyLocalePreferenceExpressedEvent(LocalePreferenceExpressedEvent $event) |
774
|
|
|
{ |
775
|
|
|
$this->preferredLocale = $event->preferredLocale; |
776
|
|
|
} |
777
|
|
|
|
778
|
|
|
protected function applyIdentityForgottenEvent(IdentityForgottenEvent $event) |
|
|
|
|
779
|
|
|
{ |
780
|
|
|
$this->commonName = CommonName::unknown(); |
781
|
|
|
$this->email = Email::unknown(); |
782
|
|
|
$this->forgotten = true; |
783
|
|
|
} |
784
|
|
|
|
785
|
|
|
public function getAggregateRootId() |
786
|
|
|
{ |
787
|
|
|
return $this->id; |
788
|
|
|
} |
789
|
|
|
|
790
|
|
|
protected function getChildEntities() |
791
|
|
|
{ |
792
|
|
|
return array_merge( |
793
|
|
|
$this->unverifiedSecondFactors->getValues(), |
794
|
|
|
$this->verifiedSecondFactors->getValues(), |
795
|
|
|
$this->vettedSecondFactors->getValues() |
796
|
|
|
); |
797
|
|
|
} |
798
|
|
|
|
799
|
|
|
/** |
800
|
|
|
* @throws DomainException |
801
|
|
|
*/ |
802
|
|
|
private function assertNotForgotten() |
803
|
|
|
{ |
804
|
|
|
if ($this->forgotten) { |
805
|
|
|
throw new DomainException('Operation on this Identity is not allowed: it has been forgotten'); |
806
|
|
|
} |
807
|
|
|
} |
808
|
|
|
|
809
|
|
|
/** |
810
|
|
|
* @throws DomainException |
811
|
|
|
*/ |
812
|
|
|
private function assertUserMayAddSecondFactor() |
813
|
|
|
{ |
814
|
|
|
if (count($this->unverifiedSecondFactors) + |
815
|
|
|
count($this->verifiedSecondFactors) + |
816
|
|
|
count($this->vettedSecondFactors) > 0 |
817
|
|
|
) { |
818
|
|
|
throw new DomainException('User may not have more than one token'); |
819
|
|
|
} |
820
|
|
|
} |
821
|
|
|
|
822
|
|
|
public function getId() |
823
|
|
|
{ |
824
|
|
|
return $this->id; |
825
|
|
|
} |
826
|
|
|
|
827
|
|
|
/** |
828
|
|
|
* @return NameId |
829
|
|
|
*/ |
830
|
|
|
public function getNameId() |
831
|
|
|
{ |
832
|
|
|
return $this->nameId; |
833
|
|
|
} |
834
|
|
|
|
835
|
|
|
/** |
836
|
|
|
* @return Institution |
837
|
|
|
*/ |
838
|
|
|
public function getInstitution() |
839
|
|
|
{ |
840
|
|
|
return $this->institution; |
841
|
|
|
} |
842
|
|
|
|
843
|
|
|
public function getCommonName() |
844
|
|
|
{ |
845
|
|
|
return $this->commonName; |
846
|
|
|
} |
847
|
|
|
|
848
|
|
|
public function getEmail() |
849
|
|
|
{ |
850
|
|
|
return $this->email; |
851
|
|
|
} |
852
|
|
|
|
853
|
|
|
public function getPreferredLocale() |
854
|
|
|
{ |
855
|
|
|
return $this->preferredLocale; |
856
|
|
|
} |
857
|
|
|
|
858
|
|
|
/** |
859
|
|
|
* @param SecondFactorId $secondFactorId |
860
|
|
|
* @return VerifiedSecondFactor|null |
861
|
|
|
*/ |
862
|
|
|
public function getVerifiedSecondFactor(SecondFactorId $secondFactorId) |
863
|
|
|
{ |
864
|
|
|
return $this->verifiedSecondFactors->get((string) $secondFactorId); |
865
|
|
|
} |
866
|
|
|
} |
867
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.