summaryrefslogtreecommitdiffstats
path: root/library/Icinga/Web/Session
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--library/Icinga/Web/Session.php54
-rw-r--r--library/Icinga/Web/Session/Php72Session.php37
-rw-r--r--library/Icinga/Web/Session/PhpSession.php256
-rw-r--r--library/Icinga/Web/Session/Session.php126
-rw-r--r--library/Icinga/Web/Session/SessionNamespace.php201
5 files changed, 674 insertions, 0 deletions
diff --git a/library/Icinga/Web/Session.php b/library/Icinga/Web/Session.php
new file mode 100644
index 0000000..40df89f
--- /dev/null
+++ b/library/Icinga/Web/Session.php
@@ -0,0 +1,54 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web;
+
+use Icinga\Web\Session\PhpSession;
+use Icinga\Web\Session\Session as BaseSession;
+use Icinga\Exception\ProgrammingError;
+
+/**
+ * Session container
+ */
+class Session
+{
+ /**
+ * The current session
+ *
+ * @var BaseSession $session
+ */
+ private static $session;
+
+ /**
+ * Create the session
+ *
+ * @param BaseSession $session
+ *
+ * @return BaseSession
+ */
+ public static function create(BaseSession $session = null)
+ {
+ if ($session === null) {
+ self::$session = PhpSession::create();
+ } else {
+ self::$session = $session;
+ }
+
+ return self::$session;
+ }
+
+ /**
+ * Return the current session
+ *
+ * @return BaseSession
+ * @throws ProgrammingError
+ */
+ public static function getSession()
+ {
+ if (self::$session === null) {
+ self::create();
+ }
+
+ return self::$session;
+ }
+}
diff --git a/library/Icinga/Web/Session/Php72Session.php b/library/Icinga/Web/Session/Php72Session.php
new file mode 100644
index 0000000..e6a6b19
--- /dev/null
+++ b/library/Icinga/Web/Session/Php72Session.php
@@ -0,0 +1,37 @@
+<?php
+/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Session;
+
+use Icinga\Application\Logger;
+use Icinga\Exception\ConfigurationError;
+use Icinga\Web\Cookie;
+
+/**
+ * Session implementation in PHP
+ */
+class Php72Session extends PhpSession
+{
+ /**
+ * Open a PHP session
+ */
+ protected function open()
+ {
+ session_name($this->sessionName);
+
+ $cookie = new Cookie('bogus');
+ session_set_cookie_params(
+ 0,
+ $cookie->getPath(),
+ $cookie->getDomain(),
+ $cookie->isSecure(),
+ true
+ );
+
+ session_start(array(
+ 'use_cookies' => true,
+ 'use_only_cookies' => true,
+ 'use_trans_sid' => false
+ ));
+ }
+}
diff --git a/library/Icinga/Web/Session/PhpSession.php b/library/Icinga/Web/Session/PhpSession.php
new file mode 100644
index 0000000..36dd84e
--- /dev/null
+++ b/library/Icinga/Web/Session/PhpSession.php
@@ -0,0 +1,256 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Session;
+
+use Icinga\Application\Logger;
+use Icinga\Exception\ConfigurationError;
+use Icinga\Web\Cookie;
+
+/**
+ * Session implementation in PHP
+ */
+class PhpSession extends Session
+{
+ /**
+ * The namespace prefix
+ *
+ * Used to differentiate between standard session keys and namespace identifiers
+ */
+ const NAMESPACE_PREFIX = 'ns.';
+
+ /**
+ * Whether the session has already been closed
+ *
+ * @var bool
+ */
+ protected $hasBeenTouched = false;
+
+ /**
+ * Name of the session
+ *
+ * @var string
+ */
+ protected $sessionName = 'Icingaweb2';
+
+ /**
+ * Create a new PHPSession object using the provided options (if any)
+ *
+ * @param array $options An optional array of ini options to set
+ *
+ * @return static
+ *
+ * @throws ConfigurationError
+ * @see http://php.net/manual/en/session.configuration.php
+ */
+ public static function create(array $options = null)
+ {
+ return version_compare(PHP_VERSION, '7.2.0') < 0 ? new self($options) : new Php72Session($options);
+ }
+
+ /**
+ * Create a new PHPSession object using the provided options (if any)
+ *
+ * @param array $options An optional array of ini options to set
+ *
+ * @throws ConfigurationError
+ * @see http://php.net/manual/en/session.configuration.php
+ */
+ public function __construct(array $options = null)
+ {
+ $defaultCookieOptions = array(
+ 'use_trans_sid' => false,
+ 'use_cookies' => true,
+ 'cookie_httponly' => true,
+ 'use_only_cookies' => true
+ );
+
+ if (version_compare(PHP_VERSION, '7.1.0') < 0) {
+ $defaultCookieOptions['hash_function'] = true;
+ $defaultCookieOptions['hash_bits_per_character'] = 5;
+ } else {
+ $defaultCookieOptions['sid_bits_per_character'] = 5;
+ }
+
+ if ($options !== null) {
+ $options = array_merge($defaultCookieOptions, $options);
+ } else {
+ $options = $defaultCookieOptions;
+ }
+
+ if (array_key_exists('test_session_name', $options)) {
+ $this->sessionName = $options['test_session_name'];
+ unset($options['test_session_name']);
+ }
+
+ foreach ($options as $sessionVar => $value) {
+ if (ini_set("session." . $sessionVar, $value) === false) {
+ Logger::warning(
+ 'Could not set php.ini setting %s = %s. This might affect your sessions behaviour.',
+ $sessionVar,
+ $value
+ );
+ }
+ }
+
+ $sessionSavePath = session_save_path() ?: sys_get_temp_dir();
+ if (session_module_name() === 'files' && !is_writable($sessionSavePath)) {
+ throw new ConfigurationError("Can't save session, path '$sessionSavePath' is not writable.");
+ }
+
+ if ($this->exists()) {
+ // We do not want to start a new session here if there is not any
+ $this->read();
+ }
+ }
+
+ /**
+ * Open a PHP session
+ */
+ protected function open()
+ {
+ session_name($this->sessionName);
+
+ if ($this->hasBeenTouched) {
+ $cacheLimiter = ini_get('session.cache_limiter');
+ ini_set('session.use_cookies', false);
+ ini_set('session.use_only_cookies', false);
+ ini_set('session.cache_limiter', null);
+ }
+
+ $cookie = new Cookie('bogus');
+ session_set_cookie_params(
+ 0,
+ $cookie->getPath(),
+ $cookie->getDomain(),
+ $cookie->isSecure(),
+ true
+ );
+
+ session_start();
+
+ if ($this->hasBeenTouched) {
+ ini_set('session.use_cookies', true);
+ ini_set('session.use_only_cookies', true);
+ /** @noinspection PhpUndefinedVariableInspection */
+ ini_set('session.cache_limiter', $cacheLimiter);
+ }
+ }
+
+ /**
+ * Read all values written to the underling session and make them accessible.
+ */
+ public function read()
+ {
+ $this->clear();
+ $this->open();
+
+ foreach ($_SESSION as $key => $value) {
+ if (strpos($key, self::NAMESPACE_PREFIX) === 0) {
+ $namespace = new SessionNamespace();
+ $namespace->setAll($value);
+ $this->namespaces[substr($key, strlen(self::NAMESPACE_PREFIX))] = $namespace;
+ } else {
+ $this->set($key, $value);
+ }
+ }
+
+ session_write_close();
+ $this->hasBeenTouched = true;
+ }
+
+ /**
+ * Write all values of this session object to the underlying session implementation
+ */
+ public function write()
+ {
+ $this->open();
+
+ foreach ($this->removed as $key) {
+ unset($_SESSION[$key]);
+ }
+ foreach ($this->values as $key => $value) {
+ $_SESSION[$key] = $value;
+ }
+ foreach ($this->removedNamespaces as $identifier) {
+ unset($_SESSION[self::NAMESPACE_PREFIX . $identifier]);
+ }
+ foreach ($this->namespaces as $identifier => $namespace) {
+ $_SESSION[self::NAMESPACE_PREFIX . $identifier] = $namespace->getAll();
+ }
+
+ session_write_close();
+ $this->hasBeenTouched = true;
+ }
+
+ /**
+ * Delete the current session, causing all session information to be lost
+ */
+ public function purge()
+ {
+ $this->open();
+ $_SESSION = array();
+ $this->clear();
+ session_destroy();
+ $this->clearCookies();
+ session_write_close();
+ $this->hasBeenTouched = true;
+ }
+
+ /**
+ * Remove session cookies
+ */
+ protected function clearCookies()
+ {
+ if (ini_get('session.use_cookies')) {
+ Logger::debug('Clear session cookie');
+ $params = session_get_cookie_params();
+ setcookie(
+ session_name(),
+ '',
+ time() - 42000,
+ $params['path'],
+ $params['domain'],
+ $params['secure'],
+ $params['httponly']
+ );
+ }
+ }
+
+ /**
+ * @see Session::getId()
+ */
+ public function getId()
+ {
+ if (($id = session_id()) === '') {
+ // Make sure we actually get a id
+ $this->open();
+ session_write_close();
+ $this->hasBeenTouched = true;
+ $id = session_id();
+ }
+
+ return $id;
+ }
+
+ /**
+ * Assign a new sessionId to the currently active session
+ */
+ public function refreshId()
+ {
+ $this->open();
+ if ($this->exists()) {
+ session_regenerate_id();
+ }
+ session_write_close();
+ $this->hasBeenTouched = true;
+ }
+
+ /**
+ * @see Session::exists()
+ */
+ public function exists()
+ {
+ return isset($_COOKIE[$this->sessionName]);
+ }
+}
diff --git a/library/Icinga/Web/Session/Session.php b/library/Icinga/Web/Session/Session.php
new file mode 100644
index 0000000..e73e9b4
--- /dev/null
+++ b/library/Icinga/Web/Session/Session.php
@@ -0,0 +1,126 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Session;
+
+use Icinga\Exception\NotImplementedError;
+
+/**
+ * Base class for handling sessions
+ */
+abstract class Session extends SessionNamespace
+{
+ /**
+ * Container for session namespaces
+ *
+ * @var array
+ */
+ protected $namespaces = array();
+
+ /**
+ * The identifiers of all namespaces removed from this session
+ *
+ * @var array
+ */
+ protected $removedNamespaces = array();
+
+ /**
+ * Read all values from the underlying session implementation
+ */
+ abstract public function read();
+
+ /**
+ * Persists changes to the underlying session implementation
+ */
+ public function write()
+ {
+ throw new NotImplementedError('You are required to implement write() in your session implementation');
+ }
+
+ /**
+ * Return whether a session exists
+ *
+ * @return bool
+ */
+ abstract public function exists();
+
+ /**
+ * Purge session
+ */
+ abstract public function purge();
+
+ /**
+ * Assign a new session id to this session.
+ */
+ abstract public function refreshId();
+
+ /**
+ * Return the id of this session
+ *
+ * @return string
+ */
+ abstract public function getId();
+
+ /**
+ * Get or create a new session namespace
+ *
+ * @param string $identifier The namespace's identifier
+ *
+ * @return SessionNamespace
+ */
+ public function getNamespace($identifier)
+ {
+ if (!isset($this->namespaces[$identifier])) {
+ if (in_array($identifier, $this->removedNamespaces, true)) {
+ unset($this->removedNamespaces[array_search($identifier, $this->removedNamespaces, true)]);
+ }
+
+ $this->namespaces[$identifier] = new SessionNamespace();
+ }
+
+ return $this->namespaces[$identifier];
+ }
+
+ /**
+ * Return whether the given session namespace exists
+ *
+ * @param string $identifier The namespace's identifier to check
+ *
+ * @return bool
+ */
+ public function hasNamespace($identifier)
+ {
+ return isset($this->namespaces[$identifier]);
+ }
+
+ /**
+ * Remove the given session namespace
+ *
+ * @param string $identifier The identifier of the namespace to remove
+ */
+ public function removeNamespace($identifier)
+ {
+ unset($this->namespaces[$identifier]);
+ $this->removedNamespaces[] = $identifier;
+ }
+
+ /**
+ * Return whether the session has changed
+ *
+ * @return bool
+ */
+ public function hasChanged()
+ {
+ return parent::hasChanged() || false === empty($this->namespaces) || false === empty($this->removedNamespaces);
+ }
+
+ /**
+ * Clear all values and namespaces from the session cache
+ */
+ public function clear()
+ {
+ parent::clear();
+ $this->namespaces = array();
+ $this->removedNamespaces = array();
+ }
+}
diff --git a/library/Icinga/Web/Session/SessionNamespace.php b/library/Icinga/Web/Session/SessionNamespace.php
new file mode 100644
index 0000000..1c9c13f
--- /dev/null
+++ b/library/Icinga/Web/Session/SessionNamespace.php
@@ -0,0 +1,201 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Session;
+
+use Exception;
+use ArrayIterator;
+use Icinga\Exception\IcingaException;
+use IteratorAggregate;
+use Traversable;
+
+/**
+ * Container for session values
+ */
+class SessionNamespace implements IteratorAggregate
+{
+ /**
+ * The actual values stored in this container
+ *
+ * @var array
+ */
+ protected $values = array();
+
+ /**
+ * The names of all values removed from this container
+ *
+ * @var array
+ */
+ protected $removed = array();
+
+ /**
+ * Return an iterator for all values in this namespace
+ *
+ * @return ArrayIterator
+ */
+ public function getIterator(): Traversable
+ {
+ return new ArrayIterator($this->getAll());
+ }
+
+ /**
+ * Set a session value by property access
+ *
+ * @param string $key The value's name
+ * @param mixed $value The value
+ */
+ public function __set($key, $value)
+ {
+ $this->set($key, $value);
+ }
+
+ /**
+ * Return a session value by property access
+ *
+ * @param string $key The value's name
+ *
+ * @return mixed The value
+ * @throws Exception When the given value-name is not found
+ */
+ public function __get($key)
+ {
+ if (!array_key_exists($key, $this->values)) {
+ throw new IcingaException(
+ 'Cannot access non-existent session value "%s"',
+ $key
+ );
+ }
+
+ return $this->get($key);
+ }
+
+ /**
+ * Return whether the given session value is set
+ *
+ * @param string $key The value's name
+ * @return bool
+ */
+ public function __isset($key)
+ {
+ return isset($this->values[$key]);
+ }
+
+ /**
+ * Unset the given session value
+ *
+ * @param string $key The value's name
+ */
+ public function __unset($key)
+ {
+ $this->delete($key);
+ }
+
+ /**
+ * Setter for session values
+ *
+ * @param string $key Name of value
+ * @param mixed $value Value to set
+ *
+ * @return $this
+ */
+ public function set($key, $value)
+ {
+ $this->values[$key] = $value;
+
+ if (in_array($key, $this->removed, true)) {
+ unset($this->removed[array_search($key, $this->removed, true)]);
+ }
+
+ return $this;
+ }
+
+ public function setByRef($key, &$value)
+ {
+ $this->values[$key] = & $value;
+
+ if (in_array($key, $this->removed, true)) {
+ unset($this->removed[array_search($key, $this->removed, true)]);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Getter for session values
+ *
+ * @param string $key Name of the value to return
+ * @param mixed $default Default value to return
+ *
+ * @return mixed
+ */
+ public function get($key, $default = null)
+ {
+ return isset($this->values[$key]) ? $this->values[$key] : $default;
+ }
+
+ public function & getByRef($key, $default = null)
+ {
+ $value = $default;
+ if (isset($this->values[$key])) {
+ $value = & $this->values[$key];
+ }
+
+ return $value;
+ }
+
+ /**
+ * Delete the given value from the session
+ *
+ * @param string $key The value's name
+ */
+ public function delete($key)
+ {
+ $this->removed[] = $key;
+ unset($this->values[$key]);
+ }
+
+ /**
+ * Getter for all session values
+ *
+ * @return array
+ */
+ public function getAll()
+ {
+ return $this->values;
+ }
+
+ /**
+ * Put an array into the session
+ *
+ * @param array $values Values to set
+ * @param bool $overwrite Overwrite existing values
+ */
+ public function setAll(array $values, $overwrite = false)
+ {
+ foreach ($values as $key => $value) {
+ if ($this->get($key, $value) !== $value && !$overwrite) {
+ continue;
+ }
+ $this->set($key, $value);
+ }
+ }
+
+ /**
+ * Return whether the session namespace has been changed
+ *
+ * @return bool
+ */
+ public function hasChanged()
+ {
+ return false === empty($this->values) || false === empty($this->removed);
+ }
+
+ /**
+ * Clear all values from the session namespace
+ */
+ public function clear()
+ {
+ $this->values = array();
+ $this->removed = array();
+ }
+}