Passed
Push — master ( 562ec1...c64a31 )
by Terrence
15:08
created

src/Service/SessionMgr.php (8 issues)

1
<?php
2
3
namespace CILogon\Service;
4
5
use CILogon\Service\DBProps;
6
use CILogon\Service\Util;
7
use DB;
8
9
/**
10
 * SessionMgr
11
 *
12
 * This class is an implementation of a PHP Session
13
 * handler using MySQL as the storage mechanism. There are
14
 * several required functions implemented as documented at
15
 * http://us3.php.net/manual/en/function.session-set-save-handler.php
16
 * and http://us3.php.net/manual/en/class.sessionhandlerinterface.php.
17
 * Implementation details were gleaned from several
18
 * web pages, in particular:
19
 * http://www.devshed.com/c/a/PHP/Storing-PHP-Sessions-in-a-Database/
20
 * Also, the PEAR HTTP_Session2 package inspired several
21
 * tweaks, such as the crc check to prevent database
22
 * writes when the session data had not changed.
23
 *
24
 * In order to use this class, you must first configure
25
 * MySQL with correct privileges and a new table.
26
 *
27
 * # mysql -u root -p
28
 * ### password is found in /var/www/config/cilogon.xml
29
 * mysql> use oauth;
30
 * mysql> GRANT ALL PRIVILEGES ON oauth.phpsessions
31
 *     ->  TO 'cilogon'@'localhost' WITH GRANT OPTION;
32
 * mysql> COMMIT;
33
 * mysql> CREATE TABLE oauth.phpsessions (
34
 *     ->  id VARCHAR(32) NOT NULL,
35
 *     ->  data BLOB NOT NULL,
36
 *     ->  expires INTEGER NOT NULL,
37
 *     ->  PRIMARY KEY (id)
38
 *     -> ) ENGINE=MyISAM;
39
 * mysql> COMMIT;
40
 * mysql> \q
41
 *
42
 * To use this class, simply create a new instance
43
 * before the call to session_start().
44
 *
45
 * require_once 'SessionMgr.php';
46
 * $sessionmgr = new SessionMgr();
47
 * session_start();
48
 *
49
 * The session data is written to database upon script
50
 * completion or when session_write_close() is called.
51
 */
52
class SessionMgr
53
{
54
    /**
55
     * @var DB|null $db A PEAR DB database connection object
56
     */
57
    protected $db = null;
58
59
    /**
60
     * @var string|null $crc Session data cache id
61
     */
62
    protected $crc = null;
63
64
    /**
65
     * __construct
66
     *
67
     * Default constructor.  This method calls
68
     * session_set_save_handler() with the methods in this class as
69
     * parameters.
70
     */
71
    public function __construct()
72
    {
73
        session_set_save_handler(
74
            array(&$this, 'open'),
75
            array(&$this, 'close'),
76
            array(&$this, 'read'),
77
            array(&$this, 'write'),
78
            array(&$this, 'destroy'),
79
            array(&$this, 'gc')
80
        );
81
        // The following prevents unexpected effects when using
82
        // objects as save handlers
83
        register_shutdown_function('session_write_close');
84
    }
85
86
    /**
87
     * open
88
     *
89
     * This method opens the database connection.
90
     *
91
     * @param string $save_path The path where PHP session files are to be
92
     *        saved. (Ignored in the MySQL case.)
93
     * @param string $session_id The PHP session identifier.
94
     * @return bool True if database connection opened successfully,
95
     *         false otherwise.
96
     */
97
    public function open($save_path, $session_id)
0 ignored issues
show
The parameter $save_path is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

97
    public function open(/** @scrutinizer ignore-unused */ $save_path, $session_id)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
The parameter $session_id is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

97
    public function open($save_path, /** @scrutinizer ignore-unused */ $session_id)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
98
    {
99
        $retval = true;  // Assume connect to database succeeded
100
101
        $storetype = STORAGE_PHPSESSIONS;
0 ignored issues
show
The constant CILogon\Service\STORAGE_PHPSESSIONS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
102
        $dbprops = new DBProps($storetype);
103
        $this->db = $dbprops->getDBConnect();
104
105
        if (is_null($this->db)) {
106
            $retval = false;
107
        }
108
        return $retval;
109
    }
110
111
    /**
112
     * close
113
     *
114
     * This method closes the database connection.
115
     *
116
     * @return bool True if database connection was closed successfully,
117
     *         false otherwise.
118
     */
119
    public function close()
120
    {
121
        $retval = true;  // Assume close database succeeded
122
123
        if (is_null($this->db)) {  // Can't close a null database
124
            $retval = false;
125
        } else {
126
            $retval = $this->db->disconnect();
0 ignored issues
show
The method disconnect() does not exist on DB. Did you maybe mean connect()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

126
            /** @scrutinizer ignore-call */ 
127
            $retval = $this->db->disconnect();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
127
            $this->db = null;
128
        }
129
130
        return $retval;
131
    }
132
133
    /**
134
     * read
135
     *
136
     * This method reads the PHP session data from the database
137
     * associated with the passed-in identifier. It calculates a cache
138
     * string using crc32 (so we can check if session data has been
139
     * updated). If there is a problem reading the data, empty string
140
     * is returned.
141
     *
142
     * @param string $session_id The PHP session identifier.
143
     * @return string The PHP session data associated with the identifier,
144
     *         or empty string on error.
145
     */
146
    public function read($session_id)
147
    {
148
        $retval = '';
149
150
        if (!is_null($this->db)) {
151
            $time = time();
152
            $quoteid = $this->db->quoteSmart($session_id);
0 ignored issues
show
The method quoteSmart() does not exist on DB. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

152
            /** @scrutinizer ignore-call */ 
153
            $quoteid = $this->db->quoteSmart($session_id);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
153
            $query = "SELECT data FROM phpsessions " .
154
                     "WHERE id = $quoteid AND expires >= $time";
155
            $retval = $this->db->getOne($query);
0 ignored issues
show
The method getOne() does not exist on DB. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

155
            /** @scrutinizer ignore-call */ 
156
            $retval = $this->db->getOne($query);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
156
            if (DB::isError($retval)) {
157
                $retval = '';
158
            } else {
159
                $this->crc = strlen($retval) . crc32($retval);
160
            }
161
        }
162
        settype($retval, 'string');
163
        return $retval;
164
    }
165
166
    /**
167
     * write
168
     *
169
     * This method is called when the PHP session data should be
170
     * written to the database (usually upon script completion, or when
171
     * session_write_close() is called). It tries to be 'smart' by
172
     * updating only the information that has changed, e.g. update
173
     * just the expiration time if session data has not changed.
174
     *
175
     * @param string $session_id The PHP session identifier.
176
     * @param string $session_data The PHP session data to be written.
177
     * @return bool True upon successful write of data to the database,
178
     *         or false on error.
179
     */
180
    public function write($session_id, $session_data)
181
    {
182
        $retval = true; // Assume write to database succeeded
183
184
        if (is_null($this->db)) {  // Can't write to a null database
185
            $retval = false;
186
        } else {
187
            $time = time();
188
            $newtime = $time + get_cfg_var('session.gc_maxlifetime');
189
            $quoteid = $this->db->quoteSmart($session_id);
190
            $query = '';
191
            if (
192
                (!is_null($this->crc)) &&
193
                ($this->crc === (strlen($session_data) . crc32($session_data)))
194
            ) {
195
                // $_SESSION hasn't been touched, so update the expires column
196
                $query = "UPDATE phpsessions SET expires = $newtime " .
197
                         "WHERE id = $quoteid";
198
            } else {
199
                // Check if the table row already exists
200
                $query = "SELECT COUNT(id) FROM phpsessions " .
201
                         "WHERE id = $quoteid";
202
                $result = $this->db->getOne($query);
203
                if (DB::isError($result)) {
204
                    $retval = false;
205
                } else {
206
                    $quotedata = $this->db->quoteSmart($session_data);
207
                    if (intval($result) == 0) {
208
                        // Insert a new row into the table
209
                        $query = "INSERT INTO phpsessions (id,data,expires) " .
210
                                 "VALUES($quoteid,$quotedata,$newtime)";
211
                    } else {
212
                        // Update existing row with new data and expires
213
                        $query = "UPDATE phpsessions " .
214
                                 "SET data = $quotedata, expires = $newtime " .
215
                                 "WHERE id = $quoteid";
216
                    }
217
                }
218
            }
219
220
            if ($retval) {
221
                $result = $this->db->query($query);
0 ignored issues
show
The method query() does not exist on DB. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

221
                /** @scrutinizer ignore-call */ 
222
                $result = $this->db->query($query);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
222
                if (DB::isError($result)) {
223
                    $retval = false;
224
                }
225
            }
226
        }
227
228
        return $retval;
229
    }
230
231
    /**
232
     * destroy
233
     *
234
     * This method deletes a session identifier from the database.
235
     *
236
     * @param string $session_id The PHP session identifier.
237
     * @return bool True upon successful deletion of data from the
238
     *         database, or false on error.
239
     */
240
    public function destroy($session_id)
241
    {
242
        $retval = true;  // Assume delete session_id from database succeeded
243
244
        if (is_null($this->db)) {  // Can't delete from a null database
245
            $retval = false;
246
        } else {
247
            $quoteid = $this->db->quoteSmart($session_id);
248
            $query = "DELETE FROM phpsessions WHERE id = $quoteid";
249
            $result = $this->db->query($query);
250
            if (DB::isError($result)) {
251
                $retval = false;
252
            }
253
        }
254
255
        return $retval;
256
    }
257
258
    /**
259
     * gc
260
     *
261
     * This method is invoked internally by PHP periodically in order
262
     * to purge old session data. It simply looks for rows where the
263
     * 'expires' column is older than the current time (less 10
264
     * seconds to allow for multiple threads).
265
     *
266
     * @param int $maxlifetime The lifetime of the PHP session (ignored since
267
     *        the 'expires' column is set using the
268
     *        session.gc_maxlifetime value).
269
     * @return bool True upon successful garbage collection run on the
270
     *         database, or false on error.
271
     */
272
    public function gc($maxlifetime)
0 ignored issues
show
The parameter $maxlifetime is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

272
    public function gc(/** @scrutinizer ignore-unused */ $maxlifetime)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
273
    {
274
        $retval = true;  // Assume garbage collection succeeded
275
276
        if (is_null($this->db)) {  // Can't garbage collect on a null database
277
            $retval = false;
278
        } else {
279
            $time = time() - 10; // Allow extra time for multi-threads
280
            $query = "DELETE FROM phpsessions WHERE expires < $time";
281
            $result = $this->db->query($query);
282
            if (DB::isError($result)) {
283
                $retval = false;
284
            }
285
        }
286
287
        return $retval;
288
    }
289
}
290