Drupal 8.0.0 beta 12. More info: https://www.drupal.org/node/2514176
This commit is contained in:
commit
9921556621
13277 changed files with 1459781 additions and 0 deletions
152
core/lib/Drupal/Core/Session/AccountInterface.php
Normal file
152
core/lib/Drupal/Core/Session/AccountInterface.php
Normal 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();
|
||||
|
||||
}
|
180
core/lib/Drupal/Core/Session/AccountProxy.php
Normal file
180
core/lib/Drupal/Core/Session/AccountProxy.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
51
core/lib/Drupal/Core/Session/AccountProxyInterface.php
Normal file
51
core/lib/Drupal/Core/Session/AccountProxyInterface.php
Normal 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);
|
||||
|
||||
}
|
96
core/lib/Drupal/Core/Session/AccountSwitcher.php
Normal file
96
core/lib/Drupal/Core/Session/AccountSwitcher.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
43
core/lib/Drupal/Core/Session/AccountSwitcherInterface.php
Normal file
43
core/lib/Drupal/Core/Session/AccountSwitcherInterface.php
Normal 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();
|
||||
|
||||
}
|
23
core/lib/Drupal/Core/Session/AnonymousUserSession.php
Normal file
23
core/lib/Drupal/Core/Session/AnonymousUserSession.php
Normal 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() {
|
||||
}
|
||||
|
||||
}
|
63
core/lib/Drupal/Core/Session/MetadataBag.php
Normal file
63
core/lib/Drupal/Core/Session/MetadataBag.php
Normal 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]);
|
||||
}
|
||||
|
||||
}
|
114
core/lib/Drupal/Core/Session/PermissionsHashGenerator.php
Normal file
114
core/lib/Drupal/Core/Session/PermissionsHashGenerator.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
164
core/lib/Drupal/Core/Session/SessionConfiguration.php
Normal file
164
core/lib/Drupal/Core/Session/SessionConfiguration.php
Normal 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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
138
core/lib/Drupal/Core/Session/SessionHandler.php
Normal file
138
core/lib/Drupal/Core/Session/SessionHandler.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
345
core/lib/Drupal/Core/Session/SessionManager.php
Normal file
345
core/lib/Drupal/Core/Session/SessionManager.php
Normal 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();
|
||||
}
|
||||
|
||||
}
|
40
core/lib/Drupal/Core/Session/SessionManagerInterface.php
Normal file
40
core/lib/Drupal/Core/Session/SessionManagerInterface.php
Normal 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);
|
||||
|
||||
}
|
199
core/lib/Drupal/Core/Session/UserSession.php
Normal file
199
core/lib/Drupal/Core/Session/UserSession.php
Normal 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');
|
||||
}
|
||||
|
||||
}
|
101
core/lib/Drupal/Core/Session/WriteSafeSessionHandler.php
Normal file
101
core/lib/Drupal/Core/Session/WriteSafeSessionHandler.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
|
||||
}
|
Reference in a new issue