Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[10.x] Update fixture hash to match testing cost #6259

Merged
merged 4 commits into from
Oct 24, 2023
Merged

[10.x] Update fixture hash to match testing cost #6259

merged 4 commits into from
Oct 24, 2023

Conversation

timacdonald
Copy link
Member

@timacdonald timacdonald commented Oct 23, 2023

Matches the lower cost for unit testing.

@bastien-phi
Copy link

I think this should be updated with

'password' => Hash::make('password'),

Because changes from laravel/framework#48791 causes

   INFO  Seeding database.  


   RuntimeException 

  The hash does not match the configured hashing options.

  at vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php:1328
    1324▕             return Hash::make($value);
    1325▕         }
    1326▕ 
    1327▕         if (Hash::needsRehash($value)) {
  ➜ 1328▕             throw new \RuntimeException('The hash does not match the configured hashing options.');
    1329▕         }
    1330▕ 
    1331▕         return $value;
    1332▕     }

during database seeding

@driesvints
Copy link
Member

No, the entire idea is that we don't need to run the hasher for each seed operation which would slow down tests, etc. If this PR fails seeding because of laravel/framework#48791 then we might need to re-consider that @timacdonald?

@Jubeki
Copy link
Contributor

Jubeki commented Oct 23, 2023

What about the database seeding, where the cost should be 12 instead of 4?

@timacdonald
Copy link
Member Author

I've just pushed a change so that the lower cost is only used while running unit tests.

Comment on lines 25 to 27
'password' => App::runningUnitTests()
? '$2y$04$Pnnh9Ay6phg2joDy5EDzE.icSkpTHvQFew2kSGPK06PyUDwpBuORG' // "password"
: '$2y$12$Z/vhVO3e.UXKaG11EWgxc.EL7uej3Pi1M0Pq0orF5cbFGtyVh0V3C',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May I suggest if this gets accepted there should be comment as to why this; I bet ppl will be confused by this. I know I am when I look just the code, even knowing the intention of the PR :-}

@driesvints
Copy link
Member

Wait, now I'm a bit confused why this was a problem @bastien-phi? In what situation would the lower cost be an issue for you?

@bastien-phi
Copy link

The problem is not about a lower or higher cost, the problem comes from a de-synchronization between the provided hash and the configured bcrypt rounds parameters.

If the hash and the configuration do not match, the hashed cast will throw a RuntimeException.

Current implementation is correct but I agree with @mfn, it could be tricky to understand why the implementation is like that (especially in user-land code).

I stand by it could be cleaner to use Hash::make('password') but it comes with a cost :

  • ~ 0.8ms with round = 4 (during tests)
  • ~ 170ms with round = 12 (during seeds)

@driesvints
Copy link
Member

@bastien-phi no I mean why can't we just switch to $2y$04$Pnnh9Ay6phg2joDy5EDzE.icSkpTHvQFew2kSGPK06PyUDwpBuORG? What would break?

@bastien-phi
Copy link

Oh sorry I wasn't clear in my first message. If we define the hash directly with '$2y$04$Pnnh9Ay6phg2joDy5EDzE.icSkpTHvQFew2kSGPK06PyUDwpBuORG', it would cause a RuntimeException during database seeding because of the changes in laravel/framework#48791 as the rounds in the hash (4) will mismatch the rounds define in the application (12 by default).

@driesvints
Copy link
Member

driesvints commented Oct 23, 2023

@bastien-phi right, thanks.

@timacdonald would it be an idea to hardcode the rounds to 4 when running tests here:

https://github.com/laravel/laravel/blob/10.x/tests/CreatesApplication.php#L18

We used to do that in the past iirc. Then we can just define $2y$04$Pnnh9Ay6phg2joDy5EDzE.icSkpTHvQFew2kSGPK06PyUDwpBuORG in the factory and @mfn's correct remark of things being confusing would be resolved.

@bastien-phi
Copy link

@driesvints don't forget that the developers can change hash algorithm and use argon instead of bcrypt...

@driesvints
Copy link
Member

@bastien-phi in that case it’s best that they update the hash itself.

@valorin
Copy link
Contributor

valorin commented Oct 23, 2023

@timacdonald would it be an idea to hardcode the rounds to 4 when running tests here:
https://github.com/laravel/laravel/blob/10.x/tests/CreatesApplication.php#L18
We used to do that in the past iirc. Then we can just define $2y$04$Pnnh9Ay6phg2joDy5EDzE.icSkpTHvQFew2kSGPK06PyUDwpBuORG in the factory and @mfn's correct remark of things being confusing would be resolved.

It's defined in the phpunit.xml as an override to the env var:

<env name="BCRYPT_ROUNDS" value="4"/>

(https://github.com/laravel/laravel/blob/10.x/phpunit.xml#L22)

If the developer changes this value and things break, it should be obvious enough that what broke it. Same as with switching to Argon.

@driesvints
Copy link
Member

@valorin ah good call. In that case there shouldn’t be anything wrong with just using the rounds 4 version.

@bastien-phi
Copy link

What about

class UserFactory extends Factory
{
    public static string $password;

    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition(): array
    {
        return [
            'name' => fake()->name(),
            'email' => fake()->unique()->safeEmail(),
            'email_verified_at' => now(),
            'password' => static::$password ??= Hash::make('password'),

?

It brings simplicity and also makes sure that generated hash is consistent with current configuration (algorithm and parameters)

@taylorotwell
Copy link
Member

@bastien-phi updated to your suggestion

@taylorotwell taylorotwell merged commit 7fe97a1 into laravel:10.x Oct 24, 2023
3 checks passed
@timacdonald timacdonald deleted the cost branch October 27, 2023 00:02
christopheredjohnson pushed a commit to TheYGSGroup/laravel that referenced this pull request Jan 5, 2024
* Update fixture hash to match testing cost

* Conditionally use lower cost in tests

* use hash facade and memoize

* remove import

---------

Co-authored-by: Taylor Otwell <[email protected]>
@Softwaerewolf
Copy link

Even though this has already been merged, I wanted to leave a note that using Hash::make('password') in UserFactory was necessary for tests to pass when using pgsql.

It would treat the hard coded hash as plain text input and get rehash, then compare the rehashed value to the hardcoded hash and fail. Only using Hash::make or providing an unhashed value allowed the test to pass.

Not sure if this points at some underlying inconsistency between mysql and pgsql drivers?

@valorin
Copy link
Contributor

valorin commented Jan 16, 2024

Sounds like you've got hashed casting enabled on the model - the database driver shouldn't be doing any hashing.

@Softwaerewolf
Copy link

Sounds like you've got hashed casting enabled on the model - the database driver shouldn't be doing any hashing.

Only if it's enabled in the default Laravel project.

I only did the bare minimum to set up pgsql instead of mysql and everything else is standard.

@Jubeki
Copy link
Contributor

Jubeki commented Jan 16, 2024

The hashed cast is enabled by default:

'password' => 'hashed',

@Softwaerewolf
Copy link

Shouldn't this also affect mysql driver then? The problem is that the behavior is different.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants