Passed
Push — master ( f2daf3...fc29ab )
by Tim
03:13
created

SQLTicketStore::delete()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 8
nc 2
nop 1
dl 0
loc 15
rs 10
c 0
b 0
f 0
1
<?php
2
3
/*
4
 *    simpleSAMLphp-casserver is a CAS 1.0 and 2.0 compliant CAS server in the form of a simpleSAMLphp module
5
 *
6
 *    Copyright (C) 2013  Bjorn R. Jensen
7
 *
8
 *    This library is free software; you can redistribute it and/or
9
 *    modify it under the terms of the GNU Lesser General Public
10
 *    License as published by the Free Software Foundation; either
11
 *    version 2.1 of the License, or (at your option) any later version.
12
 *
13
 *    This library is distributed in the hope that it will be useful,
14
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16
 *    Lesser General Public License for more details.
17
 *
18
 *    You should have received a copy of the GNU Lesser General Public
19
 *    License along with this library; if not, write to the Free Software
20
 *    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
21
 *
22
 */
23
24
namespace SimpleSAML\Module\casserver\Cas\Ticket;
25
26
class SQLTicketStore extends TicketStore
0 ignored issues
show
Bug introduced by
The type SimpleSAML\Module\casserver\Cas\Ticket\TicketStore 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...
27
{
28
    public $pdo;
29
    public $driver;
30
    public $prefix;
31
    private $tableVersions;
32
33
    public function __construct(\SimpleSAML\Configuration $config)
0 ignored issues
show
Bug introduced by
The type SimpleSAML\Configuration 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...
34
    {
35
        parent::__construct($config);
36
37
        /** @var  $storeConfig \SimpleSAML\Configuration */
38
        $storeConfig = $config->getConfigItem('ticketstore');
39
        $dsn = $storeConfig->getString('dsn');
40
        $username = $storeConfig->getString('username');
41
        $password = $storeConfig->getString('password');
42
        $options =  $storeConfig->getArray('options', []);
43
        $this->prefix = $storeConfig->getString('prefix', '');
44
45
        $this->pdo = new PDO($dsn, $username, $password, $options);
0 ignored issues
show
Bug introduced by
The type SimpleSAML\Module\casserver\Cas\Ticket\PDO was not found. Did you mean PDO? If so, make sure to prefix the type with \.
Loading history...
46
        $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
47
48
        $this->driver = $this->pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
49
50
        if ($this->driver === 'mysql') {
51
            $this->pdo->exec('SET time_zone = "+00:00"');
52
        }
53
54
        $this->initTableVersionTable();
55
        $this->initKVTable();
56
    }
57
58
    /**
59
     * @param $ticketId string
60
     * @return array|null
61
     */
62
    public function getTicket($ticketId)
63
    {
64
        $scopedTicketId = $this->scopeTicketId($ticketId);
65
66
        return $this->get($scopedTicketId);
67
    }
68
69
    public function addTicket(array $ticket)
70
    {
71
        $scopedTicketId = $this->scopeTicketId($ticket['id']);
72
73
        $this->set($scopedTicketId, $ticket, $ticket['validBefore']);
74
    }
75
76
    /**
77
     * @param $ticketId string
78
     */
79
    public function deleteTicket($ticketId)
80
    {
81
        $scopedTicketId = $this->scopeTicketId($ticketId);
82
83
        $this->delete($scopedTicketId);
84
    }
85
86
    /**
87
     * @param $ticketId string
88
     * @return string
89
     */
90
    private function scopeTicketId($ticketId)
91
    {
92
        return $this->prefix.'.'.$ticketId;
93
    }
94
95
    private function initTableVersionTable()
96
    {
97
98
        $this->tableVersions = [];
99
100
        try {
101
            $fetchTableVersion = $this->pdo->query('SELECT _name, _version FROM '.$this->prefix.'_tableVersion');
102
        } catch (PDOException $e) {
0 ignored issues
show
Bug introduced by
The type SimpleSAML\Module\casser...Cas\Ticket\PDOException was not found. Did you mean PDOException? If so, make sure to prefix the type with \.
Loading history...
103
            $this->pdo->exec('CREATE TABLE '.$this->prefix.
104
                '_tableVersion (_name VARCHAR(30) NOT NULL UNIQUE, _version INTEGER NOT NULL)');
105
            return;
106
        }
107
108
        while (($row = $fetchTableVersion->fetch(PDO::FETCH_ASSOC)) !== false) {
109
            $this->tableVersions[$row['_name']] = (int)$row['_version'];
110
        }
111
    }
112
113
    private function initKVTable()
114
    {
115
        if ($this->getTableVersion('kvstore') === 1) {
116
            /* Table initialized. */
117
            return;
118
        }
119
120
        $query = 'CREATE TABLE '.$this->prefix.
121
            '_kvstore (_key VARCHAR(50) NOT NULL, _value TEXT NOT NULL, _expire TIMESTAMP, PRIMARY KEY (_key))';
122
        $this->pdo->exec($query);
123
124
        $query = 'CREATE INDEX '.$this->prefix.'_kvstore_expire ON '.$this->prefix.'_kvstore (_expire)';
125
        $this->pdo->exec($query);
126
127
        $this->setTableVersion('kvstore', 1);
128
    }
129
130
    /**
131
     * @param $name string
132
     * @return int
133
     */
134
    private function getTableVersion($name)
135
    {
136
        assert(is_string($name));
137
138
        if (!isset($this->tableVersions[$name])) {
139
            return 0;
140
        }
141
142
        return $this->tableVersions[$name];
143
    }
144
145
    /**
146
     * @param $name string
147
     * @param $version int
148
     */
149
    private function setTableVersion($name, $version)
150
    {
151
        assert(is_string($name));
152
        assert(is_int($version));
153
154
        $this->insertOrUpdate(
155
            $this->prefix.'_tableVersion',
156
            ['_name'],
157
            [
158
                '_name' => $name,
159
                '_version' => $version
160
            ]
161
        );
162
        $this->tableVersions[$name] = $version;
163
    }
164
165
    /**
166
     * @param $table string
167
     * @param array $keys
168
     * @param array $data
169
     */
170
    private function insertOrUpdate($table, array $keys, array $data)
171
    {
172
        assert(is_string($table));
173
174
        $colNames = '('.implode(', ', array_keys($data)).')';
175
        $values = 'VALUES(:'.implode(', :', array_keys($data)).')';
176
177
        switch ($this->driver) {
178
            case 'mysql':
179
                $query = 'REPLACE INTO '.$table.' '.$colNames.' '.$values;
180
                $query = $this->pdo->prepare($query);
181
                $query->execute($data);
182
                return;
183
            case 'sqlite':
184
                $query = 'INSERT OR REPLACE INTO '.$table.' '.$colNames.' '.$values;
185
                $query = $this->pdo->prepare($query);
186
                $query->execute($data);
187
                return;
188
        }
189
190
        /* Default implementation. Try INSERT, and UPDATE if that fails. */
191
192
        $insertQuery = 'INSERT INTO '.$table.' '.$colNames.' '.$values;
193
        $insertQuery = $this->pdo->prepare($insertQuery);
194
        try {
195
            $insertQuery->execute($data);
196
            return;
197
        } catch (\PDOException $e) {
198
            $ecode = (string)$e->getCode();
199
            switch ($ecode) {
200
                case '23505': /* PostgreSQL */
201
                    break;
202
                default:
203
                    SimpleSAML\Logger::error('casserver: Error while saving data: '.$e->getMessage());
0 ignored issues
show
Bug introduced by
The type SimpleSAML\Module\casser...icket\SimpleSAML\Logger was not found. Did you mean SimpleSAML\Logger? If so, make sure to prefix the type with \.
Loading history...
204
                    throw $e;
205
            }
206
        }
207
208
        $updateCols = [];
209
        $condCols = [];
210
211
        foreach ($data as $col => $value) {
212
213
            $tmp = $col.' = :'.$col;
214
215
            if (in_array($col, $keys, true)) {
216
                $condCols[] = $tmp;
217
            } else {
218
                $updateCols[] = $tmp;
219
            }
220
        }
221
222
        $updateQuery = 'UPDATE '.$table.' SET '.implode(',', $updateCols).' WHERE '.
223
            implode(' AND ', $condCols);
224
        $updateQuery = $this->pdo->prepare($updateQuery);
225
        $updateQuery->execute($data);
226
    }
227
228
    private function cleanKVStore()
229
    {
230
        $query = 'DELETE FROM '.$this->prefix.'_kvstore WHERE _expire < :now';
231
        $params = ['now' => gmdate('Y-m-d H:i:s')];
232
233
        $query = $this->pdo->prepare($query);
234
        $query->execute($params);
235
    }
236
237
    /**
238
     * @param $key string
239
     * @return mixed|null|string
240
     */
241
    private function get($key)
242
    {
243
        assert(is_string($key));
244
245
        if (strlen($key) > 50) {
246
            $key = sha1($key);
247
        }
248
249
        $query = 'SELECT _value FROM '.$this->prefix.
250
            '_kvstore WHERE _key = :key AND (_expire IS NULL OR _expire > :now)';
251
        $params = ['key' => $key, 'now' => gmdate('Y-m-d H:i:s')];
252
253
        $query = $this->pdo->prepare($query);
254
        $query->execute($params);
255
256
        $row = $query->fetch(PDO::FETCH_ASSOC);
257
        if ($row === false) {
258
            return null;
259
        }
260
261
        $value = $row['_value'];
262
        if (is_resource($value)) {
263
            $value = stream_get_contents($value);
264
        }
265
        $value = urldecode($value);
266
        $value = unserialize($value);
267
268
        if ($value === false) {
269
            return null;
270
        }
271
272
        return $value;
273
    }
274
275
    /**
276
     * @param $key string
277
     * @param $value mixed
278
     * @param null $expire int
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $expire is correct as it would always require null to be passed?
Loading history...
279
     */
280
    private function set($key, $value, $expire = null)
281
    {
282
        assert(is_string($key));
283
        assert(is_null($expire) || (is_int($expire) && $expire > 2592000));
284
285
        if (rand(0, 1000) < 10) {
286
            $this->cleanKVStore();
287
        }
288
289
        if (strlen($key) > 50) {
290
            $key = sha1($key);
291
        }
292
293
        if ($expire !== null) {
294
            $expire = gmdate('Y-m-d H:i:s', $expire);
295
        }
296
297
        $value = serialize($value);
298
        $value = rawurlencode($value);
299
300
        $data = [
301
            '_key' => $key,
302
            '_value' => $value,
303
            '_expire' => $expire,
304
        ];
305
306
        $this->insertOrUpdate($this->prefix.'_kvstore', ['_key'], $data);
307
    }
308
309
    /**
310
     * @param $key string
311
     */
312
    private function delete($key)
313
    {
314
        assert(is_string($key));
315
316
        if (strlen($key) > 50) {
317
            $key = sha1($key);
318
        }
319
320
        $data = [
321
            '_key' => $key,
322
        ];
323
324
        $query = 'DELETE FROM '.$this->prefix.'_kvstore WHERE _key=:_key';
325
        $query = $this->pdo->prepare($query);
326
        $query->execute($data);
327
    }
328
}
329