Drupal 8.0.0 beta 12. More info: https://www.drupal.org/node/2514176

This commit is contained in:
Pantheon Automation 2015-08-17 17:00:26 -07:00 committed by Greg Anderson
commit 9921556621
13277 changed files with 1459781 additions and 0 deletions

View file

@ -0,0 +1,152 @@
<?php
/**
* @file
* Contains \Drupal\Core\Session\AccountInterface.
*/
namespace Drupal\Core\Session;
/**
* Defines an account interface which represents the current user.
*
* Defines an object that has a user id, roles and can have session data. The
* interface is implemented both by the global session and the user entity.
*
* @ingroup user_api
*/
interface AccountInterface {
/**
* Role ID for anonymous users.
*/
const ANONYMOUS_ROLE = 'anonymous';
/**
* Role ID for authenticated users.
*/
const AUTHENTICATED_ROLE = 'authenticated';
/**
* Returns the user ID or 0 for anonymous.
*
* @return int
* The user ID.
*/
public function id();
/**
* Returns a list of roles.
*
* @param bool $exclude_locked_roles
* (optional) If TRUE, locked roles (anonymous/authenticated) are not returned.
*
* @return array
* List of role IDs.
*/
public function getRoles($exclude_locked_roles = FALSE);
/**
* Checks whether a user has a certain permission.
*
* @param string $permission
* The permission string to check.
*
* @return bool
* TRUE if the user has the permission, FALSE otherwise.
*/
public function hasPermission($permission);
/**
* Returns TRUE if the account is authenticated.
*
* @return bool
* TRUE if the account is authenticated.
*/
public function isAuthenticated();
/**
* Returns TRUE if the account is anonymous.
*
* @return bool
* TRUE if the account is anonymous.
*/
public function isAnonymous();
/**
* Returns the preferred language code of the account.
*
* @param bool $fallback_to_default
* (optional) Whether the return value will fall back to the site default
* language if the user has no language preference.
*
* @return string
* The language code that is preferred by the account. If the preferred
* language is not set or is a language not configured anymore on the site,
* the site default is returned or an empty string is returned (if
* $fallback_to_default is FALSE).
*/
public function getPreferredLangcode($fallback_to_default = TRUE);
/**
* Returns the preferred administrative language code of the account.
*
* Defines which language is used on administrative pages.
*
* @param bool $fallback_to_default
* (optional) Whether the return value will fall back to the site default
* language if the user has no administration language preference.
*
* @return string
* The language code that is preferred by the account for administration
* pages. If the preferred language is not set or is a language not
* configured anymore on the site, the site default is returned or an empty
* string is returned (if $fallback_to_default is FALSE).
*/
public function getPreferredAdminLangcode($fallback_to_default = TRUE);
/**
* Returns the username of this account.
*
* By default, the passed-in object's 'name' property is used if it exists, or
* else, the site-defined value for the 'anonymous' variable. However, a module
* may override this by implementing
* hook_user_format_name_alter(&$name, $account).
*
* @see hook_user_format_name_alter()
*
* @return
* An unsanitized string with the username to display. The code receiving
* this result must ensure that \Drupal\Component\Utility\SafeMarkup::checkPlain()
* is called on it before it is
* printed to the page.
*/
public function getUsername();
/**
* Returns the email address of this account.
*
* @return string
* The email address.
*/
public function getEmail();
/**
* Returns the timezone of this account.
*
* @return string
* Name of the timezone.
*/
public function getTimeZone();
/**
* The timestamp when the account last accessed the site.
*
* A value of 0 means the user has never accessed the site.
*
* @return int
* Timestamp of the last access.
*/
public function getLastAccessedTime();
}

View file

@ -0,0 +1,180 @@
<?php
/**
* @file
* Contains \Drupal\Core\Session\AccountProxy.
*/
namespace Drupal\Core\Session;
/**
* A proxied implementation of AccountInterface.
*
* The reason why we need an account proxy is that we don't want to have global
* state directly stored in the container.
*
* This proxy object avoids multiple invocations of the authentication manager
* which can happen if the current user is accessed in constructors. It also
* allows legacy code to change the current user where the user cannot be
* directly injected into dependent code.
*/
class AccountProxy implements AccountProxyInterface {
/**
* The instantiated account.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $account;
/**
* Initial account id.
*
* @var int
*/
protected $initialAccountId;
/**
* {@inheritdoc}
*/
public function setAccount(AccountInterface $account) {
// If the passed account is already proxied, use the actual account instead
// to prevent loops.
if ($account instanceof static) {
$account = $account->getAccount();
}
$this->account = $account;
date_default_timezone_set(drupal_get_user_timezone());
}
/**
* {@inheritdoc}
*/
public function getAccount() {
if (!isset($this->account)) {
if ($this->initialAccountId) {
// After the container is rebuilt, DrupalKernel sets the initial
// account to the id of the logged in user. This is necessary in order
// to refresh the user account reference here.
$this->account = $this->loadUserEntity($this->initialAccountId);
}
else {
$this->account = new AnonymousUserSession();
}
}
return $this->account;
}
/**
* {@inheritdoc}
*/
public function id() {
return $this->getAccount()->id();
}
/**
* {@inheritdoc}
*/
public function getRoles($exclude_locked_roles = FALSE) {
return $this->getAccount()->getRoles($exclude_locked_roles);
}
/**
* {@inheritdoc}
*/
public function hasPermission($permission) {
return $this->getAccount()->hasPermission($permission);
}
/**
* {@inheritdoc}
*/
public function isAuthenticated() {
return $this->getAccount()->isAuthenticated();
}
/**
* {@inheritdoc}
*/
public function isAnonymous() {
return $this->getAccount()->isAnonymous();
}
/**
* {@inheritdoc}
*/
public function getPreferredLangcode($fallback_to_default = TRUE) {
return $this->getAccount()->getPreferredLangcode($fallback_to_default);
}
/**
* {@inheritdoc}
*/
public function getPreferredAdminLangcode($fallback_to_default = TRUE) {
return $this->getAccount()->getPreferredAdminLangcode($fallback_to_default);
}
/**
* {@inheritdoc}
*/
public function getUsername() {
return $this->getAccount()->getUsername();
}
/**
* {@inheritdoc}
*/
public function getEmail() {
return $this->getAccount()->getEmail();
}
/**
* {@inheritdoc}
*/
public function getTimeZone() {
return $this->getAccount()->getTimeZone();
}
/**
* {@inheritdoc}
*/
public function getLastAccessedTime() {
return $this->getAccount()->getLastAccessedTime();
}
/**
* {@inheritdoc}
*/
public function setInitialAccountId($account_id) {
if (isset($this->account)) {
throw new \LogicException('AccountProxyInterface::setInitialAccountId() cannot be called after an account was set on the AccountProxy');
}
$this->initialAccountId = $account_id;
}
/**
* Load a user entity.
*
* The entity manager requires additional initialization code and cache
* clearing after the list of modules is changed. Therefore it is necessary to
* retrieve it as late as possible.
*
* Because of serialization issues it is currently not possible to inject the
* container into the AccountProxy. Thus it is necessary to retrieve the
* entity manager statically.
*
* @see https://www.drupal.org/node/2430447
*
* @param int $account_id
* The id of an account to load.
*
* @return \Drupal\Core\Session\AccountInterface|NULL
* An account or NULL if none is found.
*/
protected function loadUserEntity($account_id) {
return \Drupal::entityManager()->getStorage('user')->load($account_id);
}
}

View file

@ -0,0 +1,51 @@
<?php
/**
* @file
* Contains \Drupal\Core\Session\AccountProxyInterface.
*/
namespace Drupal\Core\Session;
/**
* Defines an interface for a service which has the current account stored.
*
* @ingroup user_api
*/
interface AccountProxyInterface extends AccountInterface {
/**
* Sets the currently wrapped account.
*
* Setting the current account is highly discouraged! Instead, make sure to
* inject the desired user object into the dependent code directly.
*
* A preferable method of account impersonation is to use
* \Drupal\Core\Session\AccountSwitcherInterface::switchTo() and
* \Drupal\Core\Session\AccountSwitcherInterface::switchBack().
*
* @param \Drupal\Core\Session\AccountInterface $account
* The current account.
*/
public function setAccount(AccountInterface $account);
/**
* Gets the currently wrapped account.
*
* @return \Drupal\Core\Session\AccountInterface
* The current account.
*/
public function getAccount();
/**
* Sets the id of the initial account.
*
* Never use this method, its sole purpose is to work around weird effects
* during mid-request container rebuilds.
*
* @param int $account_id
* The id of the initial account.
*/
public function setInitialAccountId($account_id);
}

View file

@ -0,0 +1,96 @@
<?php
/**
* @file
* Contains \Drupal\Core\Session\AccountSwitcher.
*/
namespace Drupal\Core\Session;
/**
* An implementation of AccountSwitcherInterface.
*
* This allows for safe switching of user accounts by ensuring that session
* data for one user is not leaked in to others. It also provides a stack that
* allows reverting to a previous user after switching.
*/
class AccountSwitcher implements AccountSwitcherInterface {
/**
* A stack of previous overridden accounts.
*
* @var \Drupal\Core\Session\AccountInterface[]
*/
protected $accountStack = array();
/**
* The current user service.
*
* @var \Drupal\Core\Session\AccountProxyInterface
*/
protected $currentUser = array();
/**
* The write-safe session handler.
*
* @var \Drupal\Core\Session\WriteSafeSessionHandlerInterface
*/
protected $writeSafeHandler;
/**
* The original state of session saving prior to account switching.
*
* @var bool
*/
protected $originalSessionSaving;
/**
* Constructs a new AccountSwitcher.
*
* @param \Drupal\Core\Session\AccountProxyInterface $current_user
* The current user service.
* @param \Drupal\Core\Session\WriteSafeSessionHandlerInterface $write_safe_handler
* The write-safe session handler.
*/
public function __construct(AccountProxyInterface $current_user, WriteSafeSessionHandlerInterface $write_safe_handler) {
$this->currentUser = $current_user;
$this->writeSafeHandler = $write_safe_handler;
}
/**
* {@inheritdoc}
*/
public function switchTo(AccountInterface $account) {
// Prevent session information from being saved and push previous account.
if (!isset($this->originalSessionSaving)) {
// Ensure that only the first session saving status is saved.
$this->originalSessionSaving = $this->writeSafeHandler->isSessionWritable();
}
$this->writeSafeHandler->setSessionWritable(FALSE);
array_push($this->accountStack, $this->currentUser->getAccount());
$this->currentUser->setAccount($account);
return $this;
}
/**
* {@inheritdoc}
*/
public function switchBack() {
// Restore the previous account from the stack.
if (!empty($this->accountStack)) {
$this->currentUser->setAccount(array_pop($this->accountStack));
}
else {
throw new \RuntimeException('No more accounts to revert to.');
}
// Restore original session saving status if all account switches are
// reverted.
if (empty($this->accountStack)) {
if ($this->originalSessionSaving) {
$this->writeSafeHandler->setSessionWritable(TRUE);
}
}
return $this;
}
}

View file

@ -0,0 +1,43 @@
<?php
/**
* @file
* Contains \Drupal\Core\Session\AccountSwitcherInterface.
*/
namespace Drupal\Core\Session;
/**
* Defines an interface for a service for safe account switching.
*
* @ingroup user_api
*/
interface AccountSwitcherInterface {
/**
* Safely switches to another account.
*
* Each invocation of AccountSwitcherInterface::switchTo() must be
* matched by a corresponding invocation of
* AccountSwitcherInterface::switchBack() in the same function.
*
* @param \Drupal\Core\Session\AccountInterface $account
* The account to switch to.
*
* @return \Drupal\Core\Session\AccountSwitcherInterface
* $this.
*/
public function switchTo(AccountInterface $account);
/**
* Reverts to a previous account after switching.
*
* @return \Drupal\Core\Session\AccountSwitcherInterface
* $this.
*
* @throws \RuntimeException
* When there are no more account switches to revert.
*/
public function switchBack();
}

View file

@ -0,0 +1,23 @@
<?php
/**
* @file
* Contains \Drupal\Core\Session\AnonymousUserSession.
*/
namespace Drupal\Core\Session;
/**
* An account implementation representing an anonymous user.
*/
class AnonymousUserSession extends UserSession {
/**
* Constructs a new anonymous user session.
*
* Intentionally don't allow parameters to be passed in like UserSession.
*/
public function __construct() {
}
}

View file

@ -0,0 +1,63 @@
<?php
/**
* @file
* Contains \Drupal\Core\Session\MetadataBag.
*/
namespace Drupal\Core\Session;
use Drupal\Core\Site\Settings;
use Symfony\Component\HttpFoundation\Session\Storage\MetadataBag as SymfonyMetadataBag;
/**
* Provides a container for application specific session metadata.
*/
class MetadataBag extends SymfonyMetadataBag {
/**
* The key used to store the CSRF token seed in the session.
*/
const CSRF_TOKEN_SEED = 's';
/**
* Constructs a new metadata bag instance.
*
* @param \Drupal\Core\Site\Settings $settings
* The settings instance.
*/
public function __construct(Settings $settings) {
$update_threshold = $settings->get('session_write_interval', 180);
parent::__construct('_sf2_meta', $update_threshold);
}
/**
* Set the CSRF token seed.
*
* @param string $csrf_token_seed
* The per-session CSRF token seed.
*/
public function setCsrfTokenSeed($csrf_token_seed) {
$this->meta[static::CSRF_TOKEN_SEED] = $csrf_token_seed;
}
/**
* Get the CSRF token seed.
*
* @return string|null
* The per-session CSRF token seed or null when no value is set.
*/
public function getCsrfTokenSeed() {
if (isset($this->meta[static::CSRF_TOKEN_SEED])) {
return $this->meta[static::CSRF_TOKEN_SEED];
}
}
/**
* Clear the CSRF token seed.
*/
public function clearCsrfTokenSeed() {
unset($this->meta[static::CSRF_TOKEN_SEED]);
}
}

View file

@ -0,0 +1,114 @@
<?php
/**
* @file
* Contains \Drupal\Core\Session\PermissionsHashGenerator.
*/
namespace Drupal\Core\Session;
use Drupal\Core\PrivateKey;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Site\Settings;
use Drupal\user\Entity\Role;
/**
* Generates and caches the permissions hash for a user.
*/
class PermissionsHashGenerator implements PermissionsHashGeneratorInterface {
/**
* The private key service.
*
* @var \Drupal\Core\PrivateKey
*/
protected $privateKey;
/**
* The cache backend interface to use for the permission hash cache.
*
* @var \Drupal\Core\Cache\CacheBackendInterface
*/
protected $cache;
/**
* Constructs a PermissionsHashGenerator object.
*
* @param \Drupal\Core\PrivateKey $private_key
* The private key service.
* @param \Drupal\Core\Cache\CacheBackendInterface $cache
* The cache backend interface to use for the permission hash cache.
*/
public function __construct(PrivateKey $private_key, CacheBackendInterface $cache) {
$this->privateKey = $private_key;
$this->cache = $cache;
}
/**
* {@inheritdoc}
*
* Cached by role, invalidated whenever permissions change.
*/
public function generate(AccountInterface $account) {
// User 1 is the super user, and can always access all permissions. Use a
// different, unique identifier for the hash.
if ($account->id() == 1) {
return $this->hash('is-super-user');
}
$sorted_roles = $account->getRoles();
sort($sorted_roles);
$role_list = implode(',', $sorted_roles);
if ($cache = $this->cache->get("user_permissions_hash:$role_list")) {
$permissions_hash = $cache->data;
}
else {
$permissions_hash = $this->doGenerate($sorted_roles);
$tags = Cache::buildTags('config:user.role', $sorted_roles, '.');
$this->cache->set("user_permissions_hash:$role_list", $permissions_hash, Cache::PERMANENT, $tags);
}
return $permissions_hash;
}
/**
* Generates a hash that uniquely identifies the user's permissions.
*
* @param string[] $roles
* The user's roles.
*
* @return string
* The permissions hash.
*/
protected function doGenerate(array $roles) {
// @todo Once Drupal gets rid of user_role_permissions(), we should be able
// to inject the user role controller and call a method on that instead.
$permissions_by_role = user_role_permissions($roles);
foreach ($permissions_by_role as $role => $permissions) {
sort($permissions);
// Note that for admin roles (\Drupal\user\RoleInterface::isAdmin()), the
// permissions returned will be empty ($permissions = []). Therefore the
// presence of the role ID as a key in $permissions_by_role is essential
// to ensure that the hash correctly recognizes admin roles. (If the hash
// was based solely on the union of $permissions, the admin roles would
// effectively be no-ops, allowing for hash collisions.)
$permissions_by_role[$role] = $permissions;
}
return $this->hash(serialize($permissions_by_role));
}
/**
* Hashes the given string.
*
* @param string $identifier
* The string to be hashed.
*
* @return string
* The hash.
*/
protected function hash($identifier) {
return hash('sha256', $this->privateKey->get() . Settings::getHashSalt() . $identifier);
}
}

View file

@ -0,0 +1,26 @@
<?php
/**
* @file
* Contains \Drupal\Core\Session\PermissionsHashGeneratorInterface.
*/
namespace Drupal\Core\Session;
/**
* Defines the user permissions hash generator interface.
*/
interface PermissionsHashGeneratorInterface {
/**
* Generates a hash that uniquely identifies a user's permissions.
*
* @param \Drupal\Core\Session\AccountInterface $account
* The user account for which to get the permissions hash.
*
* @return string
* A permissions hash.
*/
public function generate(AccountInterface $account);
}

View file

@ -0,0 +1,164 @@
<?php
/**
* @file
* Contains \Drupal\Core\Session\SessionConfiguration.
*/
namespace Drupal\Core\Session;
use Symfony\Component\HttpFoundation\Request;
/**
* Defines the default session configuration generator.
*/
class SessionConfiguration implements SessionConfigurationInterface {
/**
* An associative array of session ini settings.
*/
protected $options;
/**
* Constructs a new session configuration instance.
*
* @param array $options
* An associative array of session ini settings.
*
* @see \Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage::__construct()
* @see http://php.net/manual/session.configuration.php
*/
public function __construct($options = []) {
$this->options = $options;
}
/**
* {@inheritdoc}
*/
public function hasSession(Request $request) {
return $request->cookies->has($this->getName($request));
}
/**
* {@inheritdoc}
*/
public function getOptions(Request $request) {
$options = $this->options;
// Generate / validate the cookie domain.
$options['cookie_domain'] = $this->getCookieDomain($request) ?: '';
// If the site is accessed via SSL, ensure that the session cookie is
// issued with the secure flag.
$options['cookie_secure'] = $request->isSecure();
// Set the session cookie name.
$options['name'] = $this->getName($request);
return $options;
}
/**
* Returns the session cookie name.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The request.
*
* @return string
* The name of the session cookie.
*/
protected function getName(Request $request) {
// To prevent session cookies from being hijacked, a user can configure the
// SSL version of their website to only transfer session cookies via SSL by
// using PHP's session.cookie_secure setting. The browser will then use two
// separate session cookies for the HTTPS and HTTP versions of the site. So
// we must use different session identifiers for HTTPS and HTTP to prevent a
// cookie collision.
$prefix = $request->isSecure() ? 'SSESS' : 'SESS';
return $prefix . $this->getUnprefixedName($request);
}
/**
* Returns the session cookie name without the secure/insecure prefix.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The request.
*
* @returns string
* The session name without the prefix (SESS/SSESS).
*/
protected function getUnprefixedName(Request $request) {
if ($test_prefix = $this->drupalValidTestUa()) {
$session_name = $test_prefix;
}
elseif (isset($this->options['cookie_domain'])) {
// If the user specifies the cookie domain, also use it for session name.
$session_name = $this->options['cookie_domain'];
}
else {
// Otherwise use $base_url as session name, without the protocol
// to use the same session identifiers across HTTP and HTTPS.
$session_name = $request->getHost() . $request->getBasePath();
// Replace "core" out of session_name so core scripts redirect properly,
// specifically install.php.
$session_name = preg_replace('#/core$#', '', $session_name);
}
return substr(hash('sha256', $session_name), 0, 32);
}
/**
* Return the session cookie domain.
*
* The Set-Cookie response header and its domain attribute are defined in RFC
* 2109, RFC 2965 and RFC 6265 each one superseeding the previous version.
*
* @see http://tools.ietf.org/html/rfc2109
* @see http://tools.ietf.org/html/rfc2965
* @see http://tools.ietf.org/html/rfc6265
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The request.
*
* @returns string
* The session cookie domain.
*/
protected function getCookieDomain(Request $request) {
if (isset($this->options['cookie_domain'])) {
$cookie_domain = $this->options['cookie_domain'];
}
else {
$host = $request->getHost();
// Strip www. from hostname.
if (strpos($host, 'www.') === 0) {
$host = substr($host, 4);
}
// To maximize compatibility and normalize the behavior across user
// agents, the cookie domain should start with a dot.
$cookie_domain = '.' . $host;
}
// Cookies for domains without an embedded dot will be rejected by user
// agents in order to defeat malicious websites attempting to set cookies
// for top-level domains. Also IP addresses may not be used in the domain
// attribute of a Set-Cookie header.
if (count(explode('.', $cookie_domain)) > 2 && !is_numeric(str_replace('.', '', $cookie_domain))) {
return $cookie_domain;
}
}
/**
* Wraps drupal_valid_test_ua().
*
* @return string|FALSE
* Either the simpletest prefix (the string "simpletest" followed by any
* number of digits) or FALSE if the user agent does not contain a valid
* HMAC and timestamp.
*/
protected function drupalValidTestUa() {
return drupal_valid_test_ua();
}
}

View file

@ -0,0 +1,46 @@
<?php
/**
* @file
* Contains \Drupal\Core\Session\SessionConfigurationInterface.
*/
namespace Drupal\Core\Session;
use Symfony\Component\HttpFoundation\Request;
/**
* Defines an interface for session configuration generators.
*/
interface SessionConfigurationInterface {
/**
* Determines whether a session identifier is on the request.
*
* This method detects whether a session was started during one of the
* previous requests from the same user agent. Session identifiers are
* normally passed along using cookies and hence a typical implementation
* checks whether the session cookie is on the request.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The request.
*
* @return bool
* TRUE if there is a session identifier on the request.
*/
public function hasSession(Request $request);
/**
* Returns a list of options suitable for passing to the session storage.
*
* @see \Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage::__construct()
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The request.
*
* @return array
* An associative array of session ini settings.
*/
public function getOptions(Request $request);
}

View file

@ -0,0 +1,138 @@
<?php
/**
* @file
* Contains \Drupal\Core\Session\SessionHandler.
*/
namespace Drupal\Core\Session;
use Drupal\Component\Utility\Crypt;
use Drupal\Core\Database\Connection;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
use Drupal\Core\Utility\Error;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy;
/**
* Default session handler.
*/
class SessionHandler extends AbstractProxy implements \SessionHandlerInterface {
use DependencySerializationTrait;
/**
* The request stack.
*
* @var \Symfony\Component\HttpFoundation\RequestStack
*/
protected $requestStack;
/**
* The database connection.
*
* @var \Drupal\Core\Database\Connection
*/
protected $connection;
/**
* Constructs a new SessionHandler instance.
*
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
* The request stack.
* @param \Drupal\Core\Database\Connection $connection
* The database connection.
*/
public function __construct(RequestStack $request_stack, Connection $connection) {
$this->requestStack = $request_stack;
$this->connection = $connection;
}
/**
* {@inheritdoc}
*/
public function open($save_path, $name) {
return TRUE;
}
/**
* {@inheritdoc}
*/
public function read($sid) {
$data = '';
if (!empty($sid)) {
// Read the session data from the database.
$query = $this->connection
->queryRange('SELECT session FROM {sessions} WHERE sid = :sid', 0, 1, ['sid' => Crypt::hashBase64($sid)]);
$data = (string) $query->fetchField();
}
return $data;
}
/**
* {@inheritdoc}
*/
public function write($sid, $value) {
// The exception handler is not active at this point, so we need to do it
// manually.
try {
$request = $this->requestStack->getCurrentRequest();
$fields = array(
'uid' => $request->getSession()->get('uid', 0),
'hostname' => $request->getClientIP(),
'session' => $value,
'timestamp' => REQUEST_TIME,
);
$this->connection->merge('sessions')
->keys(array('sid' => Crypt::hashBase64($sid)))
->fields($fields)
->execute();
return TRUE;
}
catch (\Exception $exception) {
require_once DRUPAL_ROOT . '/core/includes/errors.inc';
// If we are displaying errors, then do so with no possibility of a
// further uncaught exception being thrown.
if (error_displayable()) {
print '<h1>Uncaught exception thrown in session handler.</h1>';
print '<p>' . Error::renderExceptionSafe($exception) . '</p><hr />';
}
return FALSE;
}
}
/**
* {@inheritdoc}
*/
public function close() {
return TRUE;
}
/**
* {@inheritdoc}
*/
public function destroy($sid) {
// Delete session data.
$this->connection->delete('sessions')
->condition('sid', Crypt::hashBase64($sid))
->execute();
return TRUE;
}
/**
* {@inheritdoc}
*/
public function gc($lifetime) {
// Be sure to adjust 'php_value session.gc_maxlifetime' to a large enough
// value. For example, if you want user sessions to stay in your database
// for three weeks before deleting them, you need to set gc_maxlifetime
// to '1814400'. At that value, only after a user doesn't log in after
// three weeks (1814400 seconds) will his/her session be removed.
$this->connection->delete('sessions')
->condition('timestamp', REQUEST_TIME - $lifetime, '<')
->execute();
return TRUE;
}
}

View file

@ -0,0 +1,345 @@
<?php
/**
* @file
* Contains \Drupal\Core\Session\SessionManager.
*/
namespace Drupal\Core\Session;
use Drupal\Component\Utility\Crypt;
use Drupal\Core\Database\Connection;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
/**
* Manages user sessions.
*
* This class implements the custom session management code inherited from
* Drupal 7 on top of the corresponding Symfony component. Regrettably the name
* NativeSessionStorage is not quite accurate. In fact the responsibility for
* storing and retrieving session data has been extracted from it in Symfony 2.1
* but the class name was not changed.
*
* @todo
* In fact the NativeSessionStorage class already implements all of the
* functionality required by a typical Symfony application. Normally it is not
* necessary to subclass it at all. In order to reach the point where Drupal
* can use the Symfony session management unmodified, the code implemented
* here needs to be extracted either into a dedicated session handler proxy
* (e.g. sid-hashing) or relocated to the authentication subsystem.
*/
class SessionManager extends NativeSessionStorage implements SessionManagerInterface {
use DependencySerializationTrait;
/**
* The request stack.
*
* @var \Symfony\Component\HttpFoundation\RequestStack
*/
protected $requestStack;
/**
* The database connection to use.
*
* @var \Drupal\Core\Database\Connection
*/
protected $connection;
/**
* The session configuration.
*
* @var \Drupal\Core\Session\SessionConfigurationInterface
*/
protected $sessionConfiguration;
/**
* Whether a lazy session has been started.
*
* @var bool
*/
protected $startedLazy;
/**
* The write safe session handler.
*
* @todo: This reference should be removed once all database queries
* are removed from the session manager class.
*
* @var \Drupal\Core\Session\WriteSafeSessionHandlerInterface
*/
protected $writeSafeHandler;
/**
* Constructs a new session manager instance.
*
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
* The request stack.
* @param \Drupal\Core\Database\Connection $connection
* The database connection.
* @param \Drupal\Core\Session\MetadataBag $metadata_bag
* The session metadata bag.
* @param \Drupal\Core\Session\SessionConfigurationInterface $session_configuration
* The session configuration interface.
* @param \Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy|Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeSessionHandler|\SessionHandlerInterface|NULL $handler
* The object to register as a PHP session handler.
* @see \Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage::setSaveHandler()
*/
public function __construct(RequestStack $request_stack, Connection $connection, MetadataBag $metadata_bag, SessionConfigurationInterface $session_configuration, $handler = NULL) {
$options = array();
$this->sessionConfiguration = $session_configuration;
$this->requestStack = $request_stack;
$this->connection = $connection;
parent::__construct($options, $handler, $metadata_bag);
// @todo When not using the Symfony Session object, the list of bags in the
// NativeSessionStorage will remain uninitialized. This will lead to
// errors in NativeSessionHandler::loadSession. Remove this after
// https://www.drupal.org/node/2229145, when we will be using the Symfony
// session object (which registers an attribute bag with the
// manager upon instantiation).
$this->bags = array();
}
/**
* {@inheritdoc}
*/
public function start() {
if (($this->started || $this->startedLazy) && !$this->closed) {
return $this->started;
}
$request = $this->requestStack->getCurrentRequest();
$this->setOptions($this->sessionConfiguration->getOptions($request));
if ($this->sessionConfiguration->hasSession($request)) {
// If a session cookie exists, initialize the session. Otherwise the
// session is only started on demand in save(), making
// anonymous users not use a session cookie unless something is stored in
// $_SESSION. This allows HTTP proxies to cache anonymous pageviews.
$result = $this->startNow();
}
if (empty($result)) {
// Randomly generate a session identifier for this request. This is
// necessary because \Drupal\user\SharedTempStoreFactory::get() wants to
// know the future session ID of a lazily started session in advance.
//
// @todo: With current versions of PHP there is little reason to generate
// the session id from within application code. Consider using the
// default php session id instead of generating a custom one:
// https://www.drupal.org/node/2238561
$this->setId(Crypt::randomBytesBase64());
// Initialize the session global and attach the Symfony session bags.
$_SESSION = array();
$this->loadSession();
// NativeSessionStorage::loadSession() sets started to TRUE, reset it to
// FALSE here.
$this->started = FALSE;
$this->startedLazy = TRUE;
$result = FALSE;
}
return $result;
}
/**
* Forcibly start a PHP session.
*
* @return boolean
* TRUE if the session is started.
*/
protected function startNow() {
if ($this->isCli()) {
return FALSE;
}
if ($this->startedLazy) {
// Save current session data before starting it, as PHP will destroy it.
$session_data = $_SESSION;
}
$result = parent::start();
// Restore session data.
if ($this->startedLazy) {
$_SESSION = $session_data;
$this->loadSession();
}
return $result;
}
/**
* {@inheritdoc}
*/
public function save() {
if ($this->isCli()) {
// We don't have anything to do if we are not allowed to save the session.
return;
}
if ($this->isSessionObsolete()) {
// There is no session data to store, destroy the session if it was
// previously started.
if ($this->getSaveHandler()->isActive()) {
$this->destroy();
}
}
else {
// There is session data to store. Start the session if it is not already
// started.
if (!$this->getSaveHandler()->isActive()) {
$this->startNow();
}
// Write the session data.
parent::save();
}
$this->startedLazy = FALSE;
}
/**
* {@inheritdoc}
*/
public function regenerate($destroy = FALSE, $lifetime = NULL) {
// Nothing to do if we are not allowed to change the session.
if ($this->isCli()) {
return;
}
// We do not support the optional $destroy and $lifetime parameters as long
// as #2238561 remains open.
if ($destroy || isset($lifetime)) {
throw new \InvalidArgumentException('The optional parameters $destroy and $lifetime of SessionManager::regenerate() are not supported currently');
}
if ($this->isStarted()) {
$old_session_id = $this->getId();
}
session_id(Crypt::randomBytesBase64());
$this->getMetadataBag()->clearCsrfTokenSeed();
if (isset($old_session_id)) {
$params = session_get_cookie_params();
$expire = $params['lifetime'] ? REQUEST_TIME + $params['lifetime'] : 0;
setcookie($this->getName(), $this->getId(), $expire, $params['path'], $params['domain'], $params['secure'], $params['httponly']);
$this->migrateStoredSession($old_session_id);
}
if (!$this->isStarted()) {
// Start the session when it doesn't exist yet.
$this->startNow();
}
}
/**
* {@inheritdoc}
*/
public function delete($uid) {
// Nothing to do if we are not allowed to change the session.
if (!$this->writeSafeHandler->isSessionWritable() || $this->isCli()) {
return;
}
$this->connection->delete('sessions')
->condition('uid', $uid)
->execute();
}
/**
* {@inheritdoc}
*/
public function destroy() {
session_destroy();
// Unset the session cookies.
$session_name = $this->getName();
$cookies = $this->requestStack->getCurrentRequest()->cookies;
if ($cookies->has($session_name)) {
$params = session_get_cookie_params();
setcookie($session_name, '', REQUEST_TIME - 3600, $params['path'], $params['domain'], $params['secure'], $params['httponly']);
$cookies->remove($session_name);
}
}
/**
* {@inheritdoc}
*/
public function setWriteSafeHandler(WriteSafeSessionHandlerInterface $handler) {
$this->writeSafeHandler = $handler;
}
/**
* Returns whether the current PHP process runs on CLI.
*
* Command line clients do not support cookies nor sessions.
*
* @return bool
*/
protected function isCli() {
return PHP_SAPI === 'cli';
}
/**
* Determines whether the session contains user data.
*
* @return bool
* TRUE when the session does not contain any values and therefore can be
* destroyed.
*/
protected function isSessionObsolete() {
$used_session_keys = array_filter($this->getSessionDataMask());
return empty($used_session_keys);
}
/**
* Returns a map specifying which session key is containing user data.
*
* @return array
* An array where keys correspond to the session keys and the values are
* booleans specifying whether the corresponding session key contains any
* user data.
*/
protected function getSessionDataMask() {
if (empty($_SESSION)) {
return array();
}
// Start out with a completely filled mask.
$mask = array_fill_keys(array_keys($_SESSION), TRUE);
// Ignore the metadata bag, it does not contain any user data.
$mask[$this->metadataBag->getStorageKey()] = FALSE;
// Ignore attribute bags when they do not contain any data.
foreach ($this->bags as $bag) {
$key = $bag->getStorageKey();
$mask[$key] = !empty($_SESSION[$key]);
}
return array_intersect_key($mask, $_SESSION);
}
/**
* Migrates the current session to a new session id.
*
* @param string $old_session_id
* The old session id. The new session id is $this->getId() unless
* $new_insecure_session_id is not empty.
*/
protected function migrateStoredSession($old_session_id) {
$fields = array('sid' => Crypt::hashBase64($this->getId()));
$this->connection->update('sessions')
->fields($fields)
->condition('sid', Crypt::hashBase64($old_session_id))
->execute();
}
}

View file

@ -0,0 +1,40 @@
<?php
/**
* @file
* Contains \Drupal\Core\Session\SessionManagerInterface.
*/
namespace Drupal\Core\Session;
use Symfony\Component\HttpFoundation\Session\Storage\SessionStorageInterface;
/**
* Defines the session manager interface.
*/
interface SessionManagerInterface extends SessionStorageInterface {
/**
* Ends a specific user's session(s).
*
* @param int $uid
* User ID.
*/
public function delete($uid);
/**
* Destroys the current session and removes session cookies.
*/
public function destroy();
/**
* Sets the write safe session handler.
*
* @todo: This should be removed once all database queries are removed from
* the session manager class.
*
* @var \Drupal\Core\Session\WriteSafeSessionHandlerInterface
*/
public function setWriteSafeHandler(WriteSafeSessionHandlerInterface $handler);
}

View file

@ -0,0 +1,199 @@
<?php
/**
* @file
* Contains \Drupal\Core\Session\UserSession.
*/
namespace Drupal\Core\Session;
/**
* An implementation of the user account interface for the global user.
*
* @todo: Change all properties to protected.
*/
class UserSession implements AccountInterface {
/**
* User ID.
*
* @var int
*/
protected $uid = 0;
/**
* List of the roles this user has.
*
* Defaults to the anonymous role.
*
* @var array
*/
protected $roles = array(AccountInterface::ANONYMOUS_ROLE);
/**
* The Unix timestamp when the user last accessed the site.
*
* @var string.
*/
protected $access;
/**
* The name of this account.
*
* @var string
*/
public $name;
/**
* The preferred language code of the account.
*
* @var string
*/
protected $preferred_langcode;
/**
* The preferred administrative language code of the account.
*
* @var string
*/
protected $preferred_admin_langcode;
/**
* The email address of this account.
*
* @var string
*/
protected $mail;
/**
* The timezone of this account.
*
* @var string
*/
protected $timezone;
/**
* Constructs a new user session.
*
* @param array $values
* Array of initial values for the user session.
*/
public function __construct(array $values = array()) {
foreach ($values as $key => $value) {
$this->$key = $value;
}
}
/**
* {@inheritdoc}
*/
public function id() {
return $this->uid;
}
/**
* {@inheritdoc}
*/
public function getRoles($exclude_locked_roles = FALSE) {
$roles = $this->roles;
if ($exclude_locked_roles) {
$roles = array_values(array_diff($roles, array(AccountInterface::ANONYMOUS_ROLE, AccountInterface::AUTHENTICATED_ROLE)));
}
return $roles;
}
/**
* {@inheritdoc}
*/
public function hasPermission($permission) {
// User #1 has all privileges.
if ((int) $this->id() === 1) {
return TRUE;
}
return $this->getRoleStorage()->isPermissionInRoles($permission, $this->getRoles());
}
/**
* {@inheritdoc}
*/
public function isAuthenticated() {
return $this->uid > 0;
}
/**
* {@inheritdoc}
*/
public function isAnonymous() {
return $this->uid == 0;
}
/**
* {@inheritdoc}
*/
function getPreferredLangcode($fallback_to_default = TRUE) {
$language_list = \Drupal::languageManager()->getLanguages();
if (!empty($this->preferred_langcode) && isset($language_list[$this->preferred_langcode])) {
return $language_list[$this->preferred_langcode]->getId();
}
else {
return $fallback_to_default ? \Drupal::languageManager()->getDefaultLanguage()->getId() : '';
}
}
/**
* {@inheritdoc}
*/
function getPreferredAdminLangcode($fallback_to_default = TRUE) {
$language_list = \Drupal::languageManager()->getLanguages();
if (!empty($this->preferred_admin_langcode) && isset($language_list[$this->preferred_admin_langcode])) {
return $language_list[$this->preferred_admin_langcode]->getId();
}
else {
return $fallback_to_default ? \Drupal::languageManager()->getDefaultLanguage()->getId() : '';
}
}
/**
* {@inheritdoc}
*/
public function getUsername() {
$name = $this->name ?: \Drupal::config('user.settings')->get('anonymous');
\Drupal::moduleHandler()->alter('user_format_name', $name, $this);
return $name;
}
/**
* {@inheritdoc}
*/
public function getEmail() {
return $this->mail;
}
/**
* {@inheritdoc}
*/
public function getTimeZone() {
return $this->timezone;
}
/**
* {@inheritdoc}
*/
public function getLastAccessedTime() {
return $this->access;
}
/**
* Returns the role storage object.
*
* @return \Drupal\user\RoleStorageInterface
* The role storage object.
*/
protected function getRoleStorage() {
return \Drupal::entityManager()->getStorage('user_role');
}
}

View file

@ -0,0 +1,101 @@
<?php
/**
* @file
* Contains \Drupal\Core\Session\WriteSafeSessionHandler.
*/
namespace Drupal\Core\Session;
/**
* Wraps another SessionHandlerInterface to prevent writes when not allowed.
*/
class WriteSafeSessionHandler implements \SessionHandlerInterface, WriteSafeSessionHandlerInterface {
/**
* @var \SessionHandlerInterface
*/
protected $wrappedSessionHandler;
/**
* Whether or not the session is enabled for writing.
*
* @var bool
*/
protected $sessionWritable;
/**
* Constructs a new write safe session handler.
*
* @param \SessionHandlerInterface $wrapped_session_handler
* The underlying session handler.
* @param bool $session_writable
* Whether or not the session should be initially writable.
*/
public function __construct(\SessionHandlerInterface $wrapped_session_handler, $session_writable = TRUE) {
$this->wrappedSessionHandler = $wrapped_session_handler;
$this->sessionWritable = $session_writable;
}
/**
* {@inheritdoc}
*/
public function close() {
return $this->wrappedSessionHandler->close();
}
/**
* {@inheritdoc}
*/
public function destroy($session_id) {
return $this->wrappedSessionHandler->destroy($session_id);
}
/**
* {@inheritdoc}
*/
public function gc($max_lifetime) {
return $this->wrappedSessionHandler->gc($max_lifetime);
}
/**
* {@inheritdoc}
*/
public function open($save_path, $session_id) {
return $this->wrappedSessionHandler->open($save_path, $session_id);
}
/**
* {@inheritdoc}
*/
public function read($session_id) {
return $this->wrappedSessionHandler->read($session_id);
}
/**
* {@inheritdoc}
*/
public function write($session_id, $session_data) {
if ($this->isSessionWritable()) {
return $this->wrappedSessionHandler->write($session_id, $session_data);
}
else {
return TRUE;
}
}
/**
* {@inheritdoc}
*/
public function setSessionWritable($flag) {
$this->sessionWritable = (bool) $flag;
}
/**
* {@inheritdoc}
*/
public function isSessionWritable() {
return $this->sessionWritable;
}
}

View file

@ -0,0 +1,36 @@
<?php
/**
* @file
* Contains \Drupal\Core\Session\WriteSafeSessionHandlerInterface.
*/
namespace Drupal\Core\Session;
/**
* Provides an interface for session handlers where writing can be disabled.
*/
interface WriteSafeSessionHandlerInterface {
/**
* Sets whether or not a session may be written to storage.
*
* It is not possible to enforce writing of the session data. This method is
* only capable of forcibly disabling that session data is written to storage.
*
* @param bool $flag
* TRUE if the session the session is allowed to be written, FALSE
* otherwise.
*/
public function setSessionWritable($flag);
/**
* Returns whether or not a session may be written to storage.
*
* @return bool
* TRUE if the session the session is allowed to be written, FALSE
* otherwise.
*/
public function isSessionWritable();
}