在功能测试中为 doctrine onFlush 提供安全性
Make security available for doctrine onFlush within functional test
我实际上是在测试我的 api 代码:
- symfony 4
- api-platform
- FOS 用户
- 智威汤逊
我使用 codeption 进行测试,到目前为止一切正常。
对于几个实体,我触发了 onFlush doctrine 回调,当我在 React 中从我的前端应用程序进行身份验证时,它工作正常。
此时,我通过注入的安全组件在回调中获得了经过身份验证的用户。
然而,当通过代码接收做同样的事情时,即使触发了 onFlush,我也无法通过安全注入检索我的用户和令牌。
我尝试注入令牌,整个服务容器,none 都有效。
这是我的 OnFlush class:
{
/**
* @var Security
*/
private $security;
public function __construct(Security $security)
{
$this->security = $security;
}
public function onFlush(OnFlushEventArgs $args): void
{
$user = $this->security->getUser();
...
这里是我如何在密码测试中设置授权 header:
$I->haveHttpHeader('Authorization', 'Bearer ' . $token);
$I->sendPUT(
'/entity/uuid.json',
[
'attribute' => $value
]
);
我想让用户在回调中执行测试时拥有指定的令牌。
PS:在执行 PUT 测试之前,我用 GET 做了同样的事情,只是得到了相关的实体,当我删除 Authorization header 时,我确实得到了所有用户实体。好像只在回调中不行。
谢谢
经过大量研究,这显然是一个密码问题。
我最终使用 phpunit 进行了这个特定的测试,因为 codeception 无法在 doctrine 事件中加载服务容器。
如果您尝试编辑 services.yaml 文件并执行测试,它会在第一次运行时重新构建(重新缓存)服务容器。
但是一旦缓存,它总是return一个空容器(没有令牌存储、安全......)。
创建一个辅助方法来提供给用户也行不通,我会把代码留在这里以备不时之需:
/**
* Create user or administrator and set auth cookie to client
*
* @param string $user
* @param string $password
* @param bool $admin
*/
public function setAuth(string $user, string $password, bool $admin = false): void
{
/** @var Symfony $symfony */
try {
$symfony = $this->getModule('Symfony');
} catch (ModuleException $e) {
$this->fail('Unable to get module \'Symfony\'');
}
/** @var Doctrine2 $doctrine */
try {
$doctrine = $this->getModule('Doctrine2');
} catch (ModuleException $e) {
$this->fail('Unable to get module \'Doctrine2\'');
}
$user = $doctrine->grabEntityFromRepository(User::class, [
'username' => $user
]);
$token = new UsernamePasswordToken($user, null, 'main', $user->getRoles());
$symfony->grabService('security.token_storage')->setToken($token);
/** @var Session $session */
$session = $symfony->grabService('session');
$session->set('_security_main', serialize($token));
$session->save();
$cookie = new Cookie($session->getName(), $session->getId());
$symfony->client->getCookieJar()->set($cookie);
}
使用以下代码创建一个 phpunit 测试就可以很好地完成工作:
/**
* @param string $method
* @param string $url
* @param array $content
* @param bool $authorization
*/
protected static function performRequest (string $method, string $url, array $content = [], $authorization = false): void
{
$headers = [
'CONTENT_TYPE' => 'application/json',
];
if ($authorization)
{
$headers = array_merge($headers, [
'HTTP_AUTHORIZATION' => 'Bearer ' . self::$token
]);
}
self::$client->request(
$method,
'/api/' . $url,
[],
[],
$headers,
json_encode($content)
);
}
我遇到了完全相同的问题。这是我的解决方案:
<?php
use Codeception\Stub;
use Codeception\Module\Doctrine2;
use Symfony\Component\Security\Core\User\User;
use Symfony\Component\Security\Core\Security;
class YourEventSubscriberCest
{
/**
* @var Security
*/
protected Security $security;
/**
* @param FunctionalTester $I
*/
public function _before(FunctionalTester $I, Doctrine2 $doctrine2)
{
$user = new User();
$security = Stub::makeEmpty(Security::class, [
'getUser' => $user,
]);
$backup = $this->replaceSecurity($doctrine2, $security);
if ($backup instanceof Security) {
$this->security = $backup;
}
}
public function _after(FunctionalTester $I, Doctrine2 $doctrine2)
{
$this->replaceSecurity($doctrine2, $this->security);
}
protected function replaceSecurity(Doctrine2 $doctrine2, object $newSecurity): ?object
{
$listeners = $doctrine2->_getEntityManager()->getEventManager()->getListeners('onFlush');
foreach ($listeners as $listener) {
if ($listener instanceof YourEventSubscriber) {
$reflection = new \ReflectionObject($listener);
$property = $reflection->getProperty('security');
$property->setAccessible(true);
$oldSecurity = $property->getValue($listener);
$property->setValue($listener, $newSecurity);
return $oldSecurity;
}
}
}
}
我实际上是在测试我的 api 代码:
- symfony 4
- api-platform
- FOS 用户
- 智威汤逊
我使用 codeption 进行测试,到目前为止一切正常。
对于几个实体,我触发了 onFlush doctrine 回调,当我在 React 中从我的前端应用程序进行身份验证时,它工作正常。
此时,我通过注入的安全组件在回调中获得了经过身份验证的用户。
然而,当通过代码接收做同样的事情时,即使触发了 onFlush,我也无法通过安全注入检索我的用户和令牌。
我尝试注入令牌,整个服务容器,none 都有效。
这是我的 OnFlush class:
{
/**
* @var Security
*/
private $security;
public function __construct(Security $security)
{
$this->security = $security;
}
public function onFlush(OnFlushEventArgs $args): void
{
$user = $this->security->getUser();
...
这里是我如何在密码测试中设置授权 header:
$I->haveHttpHeader('Authorization', 'Bearer ' . $token);
$I->sendPUT(
'/entity/uuid.json',
[
'attribute' => $value
]
);
我想让用户在回调中执行测试时拥有指定的令牌。
PS:在执行 PUT 测试之前,我用 GET 做了同样的事情,只是得到了相关的实体,当我删除 Authorization header 时,我确实得到了所有用户实体。好像只在回调中不行。
谢谢
经过大量研究,这显然是一个密码问题。
我最终使用 phpunit 进行了这个特定的测试,因为 codeception 无法在 doctrine 事件中加载服务容器。
如果您尝试编辑 services.yaml 文件并执行测试,它会在第一次运行时重新构建(重新缓存)服务容器。
但是一旦缓存,它总是return一个空容器(没有令牌存储、安全......)。
创建一个辅助方法来提供给用户也行不通,我会把代码留在这里以备不时之需:
/**
* Create user or administrator and set auth cookie to client
*
* @param string $user
* @param string $password
* @param bool $admin
*/
public function setAuth(string $user, string $password, bool $admin = false): void
{
/** @var Symfony $symfony */
try {
$symfony = $this->getModule('Symfony');
} catch (ModuleException $e) {
$this->fail('Unable to get module \'Symfony\'');
}
/** @var Doctrine2 $doctrine */
try {
$doctrine = $this->getModule('Doctrine2');
} catch (ModuleException $e) {
$this->fail('Unable to get module \'Doctrine2\'');
}
$user = $doctrine->grabEntityFromRepository(User::class, [
'username' => $user
]);
$token = new UsernamePasswordToken($user, null, 'main', $user->getRoles());
$symfony->grabService('security.token_storage')->setToken($token);
/** @var Session $session */
$session = $symfony->grabService('session');
$session->set('_security_main', serialize($token));
$session->save();
$cookie = new Cookie($session->getName(), $session->getId());
$symfony->client->getCookieJar()->set($cookie);
}
使用以下代码创建一个 phpunit 测试就可以很好地完成工作:
/**
* @param string $method
* @param string $url
* @param array $content
* @param bool $authorization
*/
protected static function performRequest (string $method, string $url, array $content = [], $authorization = false): void
{
$headers = [
'CONTENT_TYPE' => 'application/json',
];
if ($authorization)
{
$headers = array_merge($headers, [
'HTTP_AUTHORIZATION' => 'Bearer ' . self::$token
]);
}
self::$client->request(
$method,
'/api/' . $url,
[],
[],
$headers,
json_encode($content)
);
}
我遇到了完全相同的问题。这是我的解决方案:
<?php
use Codeception\Stub;
use Codeception\Module\Doctrine2;
use Symfony\Component\Security\Core\User\User;
use Symfony\Component\Security\Core\Security;
class YourEventSubscriberCest
{
/**
* @var Security
*/
protected Security $security;
/**
* @param FunctionalTester $I
*/
public function _before(FunctionalTester $I, Doctrine2 $doctrine2)
{
$user = new User();
$security = Stub::makeEmpty(Security::class, [
'getUser' => $user,
]);
$backup = $this->replaceSecurity($doctrine2, $security);
if ($backup instanceof Security) {
$this->security = $backup;
}
}
public function _after(FunctionalTester $I, Doctrine2 $doctrine2)
{
$this->replaceSecurity($doctrine2, $this->security);
}
protected function replaceSecurity(Doctrine2 $doctrine2, object $newSecurity): ?object
{
$listeners = $doctrine2->_getEntityManager()->getEventManager()->getListeners('onFlush');
foreach ($listeners as $listener) {
if ($listener instanceof YourEventSubscriber) {
$reflection = new \ReflectionObject($listener);
$property = $reflection->getProperty('security');
$property->setAccessible(true);
$oldSecurity = $property->getValue($listener);
$property->setValue($listener, $newSecurity);
return $oldSecurity;
}
}
}
}