[TASK] Re-work/simplify copyright header in PHP files - Part 2
[Packages/TYPO3.CMS.git] / typo3 / sysext / install / Classes / Service / SessionService.php
1 <?php
2 namespace TYPO3\CMS\Install\Service;
3
4 /**
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\Utility\GeneralUtility;
18
19 /**
20 * Secure session handling for the install tool.
21 *
22 * @author Ernesto Baschny <ernst@cron-it.de>
23 */
24 class SessionService implements \TYPO3\CMS\Core\SingletonInterface {
25
26 /**
27 * The path to our typo3temp (where we can write our sessions). Set in the
28 * constructor.
29 *
30 * @var string
31 */
32 private $typo3tempPath;
33
34 /**
35 * Path where to store our session files in typo3temp. %s will be
36 * non-guessable.
37 *
38 * @var string
39 */
40 private $sessionPath = 'InstallToolSessions/%s';
41
42 /**
43 * the cookie to store the session ID of the install tool
44 *
45 * @var string
46 */
47 private $cookieName = 'Typo3InstallTool';
48
49 /**
50 * time (minutes) to expire an unused session
51 *
52 * @var integer
53 */
54 private $expireTimeInMinutes = 60;
55
56 /**
57 * time (minutes) to generate a new session id for our current session
58 *
59 * @var integer
60 */
61 private $regenerateSessionIdTime = 5;
62
63 /**
64 * Constructor. Starts PHP session handling in our own private store
65 *
66 * Side-effect: might set a cookie, so must be called before any other output.
67 */
68 public function __construct() {
69 $this->typo3tempPath = PATH_site . 'typo3temp/';
70 // Start our PHP session early so that hasSession() works
71 $sessionSavePath = $this->getSessionSavePath();
72 // Register our "save" session handler
73 session_set_save_handler(array($this, 'open'), array($this, 'close'), array($this, 'read'), array($this, 'write'), array($this, 'destroy'), array($this, 'gc'));
74 session_save_path($sessionSavePath);
75 session_name($this->cookieName);
76 ini_set('session.cookie_path', GeneralUtility::getIndpEnv('TYPO3_SITE_PATH'));
77 // Always call the garbage collector to clean up stale session files
78 ini_set('session.gc_probability', 100);
79 ini_set('session.gc_divisor', 100);
80 ini_set('session.gc_maxlifetime', $this->expireTimeInMinutes * 2 * 60);
81 if (\TYPO3\CMS\Core\Utility\PhpOptionsUtility::isSessionAutoStartEnabled()) {
82 $sessionCreationError = 'Error: session.auto-start is enabled.<br />';
83 $sessionCreationError .= 'The PHP option session.auto-start is enabled. Disable this option in php.ini or .htaccess:<br />';
84 $sessionCreationError .= '<pre>php_value session.auto_start Off</pre>';
85 throw new \TYPO3\CMS\Install\Exception($sessionCreationError, 1294587485);
86 } elseif (defined('SID')) {
87 $sessionCreationError = 'Session already started by session_start().<br />';
88 $sessionCreationError .= 'Make sure no installed extension is starting a session in its ext_localconf.php or ext_tables.php.';
89 throw new \TYPO3\CMS\Install\Exception($sessionCreationError, 1294587486);
90 }
91 session_start();
92 }
93
94 /**
95 * Returns the path where to store our session files
96 *
97 * @throws \TYPO3\CMS\Install\Exception
98 * @return string Session save path
99 */
100 private function getSessionSavePath() {
101 if (empty($GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'])) {
102 throw new \TYPO3\CMS\Install\Exception(
103 'No encryption key set to secure session',
104 1371243449
105 );
106 }
107 $sessionSavePath = sprintf(
108 $this->typo3tempPath . $this->sessionPath,
109 GeneralUtility::hmac('session:' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'])
110 );
111 $this->ensureSessionSavePathExists($sessionSavePath);
112 return $sessionSavePath;
113 }
114
115 /**
116 * Create directories for the session save path
117 * and throw an exception if that fails.
118 *
119 * @param string $sessionSavePath The absolute path to the session files
120 * @throws \TYPO3\CMS\Install\Exception
121 */
122 private function ensureSessionSavePathExists($sessionSavePath) {
123 if (!is_dir($sessionSavePath)) {
124 try {
125 GeneralUtility::mkdir_deep($sessionSavePath);
126 } catch (\RuntimeException $exception) {
127 throw new \TYPO3\CMS\Install\Exception(
128 'Could not create session folder in typo3temp/. Make sure it is writeable!',
129 1294587484
130 );
131 }
132 GeneralUtility::writeFile($sessionSavePath . '/.htaccess', 'Order deny, allow' . LF . 'Deny from all');
133 $indexContent = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">';
134 $indexContent .= '<HTML><HEAD<TITLE></TITLE><META http-equiv=Refresh Content="0; Url=../../">';
135 $indexContent .= '</HEAD></HTML>';
136 GeneralUtility::writeFile($sessionSavePath . '/index.html', $indexContent);
137 }
138 }
139
140 /**
141 * Starts a new session
142 *
143 * @return string The session ID
144 */
145 public function startSession() {
146 $_SESSION['active'] = TRUE;
147 // Be sure to use our own session id, so create a new one
148 return $this->renewSession();
149 }
150
151 /**
152 * Destroys a session
153 */
154 public function destroySession() {
155 session_destroy();
156 }
157
158 /**
159 * Reset session. Sets _SESSION to empty array.
160 */
161 public function resetSession() {
162 $_SESSION = array();
163 $_SESSION['active'] = FALSE;
164 }
165
166 /**
167 * Generates a new session ID and sends it to the client.
168 *
169 * @return string the new session ID
170 */
171 private function renewSession() {
172 session_regenerate_id();
173 return session_id();
174 }
175
176 /**
177 * Checks whether we already have an active session.
178 *
179 * @return boolean TRUE if there is an active session, FALSE otherwise
180 */
181 public function hasSession() {
182 return ($_SESSION['active'] === TRUE);
183 }
184
185 /**
186 * Returns the session ID of the running session.
187 *
188 * @return string the session ID
189 */
190 public function getSessionId() {
191 return session_id();
192 }
193
194 /**
195 * Returns a session hash, which can only be calculated by the server.
196 * Used to store our session files without exposing the session ID.
197 *
198 * @param string $sessionId An alternative session ID. Defaults to our current session ID
199 * @throws \TYPO3\CMS\Install\Exception
200 * @return string the session hash
201 */
202 private function getSessionHash($sessionId = '') {
203 if (empty($GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'])) {
204 throw new \TYPO3\CMS\Install\Exception(
205 'No encryption key set to secure session',
206 1371243450
207 );
208 }
209 if (!$sessionId) {
210 $sessionId = $this->getSessionId();
211 }
212 return md5($GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'] . '|' . $sessionId);
213 }
214
215 /**
216 * Marks this session as an "authorized" one (login successful).
217 * Should only be called if:
218 * a) we have a valid session running
219 * b) the "password" or some other authorization mechanism really matched
220 *
221 * @return void
222 */
223 public function setAuthorized() {
224 $_SESSION['authorized'] = TRUE;
225 $_SESSION['lastSessionId'] = time();
226 $_SESSION['tstamp'] = time();
227 $_SESSION['expires'] = time() + $this->expireTimeInMinutes * 60;
228 // Renew the session id to avoid session fixation
229 $this->renewSession();
230 }
231
232 /**
233 * Check if we have an already authorized session
234 *
235 * @return boolean TRUE if this session has been authorized before (by a correct password)
236 */
237 public function isAuthorized() {
238 if (!$_SESSION['authorized']) {
239 return FALSE;
240 }
241 if ($_SESSION['expires'] < time()) {
242 // This session has already expired
243 return FALSE;
244 }
245 return TRUE;
246 }
247
248 /**
249 * Check if our session is expired.
250 * Useful only right after a FALSE "isAuthorized" to see if this is the
251 * reason for not being authorized anymore.
252 *
253 * @return boolean TRUE if an authorized session exists, but is expired
254 */
255 public function isExpired() {
256 if (!$_SESSION['authorized']) {
257 // Session never existed, means it is not "expired"
258 return FALSE;
259 }
260 if ($_SESSION['expires'] < time()) {
261 // This session was authorized before, but has expired
262 return TRUE;
263 }
264 return FALSE;
265 }
266
267 /**
268 * Refreshes our session information, rising the expire time.
269 * Also generates a new session ID every 5 minutes to minimize the risk of
270 * session hijacking.
271 *
272 * @return void
273 */
274 public function refreshSession() {
275 $_SESSION['tstamp'] = time();
276 $_SESSION['expires'] = time() + $this->expireTimeInMinutes * 60;
277 if (time() > $_SESSION['lastSessionId'] + $this->regenerateSessionIdTime * 60) {
278 // Renew our session ID
279 $_SESSION['lastSessionId'] = time();
280 $this->renewSession();
281 }
282 }
283
284 /**
285 * Add a message to "Flash" message storage.
286 *
287 * @param \TYPO3\CMS\Install\Status\StatusInterface $message A message to add
288 * @return void
289 */
290 public function addMessage(\TYPO3\CMS\Install\Status\StatusInterface $message) {
291 if (!is_array($_SESSION['messages'])) {
292 $_SESSION['messages'] = array();
293 }
294 $_SESSION['messages'][] = $message;
295 }
296
297 /**
298 * Return stored session messages and flush.
299 *
300 * @return array<\TYPO3\CMS\Install\Status\StatusInterface> Messages
301 */
302 public function getMessagesAndFlush() {
303 $messages = array();
304 if (is_array($_SESSION['messages'])) {
305 $messages = $_SESSION['messages'];
306 }
307 $_SESSION['messages'] = array();
308 return $messages;
309 }
310
311 /*************************
312 *
313 * PHP session handling with "secure" session files (hashed session id)
314 * see http://www.php.net/manual/en/function.session-set-save-handler.php
315 *
316 *************************/
317 /**
318 * Returns the file where to store our session data
319 *
320 * @param string $id
321 * @return string A filename
322 */
323 private function getSessionFile($id) {
324 $sessionSavePath = $this->getSessionSavePath();
325 return $sessionSavePath . '/hash_' . $this->getSessionHash($id);
326 }
327
328 /**
329 * Open function. See @session_set_save_handler
330 *
331 * @param string $savePath
332 * @param string $sessionName
333 * @return boolean
334 */
335 public function open($savePath, $sessionName) {
336 return TRUE;
337 }
338
339 /**
340 * Close function. See @session_set_save_handler
341 *
342 * @return boolean
343 */
344 public function close() {
345 return TRUE;
346 }
347
348 /**
349 * Read session data. See @session_set_save_handler
350 *
351 * @param string $id The session id
352 * @return string
353 */
354 public function read($id) {
355 $sessionFile = $this->getSessionFile($id);
356 return (string) (@file_get_contents($sessionFile));
357 }
358
359 /**
360 * Write session data. See @session_set_save_handler
361 *
362 * @param string $id The session id
363 * @param string $sessionData The data to be stored
364 * @return boolean
365 */
366 public function write($id, $sessionData) {
367 $sessionFile = $this->getSessionFile($id);
368 return GeneralUtility::writeFile($sessionFile, $sessionData);
369 }
370
371 /**
372 * Destroys one session. See @session_set_save_handler
373 *
374 * @param string $id The session id
375 * @return string
376 */
377 public function destroy($id) {
378 $sessionFile = $this->getSessionFile($id);
379 return @unlink($sessionFile);
380 }
381
382 /**
383 * Garbage collect session info. See @session_set_save_handler
384 *
385 * @param integer $maxLifeTime The setting of session.gc_maxlifetime
386 * @return boolean
387 */
388 public function gc($maxLifeTime) {
389 $sessionSavePath = $this->getSessionSavePath();
390 $files = glob($sessionSavePath . '/hash_*');
391 if (!is_array($files)) {
392 return TRUE;
393 }
394 foreach ($files as $filename) {
395 if (filemtime($filename) + $this->expireTimeInMinutes * 60 < time()) {
396 @unlink($filename);
397 }
398 }
399 return TRUE;
400 }
401
402 /**
403 * Writes the session data at the end, to overcome a PHP APC bug.
404 *
405 * Writes the session data in a proper context that is not affected by the APC bug:
406 * http://pecl.php.net/bugs/bug.php?id=16721.
407 *
408 * This behaviour was introduced in #17511, where self::write() made use of GeneralUtility
409 * which due to the APC bug throws a "Fatal error: Class 'GeneralUtility' not found"
410 * (and the session data is not saved). Calling session_write_close() at this point
411 * seems to be the most easy solution, according to PHP author.
412 *
413 * @return void
414 */
415 public function __destruct() {
416 session_write_close();
417 }
418 }