Replace uses of join() by implode()
[lhc/web/wiklou.git] / includes / session / Session.php
index 840baa7..0fd8fa8 100644 (file)
@@ -23,6 +23,7 @@
 
 namespace MediaWiki\Session;
 
+use Psr\Log\LoggerInterface;
 use User;
 use WebRequest;
 
@@ -41,24 +42,28 @@ use WebRequest;
  * The Session object also serves as a replacement for PHP's $_SESSION,
  * managing access to per-session data.
  *
- * @todo Once we drop support for PHP 5.3.3, implementing ArrayAccess would be nice.
  * @ingroup Session
  * @since 1.27
  */
-final class Session implements \Countable, \Iterator {
+final class Session implements \Countable, \Iterator, \ArrayAccess {
        /** @var SessionBackend Session backend */
        private $backend;
 
        /** @var int Session index */
        private $index;
 
+       /** @var LoggerInterface */
+       private $logger;
+
        /**
         * @param SessionBackend $backend
         * @param int $index
+        * @param LoggerInterface $logger
         */
-       public function __construct( SessionBackend $backend, $index ) {
+       public function __construct( SessionBackend $backend, $index, LoggerInterface $logger ) {
                $this->backend = $backend;
                $this->index = $index;
+               $this->logger = $logger;
        }
 
        public function __destruct() {
@@ -119,6 +124,13 @@ final class Session implements \Countable, \Iterator {
                $this->backend->persist();
        }
 
+       /**
+        * Make this session not be persisted across requests
+        */
+       public function unpersist() {
+               $this->backend->unpersist();
+       }
+
        /**
         * Indicate whether the user should be remembered independently of the
         * session ID.
@@ -235,7 +247,7 @@ final class Session implements \Countable, \Iterator {
        public function clear() {
                $data = &$this->backend->getData();
                if ( $data ) {
-                       $data = array();
+                       $data = [];
                        $this->backend->dirty();
                }
                if ( $this->backend->canSetUser() ) {
@@ -271,7 +283,7 @@ final class Session implements \Countable, \Iterator {
        /**
         * Fetch a value from the session
         * @param string|int $key
-        * @param mixed $default
+        * @param mixed $default Returned if $this->exists( $key ) would be false
         * @return mixed
         */
        public function get( $key, $default = null ) {
@@ -281,6 +293,7 @@ final class Session implements \Countable, \Iterator {
 
        /**
         * Test if a value exists in the session
+        * @note Unlike isset(), null values are considered to exist.
         * @param string|int $key
         * @return bool
         */
@@ -314,6 +327,58 @@ final class Session implements \Countable, \Iterator {
                }
        }
 
+       /**
+        * Fetch a CSRF token from the session
+        *
+        * Note that this does not persist the session, which you'll probably want
+        * to do if you want the token to actually be useful.
+        *
+        * @param string|string[] $salt Token salt
+        * @param string $key Token key
+        * @return MediaWiki\\Session\\SessionToken
+        */
+       public function getToken( $salt = '', $key = 'default' ) {
+               $new = false;
+               $secrets = $this->get( 'wsTokenSecrets' );
+               if ( !is_array( $secrets ) ) {
+                       $secrets = [];
+               }
+               if ( isset( $secrets[$key] ) && is_string( $secrets[$key] ) ) {
+                       $secret = $secrets[$key];
+               } else {
+                       $secret = \MWCryptRand::generateHex( 32 );
+                       $secrets[$key] = $secret;
+                       $this->set( 'wsTokenSecrets', $secrets );
+                       $new = true;
+               }
+               if ( is_array( $salt ) ) {
+                       $salt = implode( '|', $salt );
+               }
+               return new Token( $secret, (string)$salt, $new );
+       }
+
+       /**
+        * Remove a CSRF token from the session
+        *
+        * The next call to self::getToken() with $key will generate a new secret.
+        *
+        * @param string $key Token key
+        */
+       public function resetToken( $key = 'default' ) {
+               $secrets = $this->get( 'wsTokenSecrets' );
+               if ( is_array( $secrets ) && isset( $secrets[$key] ) ) {
+                       unset( $secrets[$key] );
+                       $this->set( 'wsTokenSecrets', $secrets );
+               }
+       }
+
+       /**
+        * Remove all CSRF tokens from the session
+        */
+       public function resetAllTokens() {
+               $this->remove( 'wsTokenSecrets' );
+       }
+
        /**
         * Delay automatic saving while multiple updates are being made
         *
@@ -367,6 +432,39 @@ final class Session implements \Countable, \Iterator {
                return key( $data ) !== null;
        }
 
+       /**
+        * @note Despite the name, this seems to be intended to implement isset()
+        *  rather than array_key_exists(). So do that.
+        */
+       public function offsetExists( $offset ) {
+               $data = &$this->backend->getData();
+               return isset( $data[$offset] );
+       }
+
+       /**
+        * @note This supports indirect modifications but can't mark the session
+        *  dirty when those happen. SessionBackend::save() checks the hash of the
+        *  data to detect such changes.
+        * @note Accessing a nonexistent key via this mechanism causes that key to
+        *  be created with a null value, and does not raise a PHP warning.
+        */
+       public function &offsetGet( $offset ) {
+               $data = &$this->backend->getData();
+               if ( !array_key_exists( $offset, $data ) ) {
+                       $ex = new \Exception( "Undefined index (auto-adds to session with a null value): $offset" );
+                       $this->logger->debug( $ex->getMessage(), [ 'exception' => $ex ] );
+               }
+               return $data[$offset];
+       }
+
+       public function offsetSet( $offset, $value ) {
+               $this->set( $offset, $value );
+       }
+
+       public function offsetUnset( $offset ) {
+               $this->remove( $offset );
+       }
+
        /**@}*/
 
 }