Files
myep/tests/Security/BpnAuthenticatorTest.php
T

164 lines
5.6 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Tests\Security;
use App\BusProNet\ApiClient;
use App\BusProNet\Model\CrmAttributes;
use App\BusProNet\Model\PersonalData;
use App\Entity\User;
use App\Security\BpnAuthenticator;
use App\Security\Crypt;
use App\Security\Role;
use App\Service\ProfileCompletenessChecker;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
/**
* Covers who may grant roles: BusPro backend users can edit their own CRM selections, so the
* import must not be a channel for privilege escalation.
*/
class BpnAuthenticatorTest extends TestCase
{
public function testNewAccountIsSeededWithTheImportableRolesOnly(): void
{
$persisted = null;
$authenticator = $this->authenticator(
$this->crmAttributes([Role::ADMIN, Role::TEAMER, Role::GROUPS_ADMIN], ['SSL', 'SSL']),
null,
$persisted,
);
$user = $this->loadUser($authenticator);
self::assertSame($persisted, $user);
self::assertSame(['ROLE_USER', Role::TEAMER], $user->getRoles());
self::assertSame(['SSL'], $user->getHotelCodes());
}
public function testExistingAccountKeepsThePrivilegedRolesAnAdministratorAssigned(): void
{
$existing = (new User('[email protected]'))
->setRoles([Role::TEAMER, Role::GROUPS_MANAGER])
->setHotelCodes(['DKS'])
;
$persisted = null;
$authenticator = $this->authenticator(
$this->crmAttributes([Role::ADMIN, Role::CUSTOMER], ['SSL']),
$existing,
$persisted,
);
$user = $this->loadUser($authenticator);
self::assertNull($persisted, 'an existing account must not be persisted again');
// ROLE_TEAMER is gone with its CRM selection, ROLE_ADMIN is still not honoured, and the
// administrator-granted ROLE_GROUPS_MANAGER survives.
self::assertSame(['ROLE_USER', Role::CUSTOMER, Role::GROUPS_MANAGER], $user->getRoles());
self::assertSame(['DKS'], $user->getHotelCodes(), 'hotel codes stay administrator-managed');
self::assertNotNull($user->getLastLoginAt(), 'the rest of the profile is still synced');
}
public function testRoleGainedInBusProIsGrantedOnLogin(): void
{
$existing = (new User('[email protected]'))->setRoles([Role::CUSTOMER]);
$persisted = null;
$authenticator = $this->authenticator(
$this->crmAttributes([Role::TEAMER], []),
$existing,
$persisted,
);
// The case myep-team depends on: somebody becomes a Teamer after their account exists.
self::assertSame(['ROLE_USER', Role::TEAMER], $this->loadUser($authenticator)->getRoles());
}
public function testAccountWithoutAnyRoleIsHealedOnLogin(): void
{
$existing = new User('[email protected]');
$persisted = null;
$authenticator = $this->authenticator(
$this->crmAttributes([Role::TEAMER], []),
$existing,
$persisted,
);
self::assertSame(['ROLE_USER', Role::TEAMER], $this->loadUser($authenticator)->getRoles());
}
/**
* @param string[] $roles
* @param string[] $hotelCodes
*/
private function crmAttributes(array $roles, array $hotelCodes): CrmAttributes
{
$attributes = new CrmAttributes();
$attributes->roles = $roles;
$attributes->hotelCodes = $hotelCodes;
return $attributes;
}
private function authenticator(CrmAttributes $crmAttributes, ?User $existing, ?User &$persisted): BpnAuthenticator
{
$personalData = new PersonalData();
$personalData->personId = 42;
$personalData->addressId = 4711;
$apiClient = $this->createMock(ApiClient::class);
$apiClient->method('getPersonalData')->willReturn($personalData);
$apiClient->method('getCrmAttributes')->willReturn($crmAttributes);
$repository = $this->createMock(EntityRepository::class);
$repository->method('findOneBy')->willReturn($existing);
$entityManager = $this->createMock(EntityManagerInterface::class);
$entityManager->method('getRepository')->willReturn($repository);
$entityManager
->method('persist')
->willReturnCallback(static function (object $entity) use (&$persisted): void {
$persisted = $entity;
})
;
$crypt = $this->createMock(Crypt::class);
$crypt->method('encrypt')->willReturn('encrypted');
$completenessChecker = $this->createMock(ProfileCompletenessChecker::class);
$completenessChecker->method('isComplete')->willReturn(true);
return new BpnAuthenticator(
$this->createMock(UrlGeneratorInterface::class),
$apiClient,
$entityManager,
$crypt,
$completenessChecker,
$this->createMock(LoggerInterface::class),
);
}
private function loadUser(BpnAuthenticator $authenticator): User
{
$request = new Request();
$request->request->set('_username', '[email protected]');
$request->request->set('_password', 'secret');
$badge = $authenticator->authenticate($request)->getBadge(UserBadge::class);
self::assertInstanceOf(UserBadge::class, $badge);
$user = $badge->getUser();
self::assertInstanceOf(User::class, $user);
return $user;
}
}