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
$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
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
|
|||||||
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
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
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
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
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
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 |
This check looks for parameters that have been defined for a function or method, but which are not used in the method body.