CredentialRepository   A
last analyzed

Complexity

Total Complexity 22

Size/Duplication

Total Lines 189
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 47
c 2
b 0
f 0
dl 0
loc 189
rs 10
wmc 22

18 Methods

Rating   Name   Duplication   Size   Complexity  
A has() 0 3 1
A __construct() 0 3 1
A hasChanged() 0 3 1
A serialize() 0 3 1
A updateCounterFor() 0 6 1
A getCounterFor() 0 5 1
A getUserHandleFor() 0 5 1
A unserialize() 0 6 1
A saveCredentialSource() 0 12 2
A findOneByCredentialId() 0 8 2
A findAllForUserEntity() 0 10 2
A get() 0 5 1
A fromArray() 0 6 1
A setCredentials() 0 6 1
A reset() 0 3 1
A toArray() 0 3 1
A assertCredentialID() 0 4 2
A getCredentialIDRef() 0 3 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SilverStripe\WebAuthn;
6
7
use InvalidArgumentException;
8
use Serializable;
9
use Webauthn\AttestedCredentialData;
10
use Webauthn\PublicKeyCredentialSource;
11
use Webauthn\PublicKeyCredentialSourceRepository;
12
use Webauthn\PublicKeyCredentialUserEntity;
13
14
/**
15
 * Implements the required interface from the WebAuthn library - but it does not implement the repository pattern in the
16
 * usual way. This is expected to be stored on a DataObject for persistence. Use the
17
 * @see CredentialRepository::hasChanged() API for determining whether a DataObject this is stored upon should be
18
 * persisted.
19
 */
20
class CredentialRepository implements PublicKeyCredentialSourceRepository, Serializable
21
{
22
    /**
23
     * @var string
24
     */
25
    private $memberID;
26
27
    /**
28
     * @var array
29
     */
30
    private $credentials = [];
31
32
    /**
33
     * @var bool
34
     */
35
    private $hasChanged = false;
36
37
    /**
38
     * @param string $memberID
39
     */
40
    public function __construct(string $memberID)
41
    {
42
        $this->memberID = $memberID;
43
    }
44
45
    public function has(string $credentialId): bool
46
    {
47
        return $this->findOneByCredentialId($credentialId) !== null;
48
    }
49
50
    public function get(string $credentialId): AttestedCredentialData
51
    {
52
        $this->assertCredentialID($credentialId);
53
54
        return $this->findOneByCredentialId($credentialId)->getAttestedCredentialData();
55
    }
56
57
    public function getUserHandleFor(string $credentialId): string
58
    {
59
        $this->assertCredentialID($credentialId);
60
61
        return $this->memberID;
62
    }
63
64
    public function getCounterFor(string $credentialId): int
65
    {
66
        $this->assertCredentialID($credentialId);
67
68
        return (int) $this->credentials[$this->getCredentialIDRef($credentialId)]['counter'];
69
    }
70
71
    public function updateCounterFor(string $credentialId, int $newCounter): void
72
    {
73
        $this->assertCredentialID($credentialId);
74
75
        $this->credentials[$this->getCredentialIDRef($credentialId)]['counter'] = $newCounter;
76
        $this->hasChanged = true;
77
    }
78
79
    /**
80
     * Assert that the given credential ID matches a stored credential
81
     *
82
     * @param string $credentialId
83
     */
84
    protected function assertCredentialID(string $credentialId): void
85
    {
86
        if (!$this->has($credentialId)) {
87
            throw new InvalidArgumentException('Given credential ID does not match any stored credentials');
88
        }
89
    }
90
91
    public function findOneByCredentialId(string $publicKeyCredentialId): ?PublicKeyCredentialSource
92
    {
93
        $ref = $this->getCredentialIDRef($publicKeyCredentialId);
94
        if (!isset($this->credentials[$ref])) {
95
            return null;
96
        }
97
98
        return $this->credentials[$ref]['source'];
99
    }
100
101
    /**
102
     * @return PublicKeyCredentialSource[]
103
     */
104
    public function findAllForUserEntity(PublicKeyCredentialUserEntity $publicKeyCredentialUserEntity): array
105
    {
106
        // Only return credentials if the user entity shares the same ID.
107
        if ($publicKeyCredentialUserEntity->getId() !== $this->memberID) {
108
            return [];
109
        }
110
111
        return array_map(function ($credentialComposite) {
112
            return $credentialComposite['source'];
113
        }, $this->credentials);
114
    }
115
116
    public function saveCredentialSource(PublicKeyCredentialSource $publicKeyCredentialSource): void
117
    {
118
        $ref = $this->getCredentialIDRef($publicKeyCredentialSource->getPublicKeyCredentialId());
119
120
        if (!isset($this->credentials[$ref])) {
121
            $this->credentials[$ref] = [
122
                'counter' => 0,
123
            ];
124
        }
125
126
        $this->credentials[$ref]['source'] = $publicKeyCredentialSource;
127
        $this->hasChanged = true;
128
    }
129
130
    /**
131
     * Empty the store deleting all stored credentials
132
     */
133
    public function reset(): void
134
    {
135
        $this->credentials = [];
136
    }
137
138
    /**
139
     * Indicates the repository has changed and should be persisted (as this doesn't follow the actual repository
140
     * pattern and is expected to be stored on a dataobject for persistence)
141
     *
142
     * @return bool
143
     */
144
    public function hasChanged(): bool
145
    {
146
        return $this->hasChanged;
147
    }
148
149
    /**
150
     * Set the credentials in bulk (for internal use) ensuring that credential objects are initialised correctly
151
     *
152
     * @param array $credentials
153
     */
154
    protected function setCredentials(array $credentials): void
155
    {
156
        $this->credentials = array_map(function ($data) {
157
            $data['source'] = PublicKeyCredentialSource::createFromArray($data['source']);
158
            return $data;
159
        }, $credentials);
160
    }
161
162
    /**
163
     * Create a reference to be used as a key for the credentials in the array
164
     *
165
     * @param string $credentialID
166
     * @return string
167
     */
168
    protected function getCredentialIDRef(string $credentialID): string
169
    {
170
        return base64_encode($credentialID);
171
    }
172
173
    /**
174
     * Provide the credentials stored in this repository as an array
175
     *
176
     * @return array
177
     */
178
    public function toArray(): array
179
    {
180
        return $this->credentials;
181
    }
182
183
    /**
184
     * Create an instance of a repository from the given credentials
185
     *
186
     * @param array $credentials
187
     * @param string $memberID
188
     * @return CredentialRepository
189
     */
190
    public static function fromArray(array $credentials, string $memberID): self
191
    {
192
        $new = new static($memberID);
193
        $new->setCredentials($credentials);
194
195
        return $new;
196
    }
197
198
    public function serialize()
199
    {
200
        return json_encode(['credentials' => $this->toArray(), 'memberID' => $this->memberID]);
201
    }
202
203
    public function unserialize($serialized)
204
    {
205
        $raw = json_decode($serialized, true);
206
207
        $this->memberID = $raw['memberID'];
208
        $this->setCredentials($raw['credentials']);
209
    }
210
}
211