Merge branch 'cookies' into test

This commit is contained in:
Fedor A. Fetisov 2021-08-28 01:13:07 +03:00
commit a37ea34670
12 changed files with 162 additions and 18 deletions

View File

@ -30,7 +30,7 @@
"require": {
"mibew/handlebars.php": "~0.10.5",
"mibew/handlebars.php-helpers": "1.*",
"symfony/http-foundation": "~2.8.52",
"symfony/http-foundation": "~3.2",
"symfony/routing": "2.6.*",
"symfony/config": "2.6.*",
"symfony/yaml": "^5.2",

View File

@ -102,6 +102,11 @@ chat_user_start:
defaults:
_controller: Mibew\Controller\Chat\UserChatController::startAction
chat_user_cookie_set_permission:
path: /chat/cookies-set-permission
defaults:
_controller: Mibew\Controller\Chat\UserChatController::cookieSetPermissionAction
# Pages that are available for all users
button:
path: /b

View File

@ -301,6 +301,12 @@ var Mibew = Mibew || {};
// Call parent constructor.
BasePopup.call(this, options);
/**
* Store options in case we need some of them later.
* @type {Object}
*/
this.options = options;
/**
* Wrapper for popup iframe DOM Element.
* @type {Node}
@ -331,6 +337,12 @@ var Mibew = Mibew || {};
*/
this.isMinified = false;
/**
* Indicates if cookies are blocked.
* @type {Boolean}
*/
this.cookiesBlocked = false;
// Load default styles. These styles hide the popup while real styles
// are loading.
this.attachDefaultStyles();
@ -344,6 +356,18 @@ var Mibew = Mibew || {};
// new page is visited.
this.safeOpen(openedChatUrl);
}
// Check if it's possible to set cookies at all
var rnd = Math.random();
Mibew.Utils.createCookie('mibewCheckToken', rnd);
var checkCookiesBlock = Mibew.Utils.loadScript(options.url.split('?')[0] + '/cookies-set-permission' + '?rnd=' + rnd);
checkCookiesBlock.popup = this;
checkCookiesBlock.onload = function() {
this.popup.cookiesBlocked = false;
};
checkCookiesBlock.onerror = function() {
this.popup.cookiesBlocked = true;
};
};
// Set correct prototype chain for IFrame popup.
@ -394,6 +418,14 @@ var Mibew = Mibew || {};
return;
}
if (this.cookiesBlocked) {
// Last resort. Replace this iframe-based popup with window-based popup
// and try to open a chat in a separate window.
Mibew.Objects.ChatPopups[this.id] = new Mibew.ChatPopup.Window(this.options);
Mibew.Objects.ChatPopups[this.id].open(url);
return;
}
if (!this.wrapperDiv) {
// Create new iframe and its wrapper.
// There is a bug in IE <= 7 that make "name" attribute unchangeble
@ -513,6 +545,10 @@ var Mibew = Mibew || {};
* value is omitted, the chat initialization URL will be loaded.
*/
Mibew.ChatPopup.Window.prototype.open = function(url) {
// Windows is already opened, nothing to do.
if (this.window != null && !this.window.closed) {
return;
}
this.window = window.open(
url || this.buildChatUrl(),
'mibewChat' + this.id,

View File

@ -78,6 +78,12 @@ var Mibew = Mibew || {};
*/
this.visitorCookieName = options.visitorCookieName;
/**
* Name of tracking cookie
* @type String
*/
this.cookiesBlocked = false;
/**
* URL of file with additional CSS rules for invitation
* @type String
@ -112,7 +118,14 @@ var Mibew = Mibew || {};
// Prepare GET params list
this.dataToSend.entry = escape(document.referrer),
this.dataToSend.locale = this.locale;
// Random value should be set both as a GET param and as a cookie,
// so it will be possible to find out whether cookies are blocked
// Also it will prevent response from being cached at any level
this.dataToSend.rnd = Math.random();
Mibew.Utils.createCookie(
'mibewRndValue',
this.dataToSend.rnd
);
if (userId !== false) {
this.dataToSend.user_id = userId;
} else {
@ -330,6 +343,12 @@ var Mibew = Mibew || {};
* - 'acceptCaption': String, caption for accept button.
*/
Mibew.Invitation.create = function (options) {
// Cookies are blocked, invitation will behave badly
if (Mibew.Objects.widget.cookiesBlocked) {
return;
}
var operatorName = options.operatorName;
var avatarUrl = options.avatarUrl;
var threadUrl = options.threadUrl;
@ -487,6 +506,14 @@ var Mibew = Mibew || {};
*/
Mibew.APIFunctions = {};
/**
* Update cookies status. API function
* @param {Object} response Data object from server
*/
Mibew.APIFunctions.updateCookiesBlockStatus = function(response) {
Mibew.Objects.widget.cookiesBlocked = response.cookiesBlocked;
};
/**
* Update user id. API function
* @param {Object} response Data object from server

View File

@ -563,6 +563,8 @@ function setup_chatview_for_operator(
*/
function visitor_from_request()
{
$tmp_request = Request::createFromGlobals();
$default_name = getlocal("Guest");
$user_name = $default_name;
if (isset($_COOKIE[USERNAME_COOKIE_NAME])) {
@ -573,17 +575,32 @@ function visitor_from_request()
}
if ($user_name == $default_name) {
$temp = Request::createFromGlobals()->query->get('name');
$temp = $tmp_request->query->get('name');
$user_name = (isset($temp) && ($temp !== '')) ? $temp : $user_name;
}
if (isset($_COOKIE[USERID_COOKIE_NAME])) {
$user_id = $_COOKIE[USERID_COOKIE_NAME];
} else {
$user_id = uniqid('', true);
setcookie(USERID_COOKIE_NAME, $user_id, time() + 60 * 60 * 24 * 365);
// Check whether user id already exists in absence of the appropriate cookie:
// some browsers could have weird behaviour
$temp = $tmp_request->query->get('user_id');
$user_id = (isset($temp)) ? $temp : uniqid('', true);
$cookie_properties = array( 'expires' => time() + 60 * 60 * 24 * 365 );
if (version_compare(phpversion(), '7.3.0', '<')) {
setcookie(USERID_COOKIE_NAME, $user_id, $cookie_properties['expires']);
} else {
if ($tmp_request->isSecure()) {
$cookie_properties['samesite'] = 'None';
$cookie_properties['secure'] = true;
}
setcookie(USERID_COOKIE_NAME, $user_id, $cookie_properties);
}
}
unset($tmp_request);
return array('id' => $user_id, 'name' => $user_name);
}

View File

@ -361,7 +361,9 @@ class Application implements
$response->headers->setCookie(CookieFactory::fromRequest($request)->createCookie(
LOCALE_COOKIE_NAME,
get_current_locale(),
time() + 60 * 60 * 24 * 1000
time() + 60 * 60 * 24 * 1000,
true,
false
));
$response->prepare($request);

View File

@ -299,4 +299,29 @@ class UserChatController extends AbstractController
// Expand page
return $this->render('chat', $page);
}
/**
* Check possibility to set cookie on the client side: it's crusial for chat to work,
* since otherwise there will be no session.
*
* @param Request $request Incoming request.
* @return string Empty string.
* @throws AccessDeniedException if the thread with specified ID and token is
* not found.
*/
public function cookieSetPermissionAction(Request $request)
{
$blocked = true;
$token1 = $request->query->get('rnd');
if ($request->cookies->has('mibewCheckToken')) {
$token2 = $request->cookies->get('mibewCheckToken');
if ($token1 === $token2) {
$blocked = false;
}
}
if ($blocked) {
throw new NotFoundException('Cookies are blocked.');
}
return "";
}
}

View File

@ -51,8 +51,27 @@ class WidgetController extends AbstractController
'data' => array(),
);
// Check whether third parties cookies are blocked
// It will be impossible to chat if they are
$cookies_blocked = false;
$rnd_value1 = $request->query->get('rnd', false);
if ($request->cookies->has('mibewRndValue')) {
$rnd_value2 = $request->cookies->get('mibewRndValue');
if ($rnd_value1 !== $rnd_value2) {
$cookies_blocked = true;
}
} else {
$cookies_blocked = true;
}
// Update status on blocked cookie
$response_data['handlers'][] = 'updateCookiesBlockStatus';
$response_data['dependencies']['updateCookiesBlockStatus'] = array();
$response_data['data']['cookiesBlocked'] = $cookies_blocked;
$tracking_allowed = (Settings::get('enabletracking') == '1')
&& (Settings::get('trackoperators') == '1' || !$this->getOperator());
&& (Settings::get('trackoperators') == '1' || !$this->getOperator())
&& !$cookies_blocked;
if ($tracking_allowed) {
$entry = $request->query->get('entry', '');

View File

@ -83,11 +83,14 @@ class CookieFactory
* @param string $name The name of the cookie.
* @param string $value The value of the cookie.
* @param int|string|\DateTime $expire The time the cookie expires.
* @param bool $httpOnly Whether the cookie will be made accessible only
* @param bool $http_only Whether the cookie will be made accessible only
* through the HTTP protocol.
* @param bool $same_site Whether the cookie should be used only on the
* original site. Otherwise (but only if it's already marked as secure)
* it will be marked as SameSite=None
* @return Cookie
*/
public function createCookie($name, $value = null, $expire = 0, $http_only = true)
public function createCookie($name, $value = null, $expire = 0, $http_only = true, $same_site = true)
{
return new Cookie(
$name,
@ -96,7 +99,9 @@ class CookieFactory
$this->getPath(),
$this->getDomain(),
$this->isSecure(),
$http_only
$http_only,
true,
!$same_site && $this->isSecure() ? 'None' : false
);
}

View File

@ -240,6 +240,9 @@ abstract class AbstractProcessor
return $this->buildSyncResponses($this->responses);
}
}
} catch (\Exception $e) {
// Looks like a try to use chat with blocked cookies or something, return 403
return new Response('Update forbidden', Response::HTTP_FORBIDDEN);
} catch (\Exception $e) {
// Something went wrong. Trigger error event
$vars = array('exception' => $e);

View File

@ -553,7 +553,17 @@ class ThreadProcessor extends ClientSideProcessor implements
$thread->renameUser($args['name']);
// Update user name in cookies
$data = strtr(base64_encode($args['name']), '+/=', '-_,');
setcookie(USERNAME_COOKIE_NAME, $data, time() + 60 * 60 * 24 * 365);
$cookie_properties = array( 'expires' => time() + 60 * 60 * 24 * 365 );
if (version_compare(phpversion(), '7.3.0', '<')) {
setcookie(USERNAME_COOKIE_NAME, $data, $cookie_properties['expires']);
} else {
if ($this->currentRequest && $this->currentRequest->isSecure()) {
$cookie_properties['samesite'] = 'None';
$cookie_properties['secure'] = true;
}
setcookie(USERNAME_COOKIE_NAME, $data, $cookie_properties);
}
}
/**

View File

@ -55,6 +55,7 @@ $tmp_request = \Symfony\Component\HttpFoundation\Request::createFromGlobals();
@ini_set('session.cookie_httponly', true);
if ($tmp_request->isSecure()) {
@ini_set('session.cookie_secure', true);
@ini_set('session.cookie_samesite', 'None');
}
@ini_set('session.cookie_path', $tmp_request->getBasePath() . "/");
@ini_set('session.name', 'MibewSessionID');
@ -62,14 +63,8 @@ if ($tmp_request->isSecure()) {
// Remove temporary request to keep global scope clean.
unset($tmp_request);
if (version_compare(phpversion(), '5.4.0', '<')) {
if (session_id() == '') {
session_start();
}
} else {
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
if (function_exists("date_default_timezone_set")) {