Passed
Push — develop ( e2cf42...37d2d6 )
by Baptiste
03:06
created

Identity::identity()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
declare(strict_types = 1);
3
4
namespace PersonalGalaxy\Identity\Entity;
5
6
use PersonalGalaxy\Identity\{
7
    Entity\Identity\Email,
8
    Entity\Identity\Password,
9
    Entity\Identity\SecretKey,
10
    Entity\Identity\RecoveryCode,
11
    Event\IdentityWasCreated,
12
    Event\Identity\PasswordWasChanged,
13
    Event\Identity\TwoFactorAuthenticationWasEnabled,
14
    Event\Identity\TwoFactorAuthenticationWasDisabled,
15
    Event\Identity\RecoveryCodeWasUsed,
16
    Event\IdentityWasDeleted,
17
    TwoFactorAuthentication\Code,
18
    Exception\LogicException,
19
};
20
use Innmind\EventBus\{
21
    ContainsRecordedEventsInterface,
22
    EventRecorder,
23
};
24
use Innmind\Immutable\Set;
25
use ParagonIE\MultiFactor\{
26
    OTP\OTPInterface,
27
    FIDOU2F,
28
};
29
30
final class Identity implements ContainsRecordedEventsInterface
31
{
32
    use EventRecorder;
33
34
    private $identity;
35
    private $email;
36
    private $password;
37
    private $secretKey;
38
    private $recoveryCodes;
39
40 15
    private function __construct(
41
        Identity\Identity $identity,
42
        Email $email,
43
        Password $password
44
    ) {
45 15
        $this->identity = $identity;
46 15
        $this->email = $email;
47 15
        $this->password = $password;
48 15
    }
49
50 15
    public static function create(
51
        Identity\Identity $identity,
52
        Email $email,
53
        Password $password
54
    ): self {
55 15
        $self = new self($identity, $email, $password);
56 15
        $self->record(new IdentityWasCreated($identity, $email));
57
58 15
        return $self;
59
    }
60
61 5
    public function identity(): Identity\Identity
62
    {
63 5
        return $this->identity;
64
    }
65
66 2
    public function email(): Email
67
    {
68 2
        return $this->email;
69
    }
70
71 2
    public function changePassword(Password $password): self
72
    {
73 2
        $this->password = $password;
74 2
        $this->record(new PasswordWasChanged($this->identity));
75
76 2
        return $this;
77
    }
78
79 6
    public function verify(string $password): bool
80
    {
81 6
        return $this->password->verify($password);
82
    }
83
84 6
    public function twoFactorAuthenticationEnabled(): bool
85
    {
86 6
        return $this->secretKey instanceof SecretKey;
87
    }
88
89 6
    public function enableTwoFactorAuthentication(): self
90
    {
91 6
        $this->secretKey = new SecretKey;
92 6
        $this->recoveryCodes = Set::of(
93 6
            RecoveryCode::class,
94 6
            new RecoveryCode,
95 6
            new RecoveryCode,
96 6
            new RecoveryCode,
97 6
            new RecoveryCode,
98 6
            new RecoveryCode,
99 6
            new RecoveryCode,
100 6
            new RecoveryCode,
101 6
            new RecoveryCode,
102 6
            new RecoveryCode,
103 6
            new RecoveryCode
104
        );
105 6
        $this->record(new TwoFactorAuthenticationWasEnabled(
106 6
            $this->identity,
107 6
            $this->secretKey,
108 6
            $this->recoveryCodes
109
        ));
110
111 6
        return $this;
112
    }
113
114 2
    public function disableTwoFactorAuthentication(): self
115
    {
116 2
        $this->secretKey = null;
117 2
        $this->recoveryCodes = null;
118 2
        $this->record(new TwoFactorAuthenticationWasDisabled($this->identity));
119
120 2
        return $this;
121
    }
122
123 4
    public function validate(Code $code, OTPInterface $otp = null): bool
124
    {
125 4
        if (!$this->twoFactorAuthenticationEnabled()) {
126 1
            throw new LogicException;
127
        }
128
129 4
        $factor = new FIDOU2F((string) $this->secretKey, $otp);
130
131 4
        if ($factor->validateCode((string) $code)) {
132 1
            return true;
133
        }
134
135
        $codes = $this
136 4
            ->recoveryCodes
137
            ->filter(static function(RecoveryCode $recoveryCode) use ($code): bool {
138 4
                return $recoveryCode->equals($code);
139 4
            });
140
141 4
        if ($codes->size() === 0) {
142 3
            return false;
143
        }
144
145 2
        $this->recoveryCodes = $this->recoveryCodes->remove($codes->current());
146 2
        $this->record(new RecoveryCodeWasUsed($this->identity));
147
148 2
        return true;
149
    }
150
151 2
    public function delete(): void
152
    {
153 2
        $this->record(new IdentityWasDeleted($this->identity));
154 2
    }
155
}
156