Passed
Push — master ( e54397...67b039 )
by Jaime Pérez
02:07
created

Database::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 5
rs 10
1
<?php
2
3
namespace SimpleSAML\Module\webauthn\WebAuthn\Store;
4
5
use SimpleSAML\Configuration;
6
use SimpleSAML\Logger;
7
8
/**
9
 * Store FIDO2 information in database.
10
 *
11
 * This class implements a store which stores the FIDO2 information in a
12
 * database. It is tested with MySQL, others might work, too.
13
 *
14
 * It has the following options:
15
 * - dsn: The DSN which should be used to connect to the database server. See
16
 *   the PHP Manual for supported drivers and DSN formats.
17
 * - username: The username used for database connection.
18
 * - password: The password used for database connection.
19
 *
20
 * @author Stefan Winter <[email protected]>
21
 * @package SimpleSAMLphp
22
 */
23
24
class Database extends Store
0 ignored issues
show
Bug introduced by
The type SimpleSAML\Module\webauthn\WebAuthn\Store\Store was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
25
{
26
    /**
27
     * Database handle.
28
     *
29
     * This variable can't be serialized.
30
     */
31
    private $db;
32
33
34
    /**
35
     * The configuration for our database store.
36
     *
37
     * @var array
38
     */
39
    private $config;
40
41
42
    /**
43
     * Parse configuration.
44
     *
45
     * This constructor parses the configuration.
46
     *
47
     * @param array $config Configuration for database consent store.
48
     *
49
     * @throws \Exception in case of a configuration error.
50
     */
51
    public function __construct(array $config)
52
    {
53
        parent::__construct($config);
54
        $this->config = $config;
55
        $this->db = \SimpleSAML\Database::getInstance(Configuration::loadFromArray($config));
56
    }
57
58
    /**
59
     * Called before serialization.
60
     *
61
     * @return array The variables which should be serialized.
62
     */
63
    public function __sleep() : array
64
    {
65
        return [
66
            'config',
67
        ];
68
    }
69
70
71
    /**
72
     * Called after unserialization.
73
     */
74
    public function __wakeup()
75
    {
76
        $this->db = \SimpleSAML\Database::getInstance(Configuration::loadFromArray($this->config));
77
    }
78
79
80
    /**
81
     * is the user subject to 2nd factor at all?
82
     *
83
     * This function checks whether a given user has been enabled for WebAuthn.
84
     *
85
     * @param string $userId        The hash identifying the user at an IdP.
86
     * @param bool   $defaultIfNx   if not found in the DB, should the user be considered enabled (true) or disabled(false)
87
     *
88
     * @return bool True if the user is enabled for 2FA, false if not
89
     */
90
    public function is2FAEnabled(string $userId, bool $defaultIfNx) : bool
91
    {
92
        $st = $this->db->read('SELECT fido2Status FROM userstatus WHERE user_id = :userId', ['userId' => $userId]);
93
94
        if ($st === false) {
95
            return false;
96
        }
97
98
        $rowCount = $st->rowCount();
99
        if ($rowCount === 0) {
100
            Logger::debug('User does not exist in DB, returning desired default.');
101
            return $defaultIfNx;
102
        } else {
103
            $st2 = $this->db->read(
104
                'SELECT fido2Status FROM userstatus WHERE user_id = :userId AND fido2Status = "FIDO2Disabled"',
105
                ['userId' => $userId]
106
            );
107
            $rowCount2 = $st2->rowCount();
108
            if ($rowCount2 === 1 /* explicitly disabled user in DB */) {
109
                return false;
110
            }
111
            Logger::debug('User exists and is not disabled -> enabled.');
112
            return true;
113
        }
114
    }
115
116
117
    /**
118
     * does a given credentialID already exist?
119
     *
120
     * This function checks whether a given credential ID already exists in the database
121
     *
122
     * @param string $credIdHex The hex representation of the credentialID to look for.
123
     *
124
     * @return bool True if the credential exists, false if not
125
     */
126
    public function doesCredentialExist(string $credIdHex) : bool
127
    {
128
        $st = $this->db->read(
129
            'SELECT credentialId FROM credentials WHERE credentialId = :credentialId',
130
            ['credentialId' => $credIdHex]
131
        );
132
133
        if ($st === false) {
134
            return false;
135
        }
136
137
        $rowCount = $st->rowCount();
138
        if ($rowCount === 0) {
139
            Logger::debug('Credential does not exist yet.');
140
            return false;
141
        } else {
142
            Logger::debug('Credential exists.');
143
            return true;
144
        }
145
    }
146
147
148
    /**
149
     * store newly enrolled token data
150
     *
151
     * @param string $userId        The user.
152
     * @param string $credentialId  The id identifying the credential.
153
     * @param string $credential    The credential.
154
     * @param int    $signCounter   The signature counter for this credential.
155
     * @param string $friendlyName  A user-supplied name for this token.
156
     *
157
     * @return true
158
     */
159
    public function storeTokenData(string $userId, string $credentialId, string $credential, int $signCounter, string $friendlyName) : bool
160
    {
161
        $st = $this->db->write(
162
                'INSERT INTO credentials ' .
163
                '(user_id, credentialId, credential, signCounter, friendlyName) VALUES (:userId,:credentialId,'.
164
                ':credential,:signCounter,:friendlyName)',
165
                [
166
                    'userId' => $userId,
167
                    'credentialId' => $credentialId,
168
                    'credential' => $credential,
169
                    'signCounter' => $signCounter,
170
                    'friendlyName' => $friendlyName
171
                ]
172
        );
173
174
        if ($st === false) {
175
            throw new \Exception("Unable to save new token in database!");
176
        }
177
178
        return true;
179
    }
180
181
182
    /**
183
     * remove an existing credential from the database
184
     *
185
     * @param string $credentialId the credential
186
     * @return true
187
     */
188
    public function deleteTokenData(string $credentialId) : bool
189
    {
190
        $st = $this->db->write(
191
                'DELETE FROM credentials WHERE credentialId = :credentialId',
192
                ['credentialId' => $credentialId]
193
        );
194
195
        if ($st !== false) {
196
            Logger::debug('webauthn:Database - DELETED credential.');
197
        } else {
198
            throw new \Exception("Database execution did not work.");
199
        }
200
        return true;
201
    }
202
203
204
    /**
205
     * increment the signature counter after a successful authentication
206
     *
207
     * @param string $credentialId the credential
208
     * @param int    $signCounter  the new counter value
209
     * @return true
210
     */
211
    public function updateSignCount(string $credentialId, int $signCounter) : bool
212
    {
213
        $st = $this->db->write(
214
                'UPDATE credentials SET signCounter = :signCounter WHERE credentialId = :credentialId',
215
                ['signCounter' => $signCounter, 'credentialId' => $credentialId]
216
        );
217
218
        if ($st !== false) {
219
            Logger::debug('webauthn:Database - UPDATED signature counter.');
220
        } else {
221
            throw new \Exception("Database execution did not work.");
222
        }
223
        return true;
224
    }
225
226
227
    /**
228
     * Retrieve existing token data
229
     *
230
     * @param string $userId the username
231
     * @return array Array of all crypto data we have on file.
232
     */
233
    public function getTokenData(string $userId) : array
234
    {
235
        $ret = [];
236
237
        $st = $this->db->read(
238
                'SELECT credentialId, credential, signCounter, friendlyName FROM credentials WHERE user_id = :userId',
239
                ['userId' => $userId]
240
        );
241
242
        if ($st === false) {
243
            return [];
244
        }
245
246
        while ($row = $st->fetch(\PDO::FETCH_NUM)) {
247
            $ret[] = $row;
248
        }
249
250
        return $ret;
251
    }
252
}
253