From bf9d7002b6b5400b024c17d794009dcf906443df Mon Sep 17 00:00:00 2001 From: Moritz Demmer Date: Wed, 13 Nov 2024 05:23:43 +0100 Subject: [PATCH] OXDEV-8351 Add proper error handling for duplicate emails --- CHANGELOG-7.3.md | 1 + .../Application/Component/UserComponent.php | 65 ++++++------ .../Application/Controller/Admin/UserMain.php | 25 +++-- source/Application/Model/User.php | 99 ++++++++++++------- source/Application/translations/de/lang.php | 2 +- source/Application/translations/en/lang.php | 2 +- source/Core/InputValidator.php | 35 ++++--- .../Codeception/Acceptance/Admin/UserCest.php | 34 +++++++ .../Acceptance/UserAccountCest.php | 50 ++++++++-- tests/Codeception/Support/Data/dump.sql | 3 +- tests/Codeception/Support/Data/user.php | 7 ++ .../Component/UserComponentTest.php | 85 +++++++++++++++- 12 files changed, 295 insertions(+), 113 deletions(-) diff --git a/CHANGELOG-7.3.md b/CHANGELOG-7.3.md index 25e5cec291..bf109ab424 100644 --- a/CHANGELOG-7.3.md +++ b/CHANGELOG-7.3.md @@ -8,6 +8,7 @@ ### Fixed - Shop ID resolution considers SSL language URLs +- Email existence check when changing from user to guest email [#0006860](https://bugs.oxid-esales.com/view.php?id=6860) ### Changed - Raised minimum required version of Symfony components to 6.4 diff --git a/source/Application/Component/UserComponent.php b/source/Application/Component/UserComponent.php index 2ea5c46298..0e95acc830 100644 --- a/source/Application/Component/UserComponent.php +++ b/source/Application/Component/UserComponent.php @@ -654,59 +654,60 @@ protected function changeUserWithoutRedirect() if (!$user) { return; } + + $currentEmail = $user->getFieldData('oxusername'); + $password = $user->getFieldData('oxpassword'); $shippingAddress = $this->getShippingAddress(); $billingAddress = $this->getBillingAddress(); + $newEmail = $billingAddress['oxuser__oxusername'] ?? ''; - $username = $user->getFieldData('oxusername'); - $password = $user->getFieldData('oxpassword'); try { - $newName = $billingAddress['oxuser__oxusername'] ?? ''; + $isUsernameUpdated = $this->isUserNameUpdated($currentEmail ?? '', $newEmail); + if ($isUsernameUpdated && $this->isGuestUser($user)) { + $this->deleteExistingGuestUser($newEmail); + } if ( - $this->isGuestUser($user) - && $this->isUserNameUpdated($user->oxuser__oxusername->value ?? '', $newName) + !$this->isGuestUser($user) + && $isUsernameUpdated + && $user->isEmailInUse($newEmail) ) { - $this->deleteExistingGuestUser($newName); + Registry::getUtilsView()->addErrorToDisplay('ERROR_MESSAGE_USER_USEREXISTS'); + return false; } - $user->changeUserData($username, $password, $password, $billingAddress, $shippingAddress); + $user->changeUserData($currentEmail, $password, $password, $billingAddress, $shippingAddress); $isSubscriptionRequested = Registry::getRequest()->getRequestEscapedParameter('blnewssubscribed'); - $userSubscriptionStatus = $isSubscriptionRequested - ?? $user->getNewsSubscription()->getOptInStatus(); - // check if email address changed, if so, force check newsletter subscription settings. - $billingUsername = $billingAddress['oxuser__oxusername'] ?? null; - $forceSubscriptionCheck = ($billingUsername !== null && $billingUsername !== $username); + $userSubscriptionStatus = $isSubscriptionRequested ?? $user->getNewsSubscription()->getOptInStatus(); $isSubscriptionEmailRequested = Registry::getConfig()->getConfigParam('blOrderOptInEmail'); + $this->_blNewsSubscriptionStatus = $user->setNewsSubscription( $userSubscriptionStatus, $isSubscriptionEmailRequested, - $forceSubscriptionCheck + $isUsernameUpdated ); + + $this->resetPermissions(); + + $orderRemark = Registry::getRequest()->getRequestParameter('order_remark', true); + if ($orderRemark) { + $session->setVariable('ordrem', $orderRemark); + } else { + $session->deleteVariable('ordrem'); + } + + if ($basket = $session->getBasket()) { + $basket->setBasketUser(null); + $basket->onUpdate(); + } + + return true; } catch (UserException | ConnectionException | InputException $exception) { Registry::getUtilsView()->addErrorToDisplay($exception, false, true); - return; } catch (\Throwable) { Registry::getUtilsView()->addErrorToDisplay('ERROR_MESSAGE_USER_UPDATE_FAILED', false, true); return false; } - - $this->resetPermissions(); - - // order remark - $orderRemark = Registry::getRequest()->getRequestParameter('order_remark', true); - - if ($orderRemark) { - $session->setVariable('ordrem', $orderRemark); - } else { - $session->deleteVariable('ordrem'); - } - - if ($basket = $session->getBasket()) { - $basket->setBasketUser(null); - $basket->onUpdate(); - } - - return true; } /** diff --git a/source/Application/Controller/Admin/UserMain.php b/source/Application/Controller/Admin/UserMain.php index 6dd0e5c005..c199c85900 100644 --- a/source/Application/Controller/Admin/UserMain.php +++ b/source/Application/Controller/Admin/UserMain.php @@ -7,6 +7,7 @@ namespace OxidEsales\EshopCommunity\Application\Controller\Admin; +use OxidEsales\Eshop\Application\Model\User; use OxidEsales\Eshop\Core\Registry; use stdClass; use Exception; @@ -26,7 +27,7 @@ public function render() parent::render(); // malladmin stuff - $oAuthUser = oxNew(\OxidEsales\Eshop\Application\Model\User::class); + $oAuthUser = oxNew(User::class); $oAuthUser->loadAdminUser(); $blisMallAdmin = $oAuthUser->oxuser__oxrights->value == "malladmin"; @@ -52,7 +53,7 @@ public function render() $soxId = $this->_aViewData["oxid"] = $this->getEditObjectId(); if (isset($soxId) && $soxId != "-1") { // load object - $oUser = oxNew(\OxidEsales\Eshop\Application\Model\User::class); + $oUser = oxNew(User::class); $oUser->load($soxId); $this->_aViewData["edit"] = $oUser; @@ -102,31 +103,30 @@ public function save() { parent::save(); - //allow admin information edit only for MALL admins $soxId = $this->getEditObjectId(); if ($this->allowAdminEdit($soxId)) { $aParams = Registry::getRequest()->getRequestEscapedParameter("editval"); - // checkbox handling if (!isset($aParams['oxuser__oxactive'])) { $aParams['oxuser__oxactive'] = 0; } - $oUser = oxNew(\OxidEsales\Eshop\Application\Model\User::class); + $oUser = oxNew(User::class); if ($soxId != "-1") { $oUser->load($soxId); } else { $aParams['oxuser__oxid'] = null; } - //setting new password if (($sNewPass = Registry::getRequest()->getRequestEscapedParameter("newPassword"))) { $oUser->setPassword($sNewPass); } - //FS#2167 V checks for already used email - - if (isset($aParams['oxuser__oxusername']) && $oUser->checkIfEmailExists($aParams['oxuser__oxusername'])) { + if ( + isset($aParams['oxuser__oxusername']) + && ($aParams['oxuser__oxusername'] !== $oUser->getRawFieldData('oxusername')) + && $oUser->isEmailInUse($aParams['oxuser__oxusername']) + ) { $this->_sSaveError = 'EXCEPTION_USER_USEREXISTS'; return; @@ -134,18 +134,15 @@ public function save() $oUser->assign($aParams); - //seting shop id for ONLY for new created user if ($soxId == "-1") { $this->onUserCreation($oUser); } - // A. changing field type to save birth date correctly $oUser->oxuser__oxbirthdate->fldtype = 'char'; try { $oUser->save(); - // set oxid if inserted $this->setEditObjectId($oUser->getId()); } catch (Exception $oExcp) { $this->_sSaveError = $oExcp->getMessage(); @@ -168,9 +165,9 @@ protected function calculateAdditionalRights($userRights) /** * Additional actions on user creation. * - * @param \OxidEsales\Eshop\Application\Model\User $user + * @param User $user * - * @return \OxidEsales\Eshop\Application\Model\User + * @return User */ protected function onUserCreation($user) { diff --git a/source/Application/Model/User.php b/source/Application/Model/User.php index 764ff102aa..c8520dc0a8 100644 --- a/source/Application/Model/User.php +++ b/source/Application/Model/User.php @@ -17,8 +17,10 @@ use OxidEsales\Eshop\Core\Model\ListModel; use OxidEsales\Eshop\Core\Registry; use OxidEsales\Eshop\Core\TableViewNameGenerator; +use OxidEsales\EshopCommunity\Core\Di\ContainerFacade; use OxidEsales\EshopCommunity\Internal\Domain\Authentication\Bridge\PasswordServiceBridgeInterface; use OxidEsales\EshopCommunity\Internal\Domain\Authentication\Bridge\RandomTokenGeneratorBridgeInterface; +use OxidEsales\EshopCommunity\Internal\Framework\Database\QueryBuilderFactoryInterface; /** * User manager. @@ -1714,53 +1716,76 @@ protected function update() /** * Checks for already used email * - * @param string $sEmail user email/login + * @param string $email user email/login * * @return bool */ - public function checkIfEmailExists($sEmail) + public function checkIfEmailExists($email) { - $myConfig = \OxidEsales\Eshop\Core\Registry::getConfig(); - // We force reading from master to prevent issues with slow replications or open transactions (see ESDEV-3804). - $masterDb = \OxidEsales\Eshop\Core\DatabaseProvider::getMaster(); - $iShopId = $myConfig->getShopId(); - $blExists = false; + if (!$email) { + return false; + } - $sQ = 'select oxshopid, oxrights, oxpassword from oxuser where oxusername = :oxusername'; - $params = [ - ':oxusername' => (string) $sEmail - ]; - if (($sOxid = $this->getId())) { - $sQ .= " and oxid <> :notoxid"; - $params[':notoxid'] = $sOxid; + $config = Registry::getConfig(); + $masterDb = DatabaseProvider::getMaster(); + $shopId = $config->getShopId(); + + $query = 'SELECT oxshopid, oxrights, oxpassword FROM oxuser WHERE oxusername = :oxusername'; + $params = [':oxusername' => (string) $email]; + + if ($id = $this->getId()) { + $query .= " AND oxid <> :notoxid"; + $params[':notoxid'] = $id; } - $oRs = $masterDb->select($sQ, $params); - if ($oRs != false && $oRs->count() > 0) { - if ($this->_blMallUsers) { - $blExists = true; - if ($oRs->fields[1] == 'user' && !$oRs->fields[2]) { - // password is not set - allow to override - $blExists = false; - } - } else { - $blExists = false; - while (!$oRs->EOF) { - if ($oRs->fields[1] != 'user') { - // exists admin with same login - must not allow - $blExists = true; - break; - } elseif ($oRs->fields[0] == $iShopId && $oRs->fields[2]) { - // exists same login (with password) in same shop - $blExists = true; - break; - } - $oRs->fetchRow(); - } + $result = $masterDb->select($query, $params); + + if (!$result || $result->count() === 0) { + return false; + } + + if ($this->_blMallUsers) { + if ($result->fields[1] === 'user' && !$result->fields[2]) { + return false; // No password set - allow override + } + return true; + } + + while (!$result->EOF) { + if ($result->fields[1] !== 'user') { + return true; // Admin user exists + } + + if ($result->fields[0] == $shopId && ($result->fields[2])) { + return true; // User exists in same shop with password } + + $result->fetchRow(); + } + + return false; + } + + public function isEmailInUse(string $email): bool + { + $queryBuilder = ContainerFacade::get(QueryBuilderFactoryInterface::class)->create(); + $queryBuilder + ->select('1') + ->from('oxuser') + ->where('oxusername = :email') + ->andWhere('oxshopid = :shopId') + ->setParameters([ + 'email' => $email, + 'shopId' => Registry::getConfig()->getShopId() + ]); + + if ($this->getId()) { + $queryBuilder + ->andWhere('oxid != :currentUserId') + ->setParameter('currentUserId', $this->getId()); } - return $blExists; + return (bool) $queryBuilder->execute()->fetchOne(); } /** diff --git a/source/Application/translations/de/lang.php b/source/Application/translations/de/lang.php index deaf1adbc0..e8c5bda378 100644 --- a/source/Application/translations/de/lang.php +++ b/source/Application/translations/de/lang.php @@ -163,7 +163,7 @@ 'ERROR_MESSAGE_USER_NOVALUES' => 'E-Mail und Passwort müssen ausgefüllt sein!', 'ERROR_MESSAGE_USER_USERCREATIONFAILED' => 'Fehler beim Anlegen des Benutzers!', 'ERROR_MESSAGE_USER_UPDATE_FAILED' => 'Fehler beim Aktualisieren der Benutzerdaten!', -'ERROR_MESSAGE_USER_USEREXISTS' => '%s konnte nicht registriert werden. Haben Sie bereits ein Kundenkonto bei uns?', +'ERROR_MESSAGE_USER_USEREXISTS' => 'Bei dieser E-Mail-Adresse ist ein Fehler aufgetreten. Bitte wenden Sie sich an unseren Support.', 'ERROR_MESSAGE_VERSION_EXPIRED1' => 'Ihre Version ist leider abgelaufen. Bitte kontaktieren Sie', 'ERROR_MESSAGE_VOUCHER_INCORRECTPRICE' => 'Einkaufswert ist zu niedrig für diesen Gutschein!', 'ERROR_MESSAGE_VOUCHER_ISRESERVED' => 'Gutschein ist reserviert!', diff --git a/source/Application/translations/en/lang.php b/source/Application/translations/en/lang.php index 81abe9577c..1e4c669d3b 100644 --- a/source/Application/translations/en/lang.php +++ b/source/Application/translations/en/lang.php @@ -163,7 +163,7 @@ 'ERROR_MESSAGE_USER_NOVALUES' => 'E-mail address and password have to be entered!', 'ERROR_MESSAGE_USER_USERCREATIONFAILED' => 'Error creating the user!', 'ERROR_MESSAGE_USER_UPDATE_FAILED' => 'Error while updating the user data!', -'ERROR_MESSAGE_USER_USEREXISTS' => 'Not possible to register %s. Maybe you have already registered?', +'ERROR_MESSAGE_USER_USEREXISTS' => 'An error has occurred with this e-mail address. Please contact our support.', 'ERROR_MESSAGE_VERSION_EXPIRED1' => 'Your version is expired. Please contact', 'ERROR_MESSAGE_VOUCHER_INCORRECTPRICE' => 'The total price is too low for this coupon!', 'ERROR_MESSAGE_VOUCHER_ISRESERVED' => 'This coupon is reserved!', diff --git a/source/Core/InputValidator.php b/source/Core/InputValidator.php index 2de1e4f51d..ddd7bda956 100644 --- a/source/Core/InputValidator.php +++ b/source/Core/InputValidator.php @@ -10,7 +10,9 @@ use OxidEsales\Eshop\Application\Model\Address; use OxidEsales\Eshop\Application\Model\User; use OxidEsales\Eshop\Core\Exception\ArticleInputException; +use OxidEsales\Eshop\Core\Exception\InputException; use OxidEsales\Eshop\Core\Exception\StandardException; +use OxidEsales\Eshop\Core\Exception\UserException; use OxidEsales\Eshop\Core\Registry; use OxidEsales\Eshop\Core\Str; use OxidEsales\EshopCommunity\Core\Di\ContainerFacade; @@ -107,24 +109,28 @@ public function validateBasketAmount($amount) */ public function checkLogin($user, $login, $invAddress) { - $login = (isset($invAddress['oxuser__oxusername'])) ? $invAddress['oxuser__oxusername'] : $login; + $login = (isset($invAddress['oxuser__oxusername'])) + ? $invAddress['oxuser__oxusername'] + : $login; + + if ( + isset($user->oxuser__oxpassword->value) && + $user->oxuser__oxpassword->value && + $login != $user->oxuser__oxusername->value + ) { + $newPassword = (isset($invAddress['oxuser__oxpassword']) && $invAddress['oxuser__oxpassword']) + ? $invAddress['oxuser__oxpassword'] + : Registry::getRequest()->getRequestEscapedParameter('user_password'); - // check only for users with password during registration - // if user wants to change user name - we must check if passwords are ok before changing - if (isset($user->oxuser__oxpassword->value) && $user->oxuser__oxpassword->value && $login != $user->oxuser__oxusername->value) { - // on this case password must be taken directly from request - $newPassword = (isset($invAddress['oxuser__oxpassword']) && $invAddress['oxuser__oxpassword']) ? $invAddress['oxuser__oxpassword'] : Registry::getRequest()->getRequestEscapedParameter('user_password'); if (!$newPassword) { - // 1. user forgot to enter password - $exception = oxNew(\OxidEsales\Eshop\Core\Exception\InputException::class); - $exception->setMessage(\OxidEsales\Eshop\Core\Registry::getLang()->translateString('ERROR_MESSAGE_INPUT_NOTALLFIELDS')); + $message = Registry::getLang()->translateString('ERROR_MESSAGE_INPUT_NOTALLFIELDS'); + $exception = oxNew(InputException::class, $message); return $this->addValidationError("oxuser__oxpassword", $exception); } else { - // 2. entered wrong password if (!$user->isSamePassword($newPassword)) { - $exception = oxNew(\OxidEsales\Eshop\Core\Exception\UserException::class); - $exception->setMessage(\OxidEsales\Eshop\Core\Registry::getLang()->translateString('ERROR_MESSAGE_PASSWORD_DO_NOT_MATCH')); + $message = Registry::getLang()->translateString('ERROR_MESSAGE_PASSWORD_DO_NOT_MATCH'); + $exception = oxNew(UserException::class, $message); return $this->addValidationError("oxuser__oxpassword", $exception); } @@ -132,9 +138,8 @@ public function checkLogin($user, $login, $invAddress) } if ($user->checkIfEmailExists($login)) { - //if exists then we do not allow to do that - $exception = oxNew(\OxidEsales\Eshop\Core\Exception\UserException::class); - $exception->setMessage(sprintf(\OxidEsales\Eshop\Core\Registry::getLang()->translateString('ERROR_MESSAGE_USER_USEREXISTS'), $login)); + $message = Registry::getLang()->translateString('ERROR_MESSAGE_USER_USEREXISTS'); + $exception = oxNew(UserException::class, $message); return $this->addValidationError("oxuser__oxusername", $exception); } diff --git a/tests/Codeception/Acceptance/Admin/UserCest.php b/tests/Codeception/Acceptance/Admin/UserCest.php index 8e99babc8a..e8023b7c97 100644 --- a/tests/Codeception/Acceptance/Admin/UserCest.php +++ b/tests/Codeception/Acceptance/Admin/UserCest.php @@ -14,6 +14,7 @@ use OxidEsales\Codeception\Admin\DataObject\AdminUser; use OxidEsales\Codeception\Admin\DataObject\AdminUserAddresses; use OxidEsales\Codeception\Admin\DataObject\AdminUserExtendedInfo; +use OxidEsales\Codeception\Module\Translation\Translator; use OxidEsales\EshopCommunity\Tests\Codeception\Support\AcceptanceTester; #[Group('admin')] @@ -197,6 +198,39 @@ public function updatePassword(AcceptanceTester $I): void ->login($userData['userLoginName'], $newPass); } + public function testChangeUserEmail(AcceptanceTester $I): void + { + $I->wantToTest('changing user email addresses with validation in admin'); + + $userData = Fixtures::get('existingUser'); + $guestUserData = Fixtures::get('existingGuestUser'); + $adminUserData = Fixtures::get('adminUser'); + $newEmail = 'example02@oxid-esales.dev'; + + $I->amGoingTo('login as admin and find the test user'); + $adminUsersPage = $I->loginAdmin() + ->openUsers() + ->findByUserName($userData['userLoginName']); + + $I->amGoingTo('try to change email to an existing guest user email'); + $adminUsersPage->updateUsername($guestUserData['userLoginName']); + $I->expect('to see an error message about existing user'); + $I->see(Translator::translate('EXCEPTION_USER_USEREXISTS')); + + $I->amGoingTo('try to change email to an existing admin email'); + $adminUsersPage->updateUsername($adminUserData['userLoginName']); + $I->expect('to see an error message about existing user'); + $I->see(Translator::translate('EXCEPTION_USER_USEREXISTS')); + + $I->amGoingTo('change user email to a new valid email address'); + $adminUsersPage->updateUsername($newEmail); + $I->expect('not to see any error message'); + $I->dontSee(Translator::translate('EXCEPTION_USER_USEREXISTS')); + + $I->amGoingTo('change the email back to the original one'); + $adminUsersPage->updateUsername($userData['userLoginName']); + } + private function createAdminTestUser( AcceptanceTester $I, AdminUser $user, diff --git a/tests/Codeception/Acceptance/UserAccountCest.php b/tests/Codeception/Acceptance/UserAccountCest.php index 459e5c0741..57b371d611 100644 --- a/tests/Codeception/Acceptance/UserAccountCest.php +++ b/tests/Codeception/Acceptance/UserAccountCest.php @@ -117,36 +117,60 @@ public function sendUserPasswordReminder(AcceptanceTester $I): void #[group('myAccount')] public function changeUserEmailInBillingAddress(AcceptanceTester $I): void { - $I->wantTo('change user email in my account'); + $I->wantToTest('changing user email address in my account billing information'); $userData = $this->getExistingUserData(); + $guestUserData = $this->getExistingGuestUserData(); + $adminUserData = $this->getAdminUserData(); + $newEmail = 'example02@oxid-esales.dev'; + $I->amGoingTo('login to shop and navigate to billing address form'); $userAddressPage = $I->openShop() ->loginUser($userData['userLoginName'], $userData['userPassword']) ->openAccountPage() ->openUserAddressPage() ->openUserBillingAddressForm(); + + $I->expect('to see default country and state selections'); $I->see('Germany', $userAddressPage->billCountryId); $I->see(Translator::translate('PLEASE_SELECT_STATE'), $userAddressPage->billStateId); - //change user password - $userAddressPage = $userAddressPage->changeEmail('example02@oxid-esales.dev', $userData['userPassword']); + $I->amGoingTo('try to change email to an existing guest user email'); + $userAddressPage->changeEmail($guestUserData['userLoginName'], $userData['userPassword']); + $I->expect('to see an error message about existing user'); + $I->see(Translator::translate('ERROR_MESSAGE_USER_USEREXISTS')); + + $I->amGoingTo('try to change email to an existing admin email'); + $userAddressPage->openUserBillingAddressForm() + ->changeEmail($adminUserData['userLoginName'], $userData['userPassword']); + $I->expect('to see an error message about existing user'); + $I->see(Translator::translate('ERROR_MESSAGE_USER_USEREXISTS')); + $I->amGoingTo('change user email to a new valid email address'); + $userAddressPage = $userAddressPage->openUserBillingAddressForm() + ->changeEmail($newEmail, $userData['userPassword']); + $I->expect('not to see any error messages'); + $I->dontSee(Translator::translate('ERROR_MESSAGE_USER_USEREXISTS')); $I->dontSee(Translator::translate('COMPLETE_MARKED_FIELDS')); - $userAddressPage = $userAddressPage->logoutUser(); - //try to login with old and new email address + $I->amGoingTo('logout and try to login with the old email address'); + $userAddressPage = $userAddressPage->logoutUser(); $userAddressPage->loginUser($userData['userLoginName'], $userData['userPassword']); + $I->expect('to see login form and error message'); $I->see(Translator::translate('LOGIN')); $I->see(Translator::translate('ERROR_MESSAGE_USER_NOVALIDLOGIN'), $userAddressPage->badLoginError); - //login with new email address - $userAddressPage->loginUser('example02@oxid-esales.dev', $userData['userPassword']); + + $I->amGoingTo('login with the new email address'); + $userAddressPage->loginUser($newEmail, $userData['userPassword']); + $I->expect('to be logged in successfully'); $I->dontSee(Translator::translate('LOGIN')); - //change password back to original + $I->amGoingTo('change the email back to the original one'); $userAddressPage->openUserBillingAddressForm() ->changeEmail('example_test@oxid-esales.dev', $userData['userPassword']) ->logoutUser(); + $I->expect('to be logged out'); + $I->see(Translator::translate('LOGIN')); } #[group('myAccount')] @@ -282,6 +306,16 @@ private function getExistingUserData() return Fixtures::get('existingUser'); } + private function getExistingGuestUserData(): array + { + return Fixtures::get('existingGuestUser'); + } + + private function getAdminUserData(): array + { + return Fixtures::get('adminUser'); + } + private function getUserData(string $userId): array { return [ diff --git a/tests/Codeception/Support/Data/dump.sql b/tests/Codeception/Support/Data/dump.sql index fa96a60575..e7e3dc7aed 100644 --- a/tests/Codeception/Support/Data/dump.sql +++ b/tests/Codeception/Support/Data/dump.sql @@ -50,7 +50,8 @@ REPLACE INTO `oxobject2category` (`OXID`, `OXOBJECTID`, `O REPLACE INTO `oxuser` (`OXID`, `OXACTIVE`, `OXRIGHTS`, `OXSHOPID`, `OXUSERNAME`, `OXPASSWORD`, `OXPASSSALT`, `OXCUSTNR`, `OXUSTID`, `OXCOMPANY`, `OXFNAME`, `OXLNAME`, `OXSTREET`, `OXSTREETNR`, `OXADDINFO`, `OXCITY`, `OXCOUNTRYID`, `OXZIP`, `OXFON`, `OXFAX`, `OXSAL`, `OXBONI`, `OXCREATE`, `OXREGISTER`, `OXPRIVFON`, `OXMOBFON`, `OXBIRTHDATE`) VALUES ( 'testuser', 1, 'user', 1, 'example_test@oxid-esales.dev', 'c9dadd994241c9e5fa6469547009328a', '7573657275736572', 8, '', 'UserCompany šÄßüл', 'UserNamešÄßüл', 'UserSurnamešÄßüл', 'Musterstr.šÄßüл', '1', 'User additional info šÄßüл', 'Musterstadt šÄßüл', 'testcountry_de', '79098', '0800 111111', '0800 111112', 'Mr', 500, '2008-02-05 14:42:42', '2008-02-05 14:42:42', '0800 111113', '0800 111114', '1980-01-01'), - ('oxdefaultadmin', 1, 'malladmin', 1, 'admin@myoxideshop.com', '6cb4a34e1b66d3445108cd91b67f98b9','6631386565336161636139613634663766383538633566623662613036636539', 1, '', 'Your Company Name', 'John', 'Doe', 'Maple Street', '2425', '', 'Any City', 'testcountry_de', '9041', '217-8918712', '217-8918713', 'MR', 1000, '2003-01-01 00:00:00', '2003-01-01 00:00:00', '', '', '0000-00-00'); + ( 'testguest', 1, 'user', 1, 'example_guest@oxid-esales.dev', '', '', 9, '', 'UserCompany šÄßüл', 'UserNamešÄßüл', 'UserSurnamešÄßüл', 'Musterstr.šÄßüл', '1', 'User additional info šÄßüл', 'Musterstadt šÄßüл', 'testcountry_de', '79098', '0800 111111', '0800 111112', 'Mr', 500, '2008-02-05 14:42:42', '2008-02-05 14:42:42', '0800 111113', '0800 111114', '1980-01-01'), + ('oxdefaultadmin', 1, 'malladmin', 1, 'admin@myoxideshop.com', '6cb4a34e1b66d3445108cd91b67f98b9','6631386565336161636139613634663766383538633566623662613036636539', 1, '', 'Your Company Name', 'John', 'Doe', 'Maple Street', '2425', '', 'Any City', 'testcountry_de', '9041', '217-8918712', '217-8918713', 'MR', 1000, '2003-01-01 00:00:00', '2003-01-01 00:00:00', '', '', '1970-01-01'); #object2Group REPLACE INTO `oxobject2group` (`OXID`, `OXSHOPID`, `OXOBJECTID`, `OXGROUPSID`) VALUES diff --git a/tests/Codeception/Support/Data/user.php b/tests/Codeception/Support/Data/user.php index a090dfacc3..b26d226cb6 100644 --- a/tests/Codeception/Support/Data/user.php +++ b/tests/Codeception/Support/Data/user.php @@ -15,6 +15,13 @@ 'userName' => 'UserNamešÄßüл', 'userLastName' => 'UserSurnamešÄßüл', ], + 'existingGuestUser' => [ + 'userId' => 'testguest', + 'userLoginName' => 'example_guest@oxid-esales.dev', + 'userPassword' => '', + 'userName' => 'UserNamešÄßüл', + 'userLastName' => 'UserSurnamešÄßüл', + ], 'adminUser' => [ 'userId' => 'admin', 'userLoginName' => 'admin@myoxideshop.com', diff --git a/tests/Integration/Application/Component/UserComponentTest.php b/tests/Integration/Application/Component/UserComponentTest.php index 1cac4948d8..09bd1d72b6 100644 --- a/tests/Integration/Application/Component/UserComponentTest.php +++ b/tests/Integration/Application/Component/UserComponentTest.php @@ -19,6 +19,8 @@ final class UserComponentTest extends IntegrationTestCase { private string $userName = 'some-users-email@example.com'; + private string $password = 'password123'; + public function setUp(): void { @@ -157,6 +159,72 @@ public function testChangeUserWithExtraFormDataWillNotUpdateNonAddressUserFields $this->assertNotEquals($wrongUpdateExpiration, $userData['OXUPDATEEXP']); } + public function testChangeUserEmailValidation(): void + { + $existingUser = $this->createUser($this->userName); + $guestUser = $this->createUser('guest@example.com', true); + $secondUser = $this->createUser('second-user@example.com'); + + $this->assertEmailChangeRejected($secondUser['email'], $existingUser['email']); + $this->assertEmailChangeRejected($secondUser['email'], $guestUser['email']); + $this->assertEmailChangeAccepted($secondUser['email'], 'new-unique-email@example.com'); + } + + private function createUser(string $email, bool $isGuest = false): array + { + $userData = $this->getUserFormData(); + + if ($isGuest) { + unset($userData['lgn_pwd'], $userData['lgn_pwd2']); + } + + $userData['oxuser__oxusername'] = $email; + $userData['lgn_usr'] = $email; + $_POST = $userData; + + $this->getUserComponent()->createUser(); + + return [ + 'email' => $email + ]; + } + + private function assertEmailChangeRejected(string $currentEmail, string $newEmail): void + { + $this->userName = $currentEmail; + + $requestData = $this->prepareChangeUserRequest($newEmail); + $_POST = $requestData; + + $result = $this->getUserComponent()->changeuser_testvalues(); + + $this->assertNull($result); + $userData = $this->fetchUserData(); + $this->assertEquals($currentEmail, $userData['OXUSERNAME']); + } + + private function assertEmailChangeAccepted(string $currentEmail, string $newEmail): void + { + $this->userName = $currentEmail; + + $requestData = $this->prepareChangeUserRequest($newEmail); + $_POST = $requestData; + + $result = $this->getUserComponent()->changeuser_testvalues(); + + $this->assertEquals('account_user', $result); + $this->userName = $newEmail; + $userData = $this->fetchUserData(); + $this->assertEquals($newEmail, $userData['OXUSERNAME']); + } + + private function prepareChangeUserRequest(string $newEmail): array + { + $requestData = $this->getUserFormData(); + $requestData['invadr']['oxuser__oxusername'] = $newEmail; + return $requestData; + } + private function mockSession(): void { $sessionMock = $this->createPartialMock(Session::class, ['checkSessionChallenge']); @@ -168,15 +236,14 @@ private function mockSession(): void private function getUserFormData(): array { - $password = uniqid('some-string-', true); - return [ 'oxuser__oxfname' => uniqid('first-name-', true), 'oxuser__oxlname' => uniqid('last-name-', true), 'oxuser__oxusername' => $this->userName, 'lgn_usr' => $this->userName, - 'lgn_pwd' => $password, - 'lgn_pwd2' => $password, + 'lgn_pwd' => $this->password, + 'lgn_pwd2' => $this->password, + 'user_password' => $this->password, 'invadr' => [ 'oxuser__oxfname' => uniqid('first-name-', true), 'oxuser__oxlname' => uniqid('last-name-', true), @@ -186,9 +253,19 @@ private function getUserFormData(): array 'oxuser__oxcity' => 'Freiburg', 'oxuser__oxcountryid' => 'a7c40f631fc920687.20179984', ], + 'deladr' => [ + 'oxaddress__oxfname' => uniqid('del-first-name-', true), + 'oxaddress__oxlname' => uniqid('del-last-name-', true), + 'oxaddress__oxstreet' => uniqid('del-street-', true), + 'oxaddress__oxstreetnr' => 123, + 'oxaddress__oxzip' => 123, + 'oxaddress__oxcity' => 'Freiburg', + 'oxaddress__oxcountryid' => 'a7c40f631fc920687.20179984', + ] ]; } + private function getUserComponent(): UserComponent { $userComponent = oxNew(UserComponent::class);