Skip to content

Commit

Permalink
OXDEV-8682 Add subscriber to detect password change
Browse files Browse the repository at this point in the history
  • Loading branch information
TitaKoleva committed Sep 11, 2024
1 parent 09f2134 commit e5c9f55
Show file tree
Hide file tree
Showing 5 changed files with 266 additions and 0 deletions.
4 changes: 4 additions & 0 deletions services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ services:
class: OxidEsales\GraphQL\Base\Event\Subscriber\UserDeleteSubscriber
tags: [ 'kernel.event_subscriber' ]

OxidEsales\GraphQL\Base\Event\Subscriber\PasswordChangeSubscriber:
class: OxidEsales\GraphQL\Base\Event\Subscriber\PasswordChangeSubscriber
tags: [ 'kernel.event_subscriber' ]

OxidEsales\GraphQL\Base\Event\Subscriber\BeforeTokenCreationSubscriber:
tags: [ 'kernel.event_subscriber' ]

Expand Down
104 changes: 104 additions & 0 deletions src/Event/Subscriber/PasswordChangeSubscriber.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<?php

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

declare(strict_types=1);

namespace OxidEsales\GraphQL\Base\Event\Subscriber;

use OxidEsales\Eshop\Application\Model\User;
use OxidEsales\EshopCommunity\Internal\Transition\ShopEvents\AfterModelUpdateEvent;
use OxidEsales\EshopCommunity\Internal\Transition\ShopEvents\BeforeModelUpdateEvent;
use OxidEsales\GraphQL\Base\Service\UserModelService;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Contracts\EventDispatcher\Event;

class PasswordChangeSubscriber implements EventSubscriberInterface
{
/**
* Whether the password had been changed.
*
* @var string|null
*/
protected ?string $userIdWithChangedPwd = null;

public function __construct(private readonly UserModelService $userModelService)
{
}

/**
* Handle ApplicationModelChangedEvent.
*
* @param Event $event Event to be handled
*/
public function handleBeforeUpdate(Event $event): Event
{
/** @phpstan-ignore-next-line method.notFound */
$model = $event->getModel();

if (!$model instanceof User || !$model->getId()) {
return $event;
}

$newPassword = $model->getFieldData('oxpassword');
if (!$this->userModelService->isPasswordChanged($model->getId(), $newPassword)) {
return $event;
}

$this->userIdWithChangedPwd = $model->getId();

return $event;
}

/**
* Handle ApplicationModelChangedEvent.
*
* @param Event $event Event to be handled
*/
public function handleAfterUpdate(Event $event): Event
{
/** @phpstan-ignore-next-line method.notFound */
$model = $event->getModel();

if (!$model instanceof User) {
return $event;
}

if ($model->getId() !== $this->userIdWithChangedPwd || !$this->userIdWithChangedPwd) {
return $event;
}

//todo: delete tokens here

return $event;
}

/**
* Returns an array of event names this subscriber wants to listen to.
*
* The array keys are event names and the value can be:
*
* * The method name to call (priority defaults to 0)
* * An array composed of the method name to call and the priority
* * An array of arrays composed of the method names to call and respective
* priorities, or 0 if unset
*
* For instance:
*
* * array('eventName' => 'methodName')
* * array('eventName' => array('methodName', $priority))
* * array('eventName' => array(array('methodName1', $priority), array('methodName2')))
*
* @return array<class-string,string>
*/
public static function getSubscribedEvents(): array
{
return [
BeforeModelUpdateEvent::class => 'handleBeforeUpdate',
AfterModelUpdateEvent::class => 'handleAfterUpdate'
];
}
}
34 changes: 34 additions & 0 deletions src/Service/UserModelService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

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

declare(strict_types=1);

namespace OxidEsales\GraphQL\Base\Service;

use OxidEsales\GraphQL\Base\Infrastructure\Legacy;

/**
* User model service
*/
class UserModelService
{
public function __construct(
private readonly Legacy $legacyInfrastructure,
) {
}

public function isPasswordChanged(string $userId, ?string $passwordNew): bool
{
$userModel = $this->legacyInfrastructure->getUserModel($userId);
$currentPassword = $userModel->getFieldData('oxpassword');
if (!$passwordNew || !$currentPassword) {
return false;
}

return $currentPassword !== $passwordNew;
}
}
71 changes: 71 additions & 0 deletions tests/Integration/Service/UserModelServiceTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php

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

declare(strict_types=1);

namespace OxidEsales\GraphQL\Base\Tests\Integration\Service;

use OxidEsales\Eshop\Application\Model\User as UserModel;
use OxidEsales\GraphQL\Base\Infrastructure\Legacy;
use OxidEsales\GraphQL\Base\Service\UserModelService;
use OxidEsales\GraphQL\Base\Tests\Integration\TestCase;
use PHPUnit\Framework\Attributes\CoversClass;

#[CoversClass(UserModelService::class)]
class UserModelServiceTest extends TestCase
{
public function testIsPasswordChangedOnNewPassword(): void
{
$userId = uniqid();
$password = uniqid();

$user = $this->getUserModelStub($userId, $password);

$sut = new UserModelService(
legacyInfrastructure: $this->getLegacyMock($user),
);

$this->assertTrue($sut->isPasswordChanged($userId, 'mynewpwd'));
}

public function testIsPasswordChangedOnSamePassword(): void
{
$userId = uniqid();
$password = uniqid();

$user = $this->getUserModelStub($userId, $password);

$sut = new UserModelService(
legacyInfrastructure: $this->getLegacyMock($user),
);

$this->assertFalse($sut->isPasswordChanged($userId, $password));
}

protected function getUserModelStub(string $id, string $password): UserModel
{
$userModel = oxNew(UserModel::class);
$userModel->assign([
'oxid' => $id,
'oxpassword' => $password,
]);

return $userModel;
}

private function getLegacyMock(UserModel $user): Legacy
{
$legacyInfrastructureMock = $this->getMockBuilder(Legacy::class)
->disableOriginalConstructor()
->getMock();

$legacyInfrastructureMock->method('getUserModel')
->willReturn($user);

return $legacyInfrastructureMock;
}
}
53 changes: 53 additions & 0 deletions tests/Unit/Event/Subscriber/PasswordChangeSubscriberTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

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

declare(strict_types=1);

namespace OxidEsales\GraphQL\Base\Tests\Unit\Event\Subscriber;

use OxidEsales\EshopCommunity\Internal\Transition\ShopEvents\AfterModelUpdateEvent;
use OxidEsales\EshopCommunity\Internal\Transition\ShopEvents\BeforeModelUpdateEvent;
use OxidEsales\GraphQL\Base\Event\Subscriber\PasswordChangeSubscriber;
use OxidEsales\GraphQL\Base\Service\UserModelService;
use OxidEsales\GraphQL\Base\Tests\Unit\BaseTestCase;

class PasswordChangeSubscriberTest extends BaseTestCase
{
public function testSubscribedEventsConfiguration(): void
{
$sut = $this->getSut();
$configuration = $sut->getSubscribedEvents();

$this->assertTrue(array_key_exists(BeforeModelUpdateEvent::class, $configuration));
$this->assertTrue(array_key_exists(AfterModelUpdateEvent::class, $configuration));
$this->assertTrue($configuration[BeforeModelUpdateEvent::class] === 'handleBeforeUpdate');
$this->assertTrue($configuration[AfterModelUpdateEvent::class] === 'handleAfterUpdate');
}

public function testHandleBeforeUpdateReturnsOriginalEvent(): void
{
$sut = $this->getSut();

$eventStub = $this->createStub(BeforeModelUpdateEvent::class);
$this->assertSame($eventStub, $sut->handleBeforeUpdate($eventStub));
}

public function testHandleAfterUpdateReturnsOriginalEvent(): void
{
$sut = $this->getSut();

$eventStub = $this->createStub(AfterModelUpdateEvent::class);
$this->assertSame($eventStub, $sut->handleAfterUpdate($eventStub));
}

public function getSut(UserModelService $userModelService = null): PasswordChangeSubscriber
{
return new PasswordChangeSubscriber(
userModelService: $userModelService ?? $this->createStub(UserModelService::class)
);
}
}

0 comments on commit e5c9f55

Please sign in to comment.