View source
<?php
namespace Drupal\bakery;
use Drupal\bakery\Cookies\ChocolateChip;
use Drupal\bakery\Cookies\CookieInterface;
use Drupal\bakery\Cookies\RemoteCookieInterface;
use Drupal\bakery\Exception\MissingKeyException;
use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Logger\LoggerChannelTrait;
use Drupal\Core\Messenger\MessengerTrait;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Session\AccountProxyInterface;
use GuzzleHttp\Exception\BadResponseException;
use GuzzleHttp\Exception\RequestException;
use Symfony\Component\HttpFoundation\ParameterBag;
class Kitchen {
use MessengerTrait;
use LoggerChannelTrait;
const CHOCOLATE_CHIP = 'CHOCOLATECHIP';
const OATMEAL = 'OATMEAL';
protected $time;
protected $config;
protected $currentUser;
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;
}
public function bake(CookieInterface $cookie) {
$this->cookieJar
->set($cookie::getName(), $this
->bakeData($cookie));
}
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);
}
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) {
}
}
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';
}
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;
}
if (!$this->config
->get('bakery_domain')) {
return FALSE;
}
return $this
->tasteData($cookies
->get($cookie_name), $cookie_name);
}
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));
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;
}
public function eat(string $type) : void {
$this->cookieJar
->set($this
->cookieName($type), '');
}
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;
}
$master = $this->config
->get('bakery_master');
return $this
->shipItGood($master . $path, $cookie::getName(), $data);
}
protected function shipItGood(string $uri, string $cookie_name, string $data) {
$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;
}
}
public function cookieName(string $type) : string {
if (ini_get('session.cookie_secure')) {
$type .= 'SSL';
}
$extension = $this->config
->get('bakery_cookie_extension') ?: '';
return $type . $extension;
}
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;
}
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;
}
}