Passed
Push — master ( 35fb8a...2dccb2 )
by Pieter van der
05:32 queued 14s
created

Tiqr_StateStorage_Pdo::__construct()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 6
c 3
b 0
f 0
dl 0
loc 9
ccs 7
cts 7
cp 1
rs 10
cc 3
nc 2
nop 4
crap 3
1
<?php
2
3
use Psr\Log\LoggerInterface;
4
5
/**
6
 * This file is part of the tiqr project.
7
 * 
8
 * The tiqr project aims to provide an open implementation for 
9
 * authentication using mobile devices. It was initiated by 
10
 * SURFnet and developed by Egeniq.
11
 *
12
 * More information: http://www.tiqr.org
13
 *
14
 * @author Patrick Honing <[email protected]>
15
 * 
16
 * @package tiqr
17
 *
18
 * @license New BSD License - See LICENSE file for details.
19
 *
20
 * @copyright (C) 2010-2012 SURFnet BV
21
 * 
22
 * 
23
 * Create SQL table (MySQL):
24
25
 * CREATE TABLE IF NOT EXISTS tiqrstate (
26
    key varchar(255) PRIMARY KEY,
27
    expire BIGINT,
28
    value text
29
);
30
 */
31
32
33
class Tiqr_StateStorage_Pdo extends Tiqr_StateStorage_Abstract
34
{
35
    /**
36
     * @var PDO
37
     */
38
    protected $handle;
39
40
    /**
41
     * @var string
42
     */
43
    private $tablename;
44
45
    /**
46
     * @var int
47
     */
48
    private $cleanupProbability;
49
50
    /**
51
     * @param PDO $pdoInstance The PDO instance where all state storage operations are performed on
52
     * @param LoggerInterface
53
     * @param string $tablename The tablename that is used for storing and retrieving the state storage
54
     * @param float $cleanupProbability The probability the expired state storage items are removed on a 'setValue' call. Example usage: 0 = never, 0.5 = 50% chance, 1 = always
55
     *
56
     * @throws RuntimeException when an invalid cleanupProbability is configured
57
     */
58 5
    public function __construct(PDO $pdoInstance, LoggerInterface $logger, string $tablename, float $cleanupProbability)
59
    {
60 5
        if ($cleanupProbability < 0 || $cleanupProbability > 1) {
61 2
            throw new RuntimeException('The probability for removing the expired state should be expressed in a floating point value between 0 and 1.');
62
        }
63 5
        $this->cleanupProbability = $cleanupProbability;
64 5
        $this->tablename = $tablename;
65 5
        $this->handle = $pdoInstance;
66 5
        $this->logger = $logger;
67 5
    }
68
69
    /**
70
     * @param string $key to lookup
71
     * @return bool true when $key is found, false when the key does not exist
72
     * @throws ReadWriteException
73
     */
74 1
    private function keyExists(string $key): bool
75
    {
76 1
        if (empty($key)) {
77
            throw new InvalidArgumentException('Empty key not allowed');
78
        }
79
        try {
80 1
            $sth = $this->handle->prepare('SELECT `key` FROM ' . $this->tablename . ' WHERE `key` = ?');
81 1
            $sth->execute(array($key));
82 1
            return $sth->fetchColumn() !== false;
83
        }
84
        catch (Exception $e) {
85
            $this->logger->error(
86
                sprintf('Error checking for key "%s" in PDO StateStorage', $key),
87
                array('exception' => $e)
88
            );
89
            throw ReadWriteException::fromOriginalException($e);
90
        }
91
    }
92
93
    /**
94
     * Remove expired keys
95
     * This is a maintenance task that should be periodically run
96
     * Does not throw
97
     */
98 1
    private function cleanExpired(): void {
99
        try {
100 1
            $sth = $this->handle->prepare("DELETE FROM " . $this->tablename . " WHERE `expire` < ? AND NOT `expire` = 0");
101 1
            $sth->execute(array(time()));
102 1
            $deletedRows=$sth->rowCount();
103 1
            $this->logger->notice(
104 1
                sprintf("Deleted %i expired keys", $deletedRows)
105
            );
106
        }
107
        catch (Exception $e) {
108
            $this->logger->error(
109
                sprintf("Deleting expired keys failed: %s", $e->getMessage()),
110
                array('exception', $e)
111
            );
112
        }
113 1
    }
114
    
115
    /**
116
     * @see Tiqr_StateStorage_StateStorageInterface::setValue()
117
     */
118 2
    public function setValue(string $key, $value, int $expire=0): void
119
    {
120 2
        if (empty($key)) {
121 1
            throw new InvalidArgumentException('Empty key not allowed');
122
        }
123 1
        if (((float) rand() /(float) getrandmax()) < $this->cleanupProbability) {
124 1
            $this->cleanExpired();
125
        }
126 1
        if ($this->keyExists($key)) {
127 1
            $sth = $this->handle->prepare("UPDATE ".$this->tablename." SET `value` = ?, `expire` = ? WHERE `key` = ?");
128
        } else {
129
            $sth = $this->handle->prepare("INSERT INTO ".$this->tablename." (`value`,`expire`,`key`) VALUES (?,?,?)");
130
        }
131
        // $expire == 0 means never expire
132 1
        if ($expire != 0) {
133 1
            $expire+=time();    // Store unix timestamp after which the expires
134
        }
135
        try {
136 1
            $sth->execute(array(serialize($value), $expire, $key));
137
        }
138
        catch (Exception $e) {
139
            $this->logger->error(
140
                sprintf('Unable to store key "%s" in PDO StateStorage', $key),
141
                array('exception' => $e)
142
            );
143
            throw ReadWriteException::fromOriginalException($e);
144
        }
145 1
    }
146
        
147
    /**
148
     * @see Tiqr_StateStorage_StateStorageInterface::unsetValue()
149
     */
150 1
    public function unsetValue($key): void
151
    {
152 1
        if (empty($key)) {
153
            throw new InvalidArgumentException('Empty key not allowed');
154
        }
155
        try {
156 1
            $sth = $this->handle->prepare("DELETE FROM " . $this->tablename . " WHERE `key` = ?");
157 1
            $sth->execute(array($key));
158
        }
159
        catch (Exception $e) {
160
            $this->logger->error(
161
                sprintf('Error deleting key "%s" from PDO StateStorage', $key),
162
                array('exception' => $e)
163
            );
164
            throw ReadWriteException::fromOriginalException($e);
165
        }
166
167 1
        if ($sth->rowCount() === 0) {
168
            // Key did not exist, this is not an error
169 1
            $this->logger->info(
170 1
                sprintf('unsetValue: key "%s" not found in PDO StateStorage', $key
171
                )
172
            );
173
        }
174 1
    }
175
    
176
    /**
177
     * @see Tiqr_StateStorage_StateStorageInterface::getValue()
178
     */
179 1
    public function getValue(string $key)
180
    {
181 1
        if (empty($key)) {
182
            throw new InvalidArgumentException('Empty key not allowed');
183
        }
184
185
        try {
186 1
            $sth = $this->handle->prepare('SELECT `value` FROM ' . $this->tablename . ' WHERE `key` = ? AND (`expire` >= ? OR `expire` = 0)');
187 1
            $sth->execute(array($key, time()));
188
        }
189
        catch (Exception $e) {
190
            $this->logger->error(
191
                sprintf('Error getting value for key "%s" from PDO StateStorage', $key),
192
                array('exception' => $e)
193
            );
194
            throw ReadWriteException::fromOriginalException($e);
195
        }
196 1
        $result = $sth->fetchColumn();
197 1
        if (false === $result) {
198
            // Occurs normally
199 1
            $this->logger->info(sprintf('getValue: Key "%s" not found in PDO StateStorage', $key));
200 1
            return NULL;    // Key not found
201
        }
202
        $result=unserialize($result, array('allowed_classes' => false));
203
        if (false === $result) {
204
            throw new RuntimeException(sprintf('getValue: unserialize error for key "%s" in PDO StateStorage', $key));
205
        }
206
207
        return $result;
208
    }
209
210
}
211