Passed
Pull Request — develop (#40)
by Pieter van der
02:45
created

Tiqr_UserStorage_Pdo::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2.0811

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 11
c 3
b 0
f 0
dl 0
loc 14
ccs 8
cts 11
cp 0.7272
rs 9.9
cc 2
nc 2
nop 2
crap 2.0811
1
<?php
2
/**
3
 * This file is part of the tiqr project.
4
 * 
5
 * The tiqr project aims to provide an open implementation for 
6
 * authentication using mobile devices. It was initiated by 
7
 * SURFnet and developed by Egeniq.
8
 *
9
 * More information: http://www.tiqr.org
10
 *
11
 * @author Patrick Honing <[email protected]>
12
 * 
13
 * @package tiqr
14
 *
15
 * @license New BSD License - See LICENSE file for details.
16
 *
17
 * @copyright (C) 2010-2012 SURFnet BV
18
 * 
19
For MySQL:
20
21
CREATE TABLE IF NOT EXISTS user (
22
    id integer NOT NULL PRIMARY KEY AUTO_INCREMENT,
23
    userid varchar(30) NOT NULL UNIQUE,
24
    displayname varchar(30) NOT NULL,
25
    secret varchar(128),        // Optional column, see Tiqr_UserSecretStorage_Pdo
26
    loginattempts integer,
27
    tmpblocktimestamp BIGINT,   // 8-byte integer
28
    tmpblockattempts INT,       // 4-byte integer
29
    blocked tinyint(1),
30
    notificationtype varchar(10),
31
    notificationaddress varchar(64)
32
);
33
34
 */
35
36
use Psr\Log\LoggerInterface;
37
38
39
/**
40
 * This user storage implementation implements a user storage using PDO.
41
 * It is usable for any database with a PDO driver
42
 * 
43
 * @author Patrick Honing <[email protected]>
44
 */
45
class Tiqr_UserStorage_Pdo extends Tiqr_UserStorage_Abstract
46
{
47
    protected $handle = null;
48
    protected $tablename;
49
50
    private $_allowedStringColumns = ['displayname', 'notificationtype', 'notificationaddress'];
51
    private $_allowedIntColumns = ['loginattempts', 'tmpblockattempts', 'blocked', 'tmpblocktimestamp'];
52
    
53
    /**
54
     * Create an instance
55
     * @param array $config
56
     * @param array $secretconfig
57
     * @throws Exception
58
     */
59 2
    public function __construct(array $config, LoggerInterface $logger)
60
    {
61 2
        parent::__construct($config, $logger);
62 2
        $this->tablename = $config['table'] ?? 'tiqruser';
63
        try {
64 2
            $this->handle = new PDO(
65 2
                $config['dsn'],
66 2
                $config['username'],
67 2
                $config['password'],
68 2
                array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION)
69
            );
70
        } catch (PDOException $e) {
71
            $this->logger->error('Unable to establish a PDO connection.', array('exception'=>$e));
72
            throw ReadWriteException::fromOriginalException($e);
73
        }
74 2
    }
75
76
    /**
77
     * @param string $columnName to query
78
     * @param string $userId os an existing user, throws when the user does not exist
79
     * @return string The string value of the column, returns string('') when the column is NULL
80
     * @throws RuntimeException | InvalidArgumentException
81
     * @throws ReadWriteException when there was a problem communicating with the backed
82
     */
83 2
    private function _getStringValue(string $columnName, string $userId): string
84
    {
85 2
        if ( !in_array($columnName, $this->_allowedStringColumns) ) {
86
            throw new InvalidArgumentException('Unsupported column name');
87
        }
88
89
        try {
90 2
            $sth = $this->handle->prepare('SELECT ' . $columnName . ' FROM ' . $this->tablename . ' WHERE userid = ?');
91 2
            $sth->execute(array($userId));
92 2
            $res=$sth->fetchColumn();
93 2
            if ($res === false) {
94
                // No result
95
                $this->logger->error(sprintf('No result getting "%s" for user "%s"', $columnName, $userId));
96
                throw new RuntimeException('User not found');
97
            }
98 2
            if ($res === NULL) {
99 2
                return '';  // Value unset
100
            }
101 2
            if (!is_string($res)) {
102
                $this->logger->error(sprintf('Expected string type while getting "%s" for user "%s"', $columnName, $userId));
103
                throw new RuntimeException('Unexpected return type');
104
            }
105 2
            return $res;
106
        }
107
        catch (Exception $e) {
108
            $this->logger->error('PDO error getting user', array('exception' => $e, 'userId' => $userId, 'columnName'=>$columnName));
109
            throw ReadWriteException::fromOriginalException($e);
110
        }
111
    }
112
113
    /**
114
     * @param string $columnName to query
115
     * @param string $userId of an existing user, throws when the user does not exist
116
     * @return int The int value of the column, returns int(0) when the column is NULL
117
     * @throws RuntimeException | InvalidArgumentException
118
     * @throws ReadWriteException when there was a problem communicating with the backend
119
     *
120
     */
121 2
    private function _getIntValue(string $columnName, string $userId): int
122
    {
123 2
        if ( !in_array($columnName, $this->_allowedIntColumns) ) {
124
            throw new InvalidArgumentException('Unsupported column name');
125
        }
126
127
        try {
128 2
            $sth = $this->handle->prepare('SELECT ' . $columnName . ' FROM ' . $this->tablename . ' WHERE userid = ?');
129 2
            $sth->execute(array($userId));
130 2
            $res=$sth->fetchColumn();
131 2
            if ($res === false) {
132
                // No result
133
                $this->logger->error(sprintf('No result getting "%s" for user "%s"', $columnName, $userId));
134
                throw new RuntimeException('User not found');
135
            }
136 2
            if ($res === NULL) {
137 2
                return 0;  // Value unset
138
            }
139
            // Return type for integers depends on the PDO driver, can be string
140 2
            if (!is_numeric($res)) {
141
                $this->logger->error(sprintf('Expected int type while getting "%s" for user "%s"', $columnName, $userId));
142
                throw new RuntimeException('Unexpected return type');
143
            }
144 2
            return (int)$res;
145
        }
146
        catch (Exception $e) {
147
            $this->logger->error('PDO error getting user', array('exception' => $e, 'userId' => $userId, 'columnName'=>$columnName));
148
            throw ReadWriteException::fromOriginalException($e);
149
        }
150
    }
151
152
    /**
153
     * @param string $columnName name of the column to set
154
     * @param string $userId of an existing user, throws when the user does not exist
155
     * @param string $value The value to set in $columnName
156
     * @throws RuntimeException | InvalidArgumentException
157
     * @throws ReadWriteException when there was a problem communicating with the backend
158
     */
159 2
    private function _setStringValue(string $columnName, string $userId, string $value): void
160
    {
161 2
        if ( !in_array($columnName, $this->_allowedStringColumns) ) {
162
            throw new InvalidArgumentException('Unsupported column name');
163
        }
164
        try {
165 2
            $sth = $this->handle->prepare('UPDATE ' . $this->tablename . ' SET ' . $columnName . ' = ? WHERE userid = ?');
166 2
            $sth->execute(array($value, $userId));
167 2
            if ($sth->rowCount() == 0) {
168 2
                throw new RuntimeException('User not found');
169
            }
170
        }
171
        catch (Exception $e) {
172
            $this->logger->error('PDO error updating user', array('exception' => $e, 'userId' => $userId, 'columnName'=>$columnName));
173
            throw ReadWriteException::fromOriginalException($e);
174
        }
175 2
    }
176
177
    /**
178
     * @param string $columnName name of the column to set
179
     * @param string $userId of an existing user, throws when the user does not exist
180
     * @param int $value The value to set in $columnName
181
     * @throws RuntimeException | InvalidArgumentException
182
     * @throws ReadWriteException when there was a problem communicating with the backend
183
     */
184 2
    private function _setIntValue(string $columnName, string $userId, int $value): void
185
    {
186 2
        if ( !in_array($columnName, $this->_allowedIntColumns) ) {
187
            throw new InvalidArgumentException('Unsupported column name');
188
        }
189
        try {
190 2
            $sth = $this->handle->prepare('UPDATE ' . $this->tablename . ' SET ' . $columnName . ' = ? WHERE userid = ?');
191 2
            $sth->execute(array($value, $userId));
192 2
            if ($sth->rowCount() == 0) {
193 2
                throw new RuntimeException('User not found');
194
            }
195
        }
196
        catch (Exception $e) {
197
            $this->logger->error('PDO error updating user', array('exception' => $e, 'userId' => $userId, 'columnName'=>$columnName));
198
            throw ReadWriteException::fromOriginalException($e);
199
        }
200 2
    }
201
202
    /**
203
     * @see Tiqr_UserStorage_Interface::createUser()
204
     */
205 2
    public function createUser(string $userId, string $displayName): void
206
    {
207 2
        if ($this->userExists($userId)) {
208 2
            throw new RuntimeException(sprintf('User "%s" already exists', $userId));
209
        }
210
        try {
211 2
            $sth = $this->handle->prepare("INSERT INTO ".$this->tablename." (displayname,userid) VALUES (?,?)");
212 2
            $sth->execute(array($displayName, $userId));
213
        }
214
        catch (Exception $e) {
215
            $this->logger->error(sprintf('Error creating user "%s"', $userId), array('exception'=>$e));
216
            throw new ReadWriteException('The user could not be saved in the user storage (PDO)');
217
        }
218 2
    }
219
220
    /**
221
     * @see Tiqr_UserStorage_Interface::userExists()
222
     */
223 2
    public function userExists(string $userId): bool
224
    {
225
        try {
226 2
            $sth = $this->handle->prepare("SELECT userid FROM ".$this->tablename." WHERE userid = ?");
227 2
            $sth->execute(array($userId));
228 2
            return (false !== $sth->fetchColumn());
229
        }
230
        catch (Exception $e) {
231
            $this->logger->error('PDO error checking user exists', array('exception'=>$e, 'userId'=>$userId));
232
            throw ReadWriteException::fromOriginalException($e);
233
        }
234
    }
235
236
    /**
237
     * @see Tiqr_UserStorage_Interface::getDisplayName()
238
     */
239 2
    public function getDisplayName(string $userId): string
240
    {
241 2
        return $this->_getStringValue('displayname', $userId);
242
    }
243
244
    /**
245
     * @see Tiqr_UserStorage_Interface::getNotificationType()
246
     */
247 2
    public function getNotificationType(string $userId): string
248
    {
249 2
        return $this->_getStringValue('notificationtype', $userId);
250
    }
251
252
    /**
253
     * @see Tiqr_UserStorage_Interface::getNotificationType()
254
     */
255 2
    public function setNotificationType(string $userId, string $type): void
256
    {
257 2
        $this->_setStringValue('notificationtype', $userId, $type);
258 2
    }
259
260
    /**
261
     * @see Tiqr_UserStorage_Interface::getNotificationAddress()
262
     */
263 2
    public function getNotificationAddress(string $userId): string
264
    {
265 2
        return $this->_getStringValue('notificationaddress', $userId);
266
    }
267
268
    /**
269
     * @see Tiqr_UserStorage_Interface::setNotificationAddress()
270
     */
271
    public function setNotificationAddress(string $userId, string $address): void
272
    {
273
        $this->_setStringValue('notificationaddress', $userId, $address);
274
    }
275
276
    /**
277
     * @see Tiqr_UserStorage_Interface::getLoginAttempts()
278
     */
279 2
    public function getLoginAttempts(string $userId): int
280
    {
281 2
        return $this->_getIntValue('loginattempts', $userId);
282
    }
283
284
    /**
285
     * @see Tiqr_UserStorage_Interface::setLoginAttempts()
286
     */
287 2
    public function setLoginAttempts(string $userId, int $amount): void
288
    {
289 2
        $this->_setIntValue('loginattempts', $userId, $amount);
290 2
    }
291
292
    /**
293
     * @see Tiqr_UserStorage_Interface::isBlocked()
294
     */
295 2
    public function isBlocked(string $userId, int $tempBlockDuration = 0): bool
296
    {
297
        // Check for blocked
298 2
        if ($this->_getIntValue('blocked', $userId) != 0) {
299 2
            return true;   // Blocked
300
        }
301
302 2
        if (0 == $tempBlockDuration) {
303 2
            return false;   // No check for temporary block
304
        }
305
306
        // Check for temporary block
307 2
        $timestamp = $this->getTemporaryBlockTimestamp($userId);
308
        // if no temporary block timestamp is set or if the temporary block is expired, return false
309 2
        if ( 0 == $timestamp || ($timestamp + $tempBlockDuration * 60) < time()) {
310 2
            return false;
311
        }
312 2
        return true;
313
    }
314
315
    /**
316
     * @see Tiqr_UserStorage_Interface::setBlocked()
317
     */
318 2
    public function setBlocked(string $userId, bool $blocked): void
319
    {
320 2
        $this->_setIntValue('blocked', $userId, ($blocked) ? 1 : 0);
321 2
    }
322
323
    /**
324
     * @see Tiqr_UserStorage_Interface::setTemporaryBlockAttempts()
325
     */
326 2
    public function setTemporaryBlockAttempts(string$userId, int $amount): void
327
    {
328 2
        $this->_setIntValue('tmpblockattempts', $userId, $amount);
329 2
    }
330
331
    /**
332
     * @see Tiqr_UserStorage_Interface::getTemporaryBlockAttempts()
333
     */
334 2
    public function getTemporaryBlockAttempts(string $userId): int {
335 2
        return $this->_getIntValue('tmpblockattempts', $userId);
336
    }
337
338
    /**
339
     * @see Tiqr_UserStorage_Interface::setTemporaryBlockTimestamp()
340
     */
341 2
    public function setTemporaryBlockTimestamp(string $userId, int $timestamp): void
342
    {
343 2
        $this->_setIntValue('tmpblocktimestamp', $userId, $timestamp);
344 2
    }
345
346
    /**
347
     * @see Tiqr_UserStorage_Interface::getTemporaryBlockTimestamp()
348
     */
349 2
    public function getTemporaryBlockTimestamp(string $userId): int
350
    {
351 2
        return $this->_getIntValue('tmpblocktimestamp', $userId);
352
    }
353
}
354