class Kitchen in Bakery Single Sign-On System 8.2
Hierarchy
- class \Drupal\bakery\Kitchen uses LoggerChannelTrait, MessengerTrait
Expanded class hierarchy of Kitchen
10 files declare their use of Kitchen
- BakeryUncrumbleForm.php in src/
Forms/ BakeryUncrumbleForm.php - BootSubscriber.php in src/
EventSubscriber/ BootSubscriber.php - For Boot event subscribe.
- BrowserCookieTrait.php in src/
Cookies/ BrowserCookieTrait.php - BrowserCookieTraitTest.php in tests/
src/ Unit/ Cookies/ BrowserCookieTraitTest.php - ChildController.php in src/
Controller/ ChildController.php
1 string reference to 'Kitchen'
1 service uses Kitchen
File
- src/
Kitchen.php, line 19
Namespace
Drupal\bakeryView source
class Kitchen {
use MessengerTrait;
use LoggerChannelTrait;
/**
* Main browser cookie used for authentication.
*/
const CHOCOLATE_CHIP = 'CHOCOLATECHIP';
/**
* Secondary browser cookie used for account information.
*/
const OATMEAL = 'OATMEAL';
/**
* @var \Drupal\Component\Datetime\TimeInterface
*/
protected $time;
/**
* @var \Drupal\Core\Config\ImmutableConfig
*/
protected $config;
/**
* @var \Drupal\Core\Session\AccountProxyInterface
*/
protected $currentUser;
/**
* @var \Symfony\Component\HttpFoundation\ParameterBag
*/
protected $cookieJar;
public function __construct(TimeInterface $time, ConfigFactoryInterface $config_factory, AccountProxyInterface $current_user, ParameterBag $cookie_jar) {
$this->time = $time;
$this->config = $config_factory
->get('bakery.settings');
$this->currentUser = $current_user;
$this->cookieJar = $cookie_jar;
}
/**
* Set a cookie.
*
* @param \Drupal\bakery\Cookies\CookieInterface $cookie
* The cookie data.
*
* @throws \Drupal\bakery\Exception\MissingKeyException
* Thrown if the site key isn't configured yet.
*/
public function bake(CookieInterface $cookie) {
$this->cookieJar
->set($cookie::getName(), $this
->bakeData($cookie));
}
/**
* Encrypt and sign data for Bakery transfer.
*
* @param \Drupal\bakery\Cookies\CookieInterface $cookie
* The cookie data.
*
* @return string
* String of signed and encrypted data, url safe.
*/
public function bakeData(CookieInterface $cookie) {
$key = $this->config
->get('bakery_key');
if (empty($key)) {
throw new MissingKeyException();
}
$data = $cookie
->toData();
$data['type'] = $cookie::getName();
$data['timestamp'] = $this->time
->getRequestTime();
$data = $this
->encrypt(serialize($data));
$signature = hash_hmac('sha256', $data, $key);
return base64_encode($signature . $data);
}
/**
* Re-bake a chocolate chip cookie.
*
* @param \Drupal\Core\Session\AccountInterface $account
*/
public function reBakeChocolateChipCookie(AccountInterface $account) {
try {
$this
->bake(new ChocolateChip($account
->getAccountName(), $account
->getEmail(), $this
->generateInitField($account
->id()), $this->config
->get('bakery_is_master')));
} catch (MissingKeyException $exception) {
// Fail quietly. This could be happening during boot and allowing to
// bubble could make the site unreachable.
}
}
/**
* Build internal init url (without scheme).
*/
public function generateInitField($uid) {
$url = $this->config
->get('bakery_master');
$scheme = parse_url($url, PHP_URL_SCHEME);
return str_replace($scheme . '://', '', $url) . 'user/' . $uid . '/edit';
}
/**
* Check that the given cookie exists and doesn't taste funny.
*
* @param string $type
* Optional string defining the type of data this is.
* @param \Symfony\Component\HttpFoundation\ParameterBag|null $cookies
* Optional list of cookies from the request.
*
* @return array|bool
* Unserialized data or FALSE if invalid.
*/
public function taste(string $type, ParameterBag $cookies = NULL) {
$cookies = $cookies ?? \Drupal::request()->cookies;
$cookie_name = $this
->cookieName($type);
if (!$cookies
->has($cookie_name)) {
return FALSE;
}
// Shouldn't this be part of baking not tasting?
if (!$this->config
->get('bakery_domain')) {
return FALSE;
}
return $this
->tasteData($cookies
->get($cookie_name), $cookie_name);
}
/**
* Validate signature and decrypt data.
*
* @param string $data
* String of Bakery data, base64 encoded.
* @param string|null $type
* Optional string defining the type of data this is.
*
* @return array|bool
* Unserialized data or FALSE if invalid.
*
* @throws \Drupal\bakery\Exception\MissingKeyException
* Thrown if the site key isn't configured yet.
*/
public function tasteData(string $data, string $type = NULL) {
$key = $this->config
->get('bakery_key');
if (empty($key)) {
throw new MissingKeyException();
}
$data = base64_decode($data);
$signature = substr($data, 0, 64);
$encrypted_data = substr($data, 64);
if ($signature !== hash_hmac('sha256', $encrypted_data, $key)) {
return FALSE;
}
$decrypted_data = unserialize($this
->decrypt($encrypted_data));
// Prevent one cookie being used in place of another.
if ($type !== NULL && $decrypted_data['type'] !== $type) {
return FALSE;
}
if ($decrypted_data['timestamp'] + (int) $this->config
->get('bakery_freshness') >= $this->time
->getRequestTime()) {
return $decrypted_data;
}
return FALSE;
}
/**
* Nom Nom Nom Nom.
*
* Destroys browser cookies.
*
* @param string $type
* Cookie type
*/
public function eat(string $type) : void {
$this->cookieJar
->set($this
->cookieName($type), '');
}
/**
* Ship a cookie to child sites.
*
* @param \Drupal\bakery\Cookies\RemoteCookieInterface $cookie
* The cookie data.
*
* @return \Psr\Http\Message\ResponseInterface|bool
*/
public function ship(RemoteCookieInterface $cookie) {
$path = $cookie
->getPath();
$data = $this
->bakeData($cookie);
if ($this->config
->get('bakery_is_master')) {
$children = $this->config
->get('bakery_slaves') ?: [];
foreach ($children as $child) {
$result = $this
->shipItGood($child . $path, $cookie::getName(), $data);
if (!$result) {
if ($this->currentUser
->hasPermission('administer bakery')) {
$this
->messenger()
->addError(t('Error occurred talking to the site at %url', [
'%url' => $child,
]));
}
}
else {
if ($this->currentUser
->hasPermission('administer bakery')) {
$this
->messenger()
->addMessage((string) $result
->getBody());
}
}
}
return TRUE;
}
// From child site, send to master.
$master = $this->config
->get('bakery_master');
return $this
->shipItGood($master . $path, $cookie::getName(), $data);
}
/**
* Helper method to ship specific cookie to the site.
*
* @param string $uri
* The uri to recieve the cookie.
* @param string $cookie_name
* The name of the cookie.
* @param string $data
* The encrypted cookie data.
*
* @return false|\Psr\Http\Message\ResponseInterface
*/
protected function shipItGood(string $uri, string $cookie_name, string $data) {
// @phpstan-ignore-next-line
$client = \Drupal::httpClient();
try {
return $client
->post($uri, [
'form_params' => [
$cookie_name => $data,
],
]);
} catch (BadResponseException $exception) {
$this
->getLogger('bakery')
->error('Failed to fetch file due to HTTP error "%error"', [
'%error' => $exception
->getMessage(),
]);
return FALSE;
} catch (RequestException $exception) {
$response = $exception
->getResponse();
$this
->getLogger('bakery')
->error('Failed to fetch file due to error "%error"', [
'%error' => $exception
->getMessage(),
]);
return FALSE;
}
}
/**
* Name for cookie including session.cookie_secure and variable extension.
*
* @param string $type
* CHOCOLATECHIP or OATMEAL.
*
* @return string
* The cookie name for this environment.
*/
public function cookieName(string $type) : string {
// Use different names for HTTPS and HTTP to prevent a cookie collision.
if (ini_get('session.cookie_secure')) {
$type .= 'SSL';
}
// Allow installation to modify the cookie name.
$extension = $this->config
->get('bakery_cookie_extension') ?: '';
return $type . $extension;
}
/**
* Encryption handler.
*
* @param string $text
* The text to be encrypted.
*
* @return bool|string
* Encrypted text.
*/
public function encrypt(string $text) {
$td = phpseclib_mcrypt_module_open('rijndael-128', '', 'ecb', '');
$iv = phpseclib_mcrypt_create_iv(phpseclib_mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
$key = substr($this->config
->get('bakery_key'), 0, phpseclib_mcrypt_enc_get_key_size($td));
phpseclib_mcrypt_generic_init($td, $key, $iv);
$data = phpseclib_mcrypt_generic($td, $text);
phpseclib_mcrypt_generic_deinit($td);
phpseclib_mcrypt_module_close($td);
return $data;
}
/**
* Decryption handler.
*
* @param string $text
* The data to be decrypted.
*
* @return string
* Decrypted text.
*/
public function decrypt(string $text) : string {
$td = phpseclib_mcrypt_module_open('rijndael-128', '', 'ecb', '');
$iv = phpseclib_mcrypt_create_iv(phpseclib_mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
$key = substr($this->config
->get('bakery_key'), 0, phpseclib_mcrypt_enc_get_key_size($td));
phpseclib_mcrypt_generic_init($td, $key, $iv);
$data = phpseclib_mdecrypt_generic($td, $text);
phpseclib_mcrypt_generic_deinit($td);
phpseclib_mcrypt_module_close($td);
return $data;
}
}
Members
Name![]() |
Modifiers | Type | Description | Overrides |
---|---|---|---|---|
Kitchen:: |
protected | property | ||
Kitchen:: |
protected | property | ||
Kitchen:: |
protected | property | ||
Kitchen:: |
protected | property | ||
Kitchen:: |
public | function | Set a cookie. | |
Kitchen:: |
public | function | Encrypt and sign data for Bakery transfer. | |
Kitchen:: |
constant | Main browser cookie used for authentication. | ||
Kitchen:: |
public | function | Name for cookie including session.cookie_secure and variable extension. | |
Kitchen:: |
public | function | Decryption handler. | |
Kitchen:: |
public | function | Nom Nom Nom Nom. | |
Kitchen:: |
public | function | Encryption handler. | |
Kitchen:: |
public | function | Build internal init url (without scheme). | |
Kitchen:: |
constant | Secondary browser cookie used for account information. | ||
Kitchen:: |
public | function | Re-bake a chocolate chip cookie. | |
Kitchen:: |
public | function | Ship a cookie to child sites. | |
Kitchen:: |
protected | function | Helper method to ship specific cookie to the site. | |
Kitchen:: |
public | function | Check that the given cookie exists and doesn't taste funny. | |
Kitchen:: |
public | function | Validate signature and decrypt data. | |
Kitchen:: |
public | function | ||
LoggerChannelTrait:: |
protected | property | The logger channel factory service. | |
LoggerChannelTrait:: |
protected | function | Gets the logger for a specific channel. | |
LoggerChannelTrait:: |
public | function | Injects the logger channel factory. | |
MessengerTrait:: |
protected | property | The messenger. | 29 |
MessengerTrait:: |
public | function | Gets the messenger. | 29 |
MessengerTrait:: |
public | function | Sets the messenger. |