PageRenderTime 22ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/tine20/Tinebase/Session/Abstract.php

https://github.com/tine20/Tine-2.0-Open-Source-Groupware-and-CRM
PHP | 419 lines | 251 code | 52 blank | 116 comment | 42 complexity | 0338d7daf2ad45c02673a750c298befe MD5 | raw file
  1. <?php
  2. /**
  3. * Tine 2.0
  4. *
  5. * @package Tinebase
  6. * @subpackage Session
  7. * @license http://www.gnu.org/licenses/agpl.html AGPL Version 3
  8. * @author Guilherme Striquer Bisotto <guilherme.bisotto@serpro.gov.br>
  9. * @copyright Copyright (c) 2014-2021 Metaways Infosystems GmbH (http://www.metaways.de)
  10. *
  11. */
  12. /**
  13. * Abstract class for Session and Session Namespaces
  14. *
  15. * @package Tinebase
  16. * @subpackage Session
  17. */
  18. abstract class Tinebase_Session_Abstract extends Zend_Session_Namespace
  19. {
  20. /**
  21. * Default session directory name
  22. */
  23. const SESSION_DIR_NAME = 'tine20_sessions';
  24. /**
  25. * constant for session namespace (tinebase) registry index
  26. */
  27. const SESSION = 'session';
  28. protected static $_sessionEnabled = false;
  29. protected static $_isSetupSession = false;
  30. /**
  31. * get a value from the registry
  32. *
  33. */
  34. protected static function get($index)
  35. {
  36. return (Zend_Registry::isRegistered($index)) ? Zend_Registry::get($index) : NULL;
  37. }
  38. /**
  39. * set a registry value
  40. *
  41. * @return mixed value
  42. */
  43. protected static function set($index, $value)
  44. {
  45. Zend_Registry::set($index, $value);
  46. }
  47. /**
  48. * Create a session namespace or return an existing one
  49. *
  50. * @param unknown $_namespace
  51. * @throws Exception
  52. * @return Zend_Session_Namespace
  53. */
  54. protected static function _getSessionNamespace($_namespace)
  55. {
  56. $sessionNamespace = self::get($_namespace);
  57. if ($sessionNamespace == null) {
  58. try {
  59. $sessionNamespace = new Zend_Session_Namespace($_namespace);
  60. self::set($_namespace, $sessionNamespace);
  61. } catch (Exception $e) {
  62. self::expireSessionCookie();
  63. throw $e;
  64. }
  65. }
  66. return $sessionNamespace;
  67. }
  68. /**
  69. * Zend_Session::sessionExists encapsulation
  70. *
  71. * @return boolean
  72. */
  73. public static function sessionExists()
  74. {
  75. return Zend_Session::sessionExists();
  76. }
  77. /**
  78. * Zend_Session::isStarted encapsulation
  79. *
  80. * @return boolean
  81. */
  82. public static function isStarted()
  83. {
  84. return Zend_Session::isStarted();
  85. }
  86. /**
  87. * Destroy session and remove cookie
  88. */
  89. public static function destroyAndRemoveCookie()
  90. {
  91. if (self::sessionExists()) {
  92. if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(
  93. __METHOD__ . '::' . __LINE__ . ' Destroying session');
  94. try {
  95. Zend_Session::destroy(true, true);
  96. } catch (ErrorException $ee) {
  97. if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(
  98. __METHOD__ . '::' . __LINE__ . ' Session might have already been removed: '
  99. . $ee->getMessage());
  100. }
  101. }
  102. }
  103. /**
  104. * Destroy session but not remove cookie
  105. */
  106. public static function destroyAndMantainCookie()
  107. {
  108. Zend_Session::destroy(false, true);
  109. }
  110. /**
  111. * Zend_Session::writeClose encapsulation
  112. *
  113. * @param string $readonly
  114. */
  115. public static function writeClose($readonly = true)
  116. {
  117. if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(
  118. __METHOD__ . '::' . __LINE__ . ' Closing session (readonly: ' . (integer) $readonly . ')');
  119. Zend_Session::writeClose($readonly);
  120. }
  121. /**
  122. * Zend_Session::isWritable encapsulation
  123. *
  124. * @return boolean
  125. */
  126. public static function isWritable()
  127. {
  128. return Zend_Session::isWritable();
  129. }
  130. /**
  131. * Zend_Session::getId encapsulation
  132. *
  133. * @return string
  134. */
  135. public static function getId()
  136. {
  137. return Zend_Session::getId();
  138. }
  139. /**
  140. * Zend_Session::expireSessionCookie encapsulation
  141. */
  142. public static function expireSessionCookie()
  143. {
  144. Zend_Session::expireSessionCookie();
  145. }
  146. /**
  147. * Zend_Session::regenerateId encapsulation
  148. */
  149. public static function regenerateId()
  150. {
  151. Zend_Session::regenerateId();
  152. }
  153. /**
  154. * get session dir string (without PATH_SEP at the end)
  155. *
  156. * @return string
  157. */
  158. public static function getSessionDir()
  159. {
  160. $config = Tinebase_Core::getConfig();
  161. $sessionDir = ($config->session && $config->session->path)
  162. ? $config->session->path
  163. : null;
  164. #####################################
  165. # LEGACY/COMPATIBILITY:
  166. # (1) had to rename session.save_path key to sessiondir because otherwise the
  167. # generic save config method would interpret the "_" as array key/value seperator
  168. # (2) moved session config to subgroup 'session'
  169. if (empty($sessionDir)) {
  170. foreach (array('session.save_path', 'sessiondir') as $deprecatedSessionDir) {
  171. $sessionDir = $config->get($deprecatedSessionDir, null);
  172. if ($sessionDir) {
  173. Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . " config.inc.php key '{$deprecatedSessionDir}' should be renamed to 'path' and moved to 'session' group.");
  174. }
  175. }
  176. }
  177. #####################################
  178. if (empty($sessionDir) || !@is_writable($sessionDir)) {
  179. $sessionDir = session_save_path();
  180. if (empty($sessionDir) || !@is_writable($sessionDir)) {
  181. $sessionDir = Tinebase_Core::guessTempDir();
  182. }
  183. $sessionDirName = self::SESSION_DIR_NAME;
  184. $sessionDir .= DIRECTORY_SEPARATOR . $sessionDirName;
  185. }
  186. Tinebase_Core::getLogger()->DEBUG(__METHOD__ . '::' . __LINE__ . " Using session dir: " . $sessionDir);
  187. return $sessionDir;
  188. }
  189. /**
  190. * get session lifetime
  191. */
  192. public static function getSessionLifetime()
  193. {
  194. $config = Tinebase_Core::getConfig();
  195. $sessionLifetime = ($config->session && $config->session->lifetime) ? $config->session->lifetime : 86400; // one day is def
  196. /** @var Tinebase_Session_SessionLifetimeDelegateInterface $delegate */
  197. $delegate = Tinebase_Core::getDelegate('Tinebase', 'sessionLifetimeDelegate',
  198. 'Tinebase_Session_SessionLifetimeDelegateInterface');
  199. if (false !== $delegate) {
  200. return $delegate->getSessionLifetime($sessionLifetime);
  201. }
  202. return $sessionLifetime;
  203. }
  204. public static function getConfiguredSessionBackendType()
  205. {
  206. $config = Tinebase_Core::getConfig();
  207. return ($config->session && $config->session->backend) ? ucfirst($config->session->backend) :
  208. ucfirst(ini_get('session.save_handler'));
  209. }
  210. /**
  211. * set session backend
  212. */
  213. public static function setSessionBackend()
  214. {
  215. $config = Tinebase_Core::getConfig();
  216. $defaultSessionSavePath = ini_get('session.save_path');
  217. $backendType = self::getConfiguredSessionBackendType();
  218. $maxLifeTime = self::getSessionLifetime();
  219. switch ($backendType) {
  220. case 'Files': // this is the default for the ini setting session.save_handler
  221. case 'File':
  222. if ($config->gc_maxlifetime) {
  223. Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . " config.inc.php key 'gc_maxlifetime' should be renamed to 'lifetime' and moved to 'session' group.");
  224. $maxLifeTime = $config->get('gc_maxlifetime', 86400);
  225. }
  226. Zend_Session::setOptions(array(
  227. 'gc_maxlifetime' => $maxLifeTime
  228. ));
  229. $sessionSavepath = self::getSessionDir();
  230. if (ini_set('session.save_path', $sessionSavepath) !== FALSE) {
  231. if (!is_dir($sessionSavepath)) {
  232. mkdir($sessionSavepath, 0700);
  233. }
  234. } else {
  235. $sessionSavepath = $defaultSessionSavePath;
  236. }
  237. if (!ini_set('session.save_handler', 'files'))
  238. {
  239. Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__
  240. . " ini set didnĀ“t work. session.save_handler = " . ini_get('session.save_handler'));
  241. }
  242. if (!$config->session || !$config->session->nocleanup) {
  243. $lastSessionCleanup = Tinebase_Config::getInstance()->get(Tinebase_Config::LAST_SESSIONS_CLEANUP_RUN);
  244. if ($lastSessionCleanup instanceof DateTime && $lastSessionCleanup > Tinebase_DateTime::now()->subHour(2)) {
  245. Zend_Session::setOptions(array(
  246. 'gc_probability' => 0,
  247. 'gc_divisor' => 100
  248. ));
  249. } else if (@opendir($sessionSavepath) !== FALSE) {
  250. Zend_Session::setOptions(array(
  251. 'gc_probability' => 1,
  252. 'gc_divisor' => 100
  253. ));
  254. } else {
  255. Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__
  256. . " Unable to initialize automatic session cleanup. Check permissions to " . $sessionSavepath);
  257. }
  258. }
  259. break;
  260. case 'Redis':
  261. if ($config->session) {
  262. $host = ($config->session->host) ? $config->session->host : 'localhost';
  263. $port = ($config->session->port) ? $config->session->port : 6379;
  264. if ($config->session && $config->session->prefix) {
  265. $prefix = $config->session->prefix;
  266. } else {
  267. $prefix = ($config->database && $config->database->tableprefix) ? $config->database->tableprefix : 'tine20';
  268. }
  269. $prefix = $prefix . '_SESSION_';
  270. $redisProxy = new Zend_RedisProxy();
  271. if (!$redisProxy->connect($host, $port)) {
  272. throw new Tinebase_Exception_Backend('could not connect to session redis');
  273. }
  274. $redisSaveHandler = new Tinebase_Session_SaveHandler_Redis($redisProxy, $maxLifeTime, $prefix);
  275. $redisSaveHandler->setRedisLogDelegator(function($exception) {
  276. Tinebase_Exception::log($exception);
  277. });
  278. Zend_Session::setOptions([
  279. 'gc_maxlifetime' => $maxLifeTime,
  280. ]);
  281. Zend_Session::setSaveHandler($redisSaveHandler);
  282. } else {
  283. Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__
  284. . " Unable to setup redis proxy session backend - config missing");
  285. return;
  286. }
  287. break;
  288. default:
  289. break;
  290. }
  291. Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " Session of backend type '{$backendType}' configured.");
  292. }
  293. /**
  294. * activate session and set name in options
  295. *
  296. * @param $sessionName
  297. */
  298. public static function setSessionEnabled($sessionName)
  299. {
  300. self::setSessionOptions(array(
  301. 'name' => $sessionName
  302. ));
  303. self::$_sessionEnabled = true;
  304. self::$_isSetupSession = $sessionName === 'TINE20SETUPSESSID';
  305. }
  306. /**
  307. * @return bool
  308. *
  309. * TODO it would be better to look into the session options and check the name
  310. * TODO and maybe this can be removed as we already have Setup_Session and Tinebase_Session classes ...
  311. */
  312. public static function isSetupSession()
  313. {
  314. return self::$_isSetupSession;
  315. }
  316. /**
  317. * set session options
  318. *
  319. * @param array $_options
  320. */
  321. public static function setSessionOptions($options = array())
  322. {
  323. $options = array_merge(
  324. $options,
  325. array (
  326. 'cookie_httponly' => true,
  327. 'hash_function' => 1
  328. )
  329. );
  330. $baseUri = Tinebase_Core::getUrl(Tinebase_Core::GET_URL_PATH);
  331. $options['cookie_path'] = $baseUri;
  332. if (!empty($_SERVER['HTTPS']) && strtoupper($_SERVER['HTTPS']) != 'OFF') {
  333. $options['cookie_secure'] = true;
  334. }
  335. if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(
  336. __METHOD__ . '::' . __LINE__ . ' Session options: ' . print_r($options, true));
  337. Zend_Session::setOptions($options);
  338. }
  339. public static function getSessionEnabled()
  340. {
  341. return self::$_sessionEnabled;
  342. }
  343. /**
  344. * Gets Tinebase User session namespace
  345. *
  346. * @param string $sessionNamespace (optional)
  347. * @throws Zend_Session_Exception
  348. * @return Zend_Session_Namespace
  349. */
  350. public static function getSessionNamespace($sessionNamespace = 'Default')
  351. {
  352. if (! Tinebase_Session::isStarted()) {
  353. throw new Zend_Session_Exception('Session not started');
  354. }
  355. if (!self::getSessionEnabled()) {
  356. throw new Zend_Session_Exception('Session not enabled for request');
  357. }
  358. $sessionNamespace = (is_null($sessionNamespace)) ? get_called_class() . '_Namespace' : $sessionNamespace;
  359. try {
  360. return self::_getSessionNamespace($sessionNamespace);
  361. } catch(Exception $e) {
  362. Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' Session error: ' . $e->getMessage());
  363. Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' ' . $e->getTraceAsString());
  364. throw $e;
  365. }
  366. }
  367. }