Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/b-7.2.x' into b-8.0.x
Browse files Browse the repository at this point in the history
  • Loading branch information
liulka-oxid committed Oct 7, 2024
2 parents c7fffe8 + 2442d84 commit 595ba73
Show file tree
Hide file tree
Showing 10 changed files with 333 additions and 127 deletions.
13 changes: 13 additions & 0 deletions source/Application/Component/UserComponent.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
use OxidEsales\Eshop\Core\Form\FormFieldsTrimmer;
use OxidEsales\Eshop\Core\Form\UpdatableFieldsConstructor;
use OxidEsales\Eshop\Core\Registry;
use OxidEsales\EshopCommunity\Core\Di\ContainerFacade;
use OxidEsales\EshopCommunity\Internal\Domain\Authentication\Bridge\PasswordServiceBridgeInterface;

use function array_key_exists;
use function is_array;

Expand Down Expand Up @@ -512,6 +515,7 @@ public function createUser()

if (!$isPrivateSales) {
Registry::getSession()->setVariable('usr', $user->getId());
$this->setSessionLoginToken((string)$user->getFieldData('oxpassword'));
$this->afterLogin($user);

// order remark
Expand Down Expand Up @@ -909,4 +913,13 @@ private function removeNonAddressFields(array $addressFormData): array

return $addressFormData;
}

private function setSessionLoginToken(string $passwordHash): void
{
Registry::getSession()
->setVariable(
'login-token',
ContainerFacade::get(PasswordServiceBridgeInterface::class)->hash($passwordHash)
);
}
}
6 changes: 0 additions & 6 deletions source/Application/Controller/Admin/LanguageMain.php
Original file line number Diff line number Diff line change
Expand Up @@ -380,15 +380,9 @@ protected function addNewMultilangFieldsToDb()
//creating new multilingual fields with new id over whole DB
$oDbMeta = oxNew(\OxidEsales\Eshop\Core\DbMetaDataHandler::class);

$db = \OxidEsales\Eshop\Core\DatabaseProvider::getDb();
$db->startTransaction();
try {
$oDbMeta->addNewLangToDb();
$db->commitTransaction();
} catch (Exception $oEx) {
if (!$oEx instanceof \PDOException) {
$db->rollbackTransaction();
}
//show warning
$oEx = oxNew(\OxidEsales\Eshop\Core\Exception\ExceptionToDisplay::class);
$oEx->setMessage('LANGUAGE_ERROR_ADDING_MULTILANG_FIELDS');
Expand Down
2 changes: 0 additions & 2 deletions source/Application/Controller/ForgotPasswordController.php
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,6 @@ public function isExpiredLink()
}

/**
* Template variable getter. Returns searched article list
*
* @return string
*/
public function getForgotEmail()
Expand Down
138 changes: 56 additions & 82 deletions source/Application/Model/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -1406,24 +1406,21 @@ protected function _getLoginQuery($userName, $password, $shopId, $isAdmin) // ph
*/
public function login($userName, $password, $setSessionCookie = false)
{
$isLoginAttemptToAdminBackend = $this->isAdmin();
$isAdmin = $this->isAdmin();

$cookie = Registry::getUtilsServer()->getOxCookie();
if ($cookie === null && $isLoginAttemptToAdminBackend) {
if ($cookie === null && $isAdmin) {
throw oxNew(CookieException::class, 'ERROR_MESSAGE_COOKIE_NOCOOKIE');
}

$config = Registry::getConfig();
$shopId = $config->getShopId();

/** New authentication mechanism */
$passwordHashFromDatabase = $this->getPasswordHashFromDatabase($userName, $shopId, $isLoginAttemptToAdminBackend);
$passwordHash = $this->getPasswordHashFromDatabase($userName, $shopId, $isAdmin);
$passwordServiceBridge = ContainerFacade::get(PasswordServiceBridgeInterface::class);
if ($password && !$this->isLoaded()) {
$userIsAuthenticated = $passwordServiceBridge->verifyPassword($password, $passwordHashFromDatabase);
if ($userIsAuthenticated) {
$this->loadAuthenticatedUser($userName, $shopId);
}
if ($password && !$this->isLoaded() && $this->verifyHash($password, $passwordHash)) {
$this->loadAuthenticatedUser($userName, $shopId);
}

/** Old authentication + authorization */
Expand All @@ -1434,10 +1431,10 @@ public function login($userName, $password, $setSessionCookie = false)
/** If needed, store a rehashed password with the authenticated user */
if ($password && $this->isLoaded()) {
$passwordNeedsRehash = $this->isOutdatedPasswordHashAlgorithmUsed ||
$passwordServiceBridge->passwordNeedsRehash($passwordHashFromDatabase);
$passwordServiceBridge->passwordNeedsRehash($passwordHash);
if ($passwordNeedsRehash) {
$generatedPasswordHash = $this->hashPassword($password);
$this->oxuser__oxpassword = new Field($generatedPasswordHash, Field::T_RAW);
$passwordHash = $this->getHash($password);
$this->oxuser__oxpassword = new Field($passwordHash, Field::T_RAW);
/** The use of a salt is deprecated and an empty salt will be stored */
$this->oxuser__oxpasssalt = new Field('');
$this->save();
Expand All @@ -1457,11 +1454,9 @@ public function login($userName, $password, $setSessionCookie = false)
//resetting active user
$this->setUser(null);

if ($isLoginAttemptToAdminBackend) {
Registry::getSession()->setVariable('auth', $this->oxuser__oxid->value);
} else {
Registry::getSession()->setVariable('usr', $this->oxuser__oxid->value);
}
$userIdParameter = $isAdmin ? 'auth' : 'usr';
Registry::getSession()->setVariable($userIdParameter, $this->getFieldData('oxid'));
Registry::getSession()->setVariable('login-token', $this->getHash($passwordHash));

// cookie must be set ?
if ($setSessionCookie && $config->getConfigParam('blShowRememberMe')) {
Expand Down Expand Up @@ -1524,17 +1519,14 @@ private function getAuthenticatedUserId(string $userName, int $shopId, bool $isL
*/
public function logout()
{
// deleting session info
Registry::getSession()->deleteVariable('usr'); // for front end
Registry::getSession()->deleteVariable('auth'); // for back end
Registry::getSession()->deleteVariable('dynvalue');
Registry::getSession()->deleteVariable('paymentid');
// Registry::getSession()->deleteVariable( 'deladrid' );
Registry::getSession()->deleteVariable('login-token');

// delete cookie
Registry::getUtilsServer()->deleteUserCookie(\OxidEsales\Eshop\Core\Registry::getConfig()->getShopID());
Registry::getUtilsServer()->deleteUserCookie(Registry::getConfig()->getShopID());

// unsetting global user
$this->setUser(null);

return true;
Expand All @@ -1552,54 +1544,37 @@ public function loadAdminUser()
}

/**
* Loads active user object. If
* user is not available - returns false.
*
* @param bool $blForceAdmin (default false)
* @param bool $blForceAdmin
*
* @return bool
*/
public function loadActiveUser($blForceAdmin = false)
{
$oConfig = \OxidEsales\Eshop\Core\Registry::getConfig();

$blAdmin = $this->isAdmin() || $blForceAdmin;

// first - checking session info
$sUserID = $blAdmin ? Registry::getSession()->getVariable('auth') : Registry::getSession()->getVariable('usr');
$isAdmin = $this->isAdmin() || $blForceAdmin;
$userIdParameter = $isAdmin ? 'auth' : 'usr';
$userId = Registry::getSession()->getVariable($userIdParameter);

// trying automatic login (by 'remember me' cookie)
$blFoundInCookie = false;
if (!$sUserID && !$blAdmin && $oConfig->getConfigParam('blShowRememberMe')) {
$sUserID = $this->getCookieUserId();
$blFoundInCookie = $sUserID ? true : false;
}

// checking user results
if ($sUserID) {
if ($this->load($sUserID)) {
// storing into session
if ($blAdmin) {
Registry::getSession()->setVariable('auth', $sUserID);
} else {
Registry::getSession()->setVariable('usr', $sUserID);
}

// marking the way user was loaded
$this->_blLoadedFromCookie = $blFoundInCookie;

return true;
}
} else {
// no user
if ($blAdmin) {
Registry::getSession()->deleteVariable('auth');
} else {
Registry::getSession()->deleteVariable('usr');
}

$isUserIdInCookie = false;
if (!$userId && !$isAdmin && Registry::getConfig()->getConfigParam('blShowRememberMe')) {
$userId = $this->getCookieUserId();
$isUserIdInCookie = (bool)$userId;
}
if (!$userId) {
Registry::getSession()->deleteVariable($userIdParameter);
return false;
}
if ($this->load($userId)) {
$loginToken = (string)Registry::getSession()->getVariable('login-token');
$passwordHash = (string)$this->getFieldData('oxpassword');
if ($loginToken && !$this->verifyHash($passwordHash, $loginToken)) {
$this->logout();
return false;
}
Registry::getSession()->setVariable($userIdParameter, $userId);
$this->_blLoadedFromCookie = $isUserIdInCookie;
return true;
}
}

/**
Expand All @@ -1613,7 +1588,6 @@ protected function getCookieUserId()
$oConfig = \OxidEsales\Eshop\Core\Registry::getConfig();
$sShopID = $oConfig->getShopId();
if (($sSet = Registry::getUtilsServer()->getUserCookie($sShopID))) {
$passwordServiceBridge = ContainerFacade::get(PasswordServiceBridgeInterface::class);
$oDb = \OxidEsales\Eshop\Core\DatabaseProvider::getDb();
$aData = explode('@@@', $sSet);
$sUser = $aData[0];
Expand All @@ -1623,7 +1597,7 @@ protected function getCookieUserId()
$rs = $oDb->select($sSelect);
if ($rs != false && $rs->count() > 0) {
while (!$rs->EOF) {
if ($passwordServiceBridge->verifyPassword($rs->fields[1] . static::USER_COOKIE_SALT, $sPWD)) {
if ($this->verifyHash($rs->fields[1] . static::USER_COOKIE_SALT, $sPWD)) {
// found
$sUserID = $rs->fields[0];
break;
Expand Down Expand Up @@ -2000,33 +1974,21 @@ public function encodePassword($sPassword, $sSalt)

return $oHasher->hash($sPassword, $sSalt);
}

/**
* Sets new password for user ( save is not called)
* Sets new password for user (save is not called)
*
* @param string $password password
* @param string $password
*/
public function setPassword($password = null)
{
if (empty($password)) {
$passwordHash = '';
} else {
$passwordHash = $this->hashPassword($password);
}

$this->oxuser__oxpassword = new \OxidEsales\Eshop\Core\Field($passwordHash, \OxidEsales\Eshop\Core\Field::T_RAW);
$this->oxuser__oxpasssalt = new \OxidEsales\Eshop\Core\Field('');
$this->oxuser__oxpassword = new Field(
empty($password) ? '' : $this->getHash($password),
Field::T_RAW
);
$this->oxuser__oxpasssalt = new Field('');
}

/**
* @param string $password
*
* @return string
*/
private function hashPassword(string $password): string
{
return ContainerFacade::get(PasswordServiceBridgeInterface::class)
->hash($password);
}

/**
* Checks if user entered password is the same as old
Expand Down Expand Up @@ -2721,4 +2683,16 @@ private function getRandomToken(): string
return ContainerFacade::get(RandomTokenGeneratorBridgeInterface::class)
->getAlphanumericToken(32);
}

private function getHash(string $password): string
{
return ContainerFacade::get(PasswordServiceBridgeInterface::class)
->hash($password);
}

private function verifyHash(string $password, string $hash): string
{
return ContainerFacade::get(PasswordServiceBridgeInterface::class)
->verifyPassword($password, $hash);
}
}
44 changes: 41 additions & 3 deletions tests/Codeception/Acceptance/Admin/NewLanguageCreationCest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,60 @@
namespace OxidEsales\EshopCommunity\Tests\Codeception\Acceptance\Admin;

use Codeception\Attribute\Group;
use OxidEsales\Codeception\Admin\Languages;
use OxidEsales\Codeception\Module\Translation\Translator;
use OxidEsales\EshopCommunity\Tests\Codeception\Support\AcceptanceTester;

#[Group('admin')]
final class NewLanguageCreationCest
{
public function newLanguageCreation(AcceptanceTester $I): void
public function newLanguagesCreation(AcceptanceTester $I): void
{
$I->wantToTest('if we can create a new language successfully');
$I->wantToTest('if we can create four new languages successfully.');

$adminPanel = $I->loginAdmin();
$languages = $adminPanel->openLanguages();
$languages->createNewLanguage('lt', 'Lietuviu');

$I->amGoingTo('create the first language.');
$this->createNewLanguage($languages, $I, 'lt', 'Lietuviu');

$I->amGoingTo('create the second language.');
$this->createNewLanguage($languages, $I, 'hu', 'Hungarian');

$I->amGoingTo('create the third language.');
$this->createNewLanguage($languages, $I, 'mt', 'Maltese');

$I->amGoingTo('create the fourth language.');
$this->createNewLanguage($languages, $I, 'es', 'Spanish');

$I->amGoingTo('generate DB views.');
$tools = $adminPanel->openTools();
$tools->updateDbViews();

$I->amGoingTo('check the new languages fields.');
$this->checkLanguageFields($I);
}

private function createNewLanguage(Languages $languages, AcceptanceTester $I, string $code, string $name): void
{
$languages->createNewLanguage($code, $name);
$I->amGoingTo('check extra messages.');
$this->checkMessages($I);
}

private function checkMessages(AcceptanceTester $I): void
{
$I->selectEditFrame();
$I->expect('not to see the multilingual fields error message. Four language fields are predefined,
so on the addition of a fifth language, new fields should be added as well.');
$I->dontSee(Translator::translate('LANGUAGE_ERROR_ADDING_MULTILANG_FIELDS'));
}

private function checkLanguageFields(AcceptanceTester $I): void
{
$I->retryGrabFromDatabase('oxv_oxarticles_lt', 'oxid', ['oxartnum' => '3503']);
$I->retryGrabFromDatabase('oxv_oxarticles_hu', 'oxid', ['oxartnum' => '3503']);
$I->retryGrabFromDatabase('oxv_oxarticles_mt', 'oxid', ['oxartnum' => '3503']);
$I->retryGrabFromDatabase('oxv_oxarticles_es', 'oxid', ['oxartnum' => '3503']);
}
}
40 changes: 40 additions & 0 deletions tests/Codeception/Acceptance/Admin/SessionHandlingCest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

/**
* Copyright © OXID eSales AG. All rights reserved.
* See LICENSE file for license details.
*/

declare(strict_types=1);

namespace OxidEsales\EshopCommunity\Tests\Codeception\Acceptance\Admin;

use Codeception\Attribute\Group;
use Codeception\Util\Fixtures;
use OxidEsales\EshopCommunity\Tests\Codeception\Support\AcceptanceTester;

final class SessionHandlingCest
{
#[Group('session')]
public function adminSessionAfterPasswordChange(AcceptanceTester $I): void
{
$I->wantToTest('that admin will be logged out if someone changes his password from another active session');
$userData = Fixtures::get('adminUser');
$adminLoginPage = $I->openAdmin();
$I->amGoingTo('log the existing admin in');
$adminLoginPage->login($userData['userLoginName'], $userData['userPassword']);

$I->amGoingTo('mock password change for this admin from another browser session');
$I->updateInDatabase(
'oxuser',
['OXPASSWORD' => 'some-new-password-hash'],
['OXUSERNAME' => $userData['userLoginName']]
);

$I->amGoingTo('send any page request after password change');
$I->reloadPage();

$I->expect('that admin is logged out');
$adminLoginPage->seeLoginForm();
}
}
Loading

0 comments on commit 595ba73

Please sign in to comment.