$cache[$key] = empty($arr) ? NULL : $arr; return $cache[$key]; } // 门户 获取需要在频道显示的栏目主题数据 function portal_channel_thread($fid) { global $forumlist; if (empty($fid)) return NULL; $orderby = array('tid' => 1); $page = 1; // 遍历所有在频道显示内容的栏目 $category_forumlist = channel_category($fid); $arrlist = array(); $forum_tids = array(); $tidlist = array(); if ($category_forumlist) { foreach ($category_forumlist as &$_forum) { // 频道显示数据 $arrlist['list'][$_forum['fid']] = array( 'fid' => $_forum['fid'], 'name' => $_forum['name'], 'rank' => $_forum['rank'], 'type' => $_forum['type'], 'url' => $_forum['url'], 'channel_new' => $_forum['channel_new'], ); $forum_thread = thread_tid__find(array('fid' => $_forum['fid']), $orderby, $page, $_forum['channel_new'], 'tid', array('tid')); // 最新信息按栏目分组 foreach ($forum_thread as $key => $_thread) { $forum_tids[$key] = $_thread; } unset($forum_thread); } $tidlist += $forum_tids; } unset($category_forumlist); // 获取属性对应的tid集合 list($flaglist, $flagtids) = flag_thread_by_fid($fid); empty($flagtids) || $tidlist += $flagtids; unset($flagtids); // 频道置顶 $stickylist = sticky_list_thread($fid); empty($stickylist) || $tidlist += $stickylist; // 在这之前合并所有二维数组 tid值为键/array('tid值' => tid值) $tidarr = arrlist_values($tidlist, 'tid'); // 在这之前使用$tidarr = array_merge($tidarr, $arr)前合并所有一维数组 tid/array(1,2,3) if (empty($tidarr)) { $arrlist['list'] = isset($arrlist['list']) ? array_multisort_key($arrlist['list'], 'rank', FALSE, 'fid') : array(); return $arrlist; } $tidarr = array_unique($tidarr); $pagesize = count($tidarr); // 遍历获取的所有tid主题 $threadlist = well_thread_find_asc($tidarr, $pagesize); // 遍历时为升序,翻转为降序 $threadlist = array_reverse($threadlist); foreach ($threadlist as &$_thread) { // 各栏目最新内容 isset($forum_tids[$_thread['tid']]) AND $arrlist['list'][$_thread['fid']]['news'][$_thread['tid']] = $_thread; // 全站置顶内容 isset($stickylist[$_thread['tid']]) AND $arrlist['sticky'][$_thread['tid']] = $_thread; // 首页属性主题 if (!empty($flaglist)) { foreach ($flaglist as $key => $val) { if (isset($val['tids']) && in_array($_thread['tid'], $val['tids'])) { $arrlist['flaglist'][$key][array_search($_thread['tid'], $val['tids'])] = $_thread; ksort($arrlist['flaglist'][$key]); $arrlist['flag'][$_thread['tid']] = $_thread; } } } } unset($threadlist); if (isset($arrlist['sticky'])) { $i = 0; foreach ($arrlist['sticky'] as &$val) { ++$i; $val['i'] = $i; } } if (isset($arrlist['flag'])) { $i = 0; foreach ($arrlist['flag'] as &$val) { ++$i; $val['i'] = $i; } } if (isset($arrlist['flaglist'])) { foreach ($arrlist['flaglist'] as &$val) { $i = 0; foreach ($val as &$v) { ++$i; $v['i'] = $i; } } } isset($arrlist['list']) AND $arrlist['list'] = array_multisort_key($arrlist['list'], 'rank', FALSE, 'fid'); return $arrlist; } ?>php - Should I hash the password in a fixture if it's stored as Argon2i in the database? - Stack Overflow
最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

php - Should I hash the password in a fixture if it's stored as Argon2i in the database? - Stack Overflow

programmeradmin0浏览0评论

I'm writing tests in Symfony and using fixtures to load test data into the database. One of the entities I need to create is a user, which has a password field. In production, passwords are hashed using Argon2id before being stored in the database.

When defining the fixture, should I manually hash the password using the same algorithm, or can I store it as plain text since it's just for testing purposes? This encoder is defined in Yaml security

If it’s still necessary, am I doing it right? I have a problem when passing an encoder parameter inside a fixture located in an abstract class

Fixture

class AuthUserFixture extends Fixture
{
    private UserPasswordEncoderInterface $encoder;

    public function __construct(UserPasswordEncoderInterface $encoder) {}

    /**
     * @inheritDoc
     */
    public function load(ObjectManager $manager): void {
        $this->loadUsers($manager);
    }

    private function loadUsers(ObjectManager $manager): void
    {
        foreach ($this->getUserData() as [
            $username, $usernameCanonical, $email, $emailCanonical, $enabled, $password, $roles
        ]) {
            $user = new User();
            $user->setUsername($username);
            $user->setUsernameCanonical($usernameCanonical);
            $user->setEmail($email);
            $user->setEmailCanonical($emailCanonical);
            $user->setEnabled($enabled);
            $user->setRoles($roles);

            $password = $this->encoder->encodePassword($user, $password);
            $user->setPassword($password);

            $manager->persist($user);

            $this->addReference($username, $user);
        }
        $manager->flush();
    }

    private function getUserData(): array
    {
        // $userData = [$username, $usernameCanonical, $email, $emailCanonical, $enabled, $password, $roles];
        return [
            [
                'User',
                'user',
                '[email protected]',
                '[email protected]',
                true,
                '7jKuXn97ReDK',
                [User::ADMIN_DASHBOARD_ACCESS_ROLES_HAYSTACK]
            ],
        ];
    }
}

Abstract Class

/** @var KernelBrowser $client */
protected KernelBrowser $client;

/** @var EntityManagerInterface */
private  $entityManager;

public static function setUpBeforeClass(): void
{
    $kernel = static::createKernel();
    $kernel->boot();
    $entityManager = $kernel->getContainer()->get('doctrine')->getManager();

    $loader = new Loader();
    foreach (self::getFixtures() as $fixture) {
        $loader->addFixture($fixture);
    }

    $purger = new ORMPurger();
    $purger->setPurgeMode(ORMPurger::PURGE_MODE_DELETE);

    $executor = new ORMExecutor($entityManager, $purger);
    $executor->execute($loader->getFixtures());
}

protected function setUp(): void
{
    parent::setUp();

    $this->client = static::createClient();
    $this->client->disableReboot();

    $this->client->setServerParameter('HTTP_HOST', '172.19.0.1');

    $this->entityManager = $this->client->getContainer()->get('doctrine.orm.entity_manager');
    $this->entityManager->beginTransaction();
    $this->entityManager->getConnection()->setAutoCommit(false);
}

protected function tearDown(): void
{
    if ($this->entityManager->getConnection()->isTransactionActive()) {
        try {
            $this->entityManager->rollback();
        } catch (\Exception $e) {
            echo "Rollback error: " . $e->getMessage();
        }
    }

    $this->entityManager->clear();
    $this->entityManager->close();

    parent::tearDown();
}

protected function apiLoginAs(string $username, string $password): void
{
    $expectedBase64 = base64_encode("$username:$password");
    $authHeader = 'Basic ' . $expectedBase64;

    $this->client->setServerParameter('HTTP_Authorization', $authHeader);
}

private static function getFixtures(): iterable
{
    return [
        new AuthUserFixture(),
    ];
}

I don’t fully understand whether the implementation is correct. And how to correctly pass the parameter correctly to

private static function getFixtures(): iterable
{
    return [
        new AuthUserFixture($encoder),
    ];
}

Because when passing this parameter, an error is displayed to me

Notice: Undefined variable: encoder

How to initialize it? and do I even need a constructor in fixtures?

I'm writing tests in Symfony and using fixtures to load test data into the database. One of the entities I need to create is a user, which has a password field. In production, passwords are hashed using Argon2id before being stored in the database.

When defining the fixture, should I manually hash the password using the same algorithm, or can I store it as plain text since it's just for testing purposes? This encoder is defined in Yaml security

If it’s still necessary, am I doing it right? I have a problem when passing an encoder parameter inside a fixture located in an abstract class

Fixture

class AuthUserFixture extends Fixture
{
    private UserPasswordEncoderInterface $encoder;

    public function __construct(UserPasswordEncoderInterface $encoder) {}

    /**
     * @inheritDoc
     */
    public function load(ObjectManager $manager): void {
        $this->loadUsers($manager);
    }

    private function loadUsers(ObjectManager $manager): void
    {
        foreach ($this->getUserData() as [
            $username, $usernameCanonical, $email, $emailCanonical, $enabled, $password, $roles
        ]) {
            $user = new User();
            $user->setUsername($username);
            $user->setUsernameCanonical($usernameCanonical);
            $user->setEmail($email);
            $user->setEmailCanonical($emailCanonical);
            $user->setEnabled($enabled);
            $user->setRoles($roles);

            $password = $this->encoder->encodePassword($user, $password);
            $user->setPassword($password);

            $manager->persist($user);

            $this->addReference($username, $user);
        }
        $manager->flush();
    }

    private function getUserData(): array
    {
        // $userData = [$username, $usernameCanonical, $email, $emailCanonical, $enabled, $password, $roles];
        return [
            [
                'User',
                'user',
                '[email protected]',
                '[email protected]',
                true,
                '7jKuXn97ReDK',
                [User::ADMIN_DASHBOARD_ACCESS_ROLES_HAYSTACK]
            ],
        ];
    }
}

Abstract Class

/** @var KernelBrowser $client */
protected KernelBrowser $client;

/** @var EntityManagerInterface */
private  $entityManager;

public static function setUpBeforeClass(): void
{
    $kernel = static::createKernel();
    $kernel->boot();
    $entityManager = $kernel->getContainer()->get('doctrine')->getManager();

    $loader = new Loader();
    foreach (self::getFixtures() as $fixture) {
        $loader->addFixture($fixture);
    }

    $purger = new ORMPurger();
    $purger->setPurgeMode(ORMPurger::PURGE_MODE_DELETE);

    $executor = new ORMExecutor($entityManager, $purger);
    $executor->execute($loader->getFixtures());
}

protected function setUp(): void
{
    parent::setUp();

    $this->client = static::createClient();
    $this->client->disableReboot();

    $this->client->setServerParameter('HTTP_HOST', '172.19.0.1');

    $this->entityManager = $this->client->getContainer()->get('doctrine.orm.entity_manager');
    $this->entityManager->beginTransaction();
    $this->entityManager->getConnection()->setAutoCommit(false);
}

protected function tearDown(): void
{
    if ($this->entityManager->getConnection()->isTransactionActive()) {
        try {
            $this->entityManager->rollback();
        } catch (\Exception $e) {
            echo "Rollback error: " . $e->getMessage();
        }
    }

    $this->entityManager->clear();
    $this->entityManager->close();

    parent::tearDown();
}

protected function apiLoginAs(string $username, string $password): void
{
    $expectedBase64 = base64_encode("$username:$password");
    $authHeader = 'Basic ' . $expectedBase64;

    $this->client->setServerParameter('HTTP_Authorization', $authHeader);
}

private static function getFixtures(): iterable
{
    return [
        new AuthUserFixture(),
    ];
}

I don’t fully understand whether the implementation is correct. And how to correctly pass the parameter correctly to

private static function getFixtures(): iterable
{
    return [
        new AuthUserFixture($encoder),
    ];
}

Because when passing this parameter, an error is displayed to me

Notice: Undefined variable: encoder

How to initialize it? and do I even need a constructor in fixtures?

Share Improve this question edited Feb 6 at 14:38 AymDev 7,5544 gold badges33 silver badges57 bronze badges asked Feb 6 at 13:04 Єськіна Анна АндріївнаЄськіна Анна Андріївна 12 bronze badges New contributor Єськіна Анна Андріївна is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct. 7
  • Wouldn't storing the password in plain text make it impossible to use it properly? Why not store it in the same way as passwords of production users? – Nico Haase Commented Feb 6 at 13:12
  • "An error also occurred during its initialization." - if you need help to resolve that error, please share it – Nico Haase Commented Feb 6 at 13:12
  • Finally, wouldn't it be simpler to load all fixtures before running your tests, as in: completely independent? – Nico Haase Commented Feb 6 at 13:13
  • ArgumentCountError : Too few arguments to function App\Tests\DataFixtures\AuthUserFixture::__construct(), 0 passed in /home/yeskina-a/amazon-admin/releases/1/tests/AbstractTestClass/AbstractAuthenticatedTest.php on line 80 and exactly 1 expected But when I pass a parameter to the fixture, I get "Undefined variable '$encoder' ". How can I initialize correctly? or shouldn’t create a constructor at all – Єськіна Анна Андріївна Commented Feb 6 at 13:18
  • Please add all clarification to your question by editing it – Nico Haase Commented Feb 6 at 13:28
 |  Show 2 more comments

1 Answer 1

Reset to default 0

You should not mix your tests with data fixtures. Here the issue is that you instanciate the fixture class yourself in the getFixtures() method of your test class. This won't work as it needs the UserPasswordEncoderInterface service: you don't want to build it yourself and the most important part, you want to get the correct service (according to your configuration in security.yaml).

Plaintext passwords

Symfony allows to disabled password hashing for testing purposes. You are right that it is not necessary, and it will make your test suite run faster. You can find an example in the documentation. Here's what I'd suggest:

# apply this config only for the `test` environment
when@test:
    security:
        password_hashers:
            # replace with the correct FQCN for your `User` class
            'Your\UserClass\Fqcn':
                algorithm: plaintext

Testing dataset

It looks like you want to have a fixed dataset for every test you run. The DAMADoctrineTestBundle is built for that purpose and recommended in the Symfony documentation: install it and enable the PHPUnit extension.

As recommended (but you may easily miss that info), you can create a test bootstrapping file to automatically recreate the database and load your fixtures. First create the file (let's say tests/database.php):

<?php

use App\Kernel;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Component\Console\Input\ArrayInput;

$kernel = new Kernel('test', true);
$kernel->boot();

$application = new Application($kernel);
$application->setAutoExit(false);

// Deletes the testing database
$application->run(new ArrayInput([
    'command' => 'doctrine:database:drop',
    '--if-exists' => 1,
    '--force' => 1,
]));

// Creates the testing database
$application->run(new ArrayInput([
    'command' => 'doctrine:database:create',
]));

// Runs your Doctrine migrations
$application->run(new ArrayInput([
    'command' => 'doctrine:migrations:migrate',
    '--no-interaction' => 1,
]));

// Loads your fixtures
$application->run(new ArrayInput([
    'command' => 'doctrine:fixtures:load',
    '--no-interaction' => 1,
]));

Then include this file in the main PHPUnit bootstrap file tests/bootstrap.php:

// ...

// Database bootstrapping
require_once __DIR__ . '/database.php';

Separate dev/test databases

Make sure you have a specific database name for testing so it won't touch your development database in any way. You have 2 options:

  • set a database name suffix in the Doctrine configuration (should be enabled by default):

    # in doctrine.yaml
    when@test:
        doctrine:
            dbal:
                # "TEST_TOKEN" is typically set by ParaTest
                dbname_suffix: '_test%env(default::TEST_TOKEN)%'
    
  • set a custom database name in the PHPUnit configuration:

    <!-- in phpunit.xml.dist -->
    <phpunit
        ...
    >
        <php>
            <!-- ... -->
            <server name="DATABASE_URL" value="<!-- SET DESIRED VALUE HERE -->" />
        </php>
        <!-- ... -->
    
发布评论

评论列表(0)

  1. 暂无评论