3
0
mirror of https://github.com/snipe/snipe-it.git synced 2026-08-23 13:49:03 +00:00
Files
snipe-it/tests/Feature/Users/Ui/CreateInactiveUserWithoutPasswordTest.php
2026-07-23 17:38:42 +01:00

155 lines
6.1 KiB
PHP

<?php
namespace Tests\Feature\Users\Ui;
use App\Models\User;
use Illuminate\Support\Facades\Hash;
use Tests\TestCase;
/**
* When a new user is created deactivated ("This user can login" unchecked),
* SaveUserRequest skips the password rule entirely for that request. The
* controller then stores User::noPassword() raw so Hash::check at login
* always fails. Matches the CSV importer's long-standing "no password"
* behavior and the API's existing fallback.
*/
class CreateInactiveUserWithoutPasswordTest extends TestCase
{
public function test_inactive_user_can_be_created_without_a_password(): void
{
$actor = User::factory()->createUsers()->create();
$this->actingAs($actor)
->from(route('users.create'))
->post(route('users.store'), [
'first_name' => 'Inactive',
'last_name' => 'User',
'username' => 'inactive_user',
'email' => 'inactive@example.test',
'notes' => 'created deactivated with no password',
// activated deliberately omitted
// password + password_confirmation deliberately omitted
])
->assertSessionHasNoErrors()
->assertRedirect();
$this->assertDatabaseHas('users', [
'username' => 'inactive_user',
'activated' => 0,
]);
$created = User::where('username', 'inactive_user')->first();
$this->assertNotNull($created);
// The stored value must be the raw sentinel, not a bcrypt hash of
// it. If the controller accidentally bcrypted the sentinel, that
// hash could be recovered by anyone who knows the sentinel value.
$this->assertSame($created->noPassword(), $created->password);
}
public function test_stored_sentinel_can_never_authenticate(): void
{
// Belt-and-suspenders: even if an attacker somehow knew the
// sentinel string, Hash::check should reject it because a plain
// string is not a valid bcrypt hash. This holds no matter what
// the input is.
$actor = User::factory()->createUsers()->create();
$this->actingAs($actor)
->from(route('users.create'))
->post(route('users.store'), [
'first_name' => 'Sentinel',
'last_name' => 'Test',
'username' => 'sentinel_user',
'email' => 'sentinel@example.test',
'notes' => 'sentinel storage check',
])
->assertSessionHasNoErrors();
$created = User::where('username', 'sentinel_user')->first();
$this->assertFalse(Hash::check($created->noPassword(), $created->password));
$this->assertFalse(Hash::check('anything at all', $created->password));
}
public function test_active_user_still_requires_a_password(): void
{
// Regression guard: the injection only fires when activated is
// falsy. An active user with an empty password must still fail
// validation.
$actor = User::factory()->createUsers()->create();
$this->actingAs($actor)
->from(route('users.create'))
->post(route('users.store'), [
'first_name' => 'Active',
'last_name' => 'User',
'username' => 'active_needs_password',
'email' => 'active@example.test',
'activated' => '1',
// password deliberately omitted
])
->assertSessionHasErrors('password');
$this->assertDatabaseMissing('users', ['username' => 'active_needs_password']);
}
public function test_strict_complexity_rules_do_not_block_no_password_deactivated_create(): void
{
// Admin can configure password complexity via settings
// (letters, numbers, case_diff, symbols, min length, uncommon).
// Because SaveUserRequest skips the password rule entirely when
// activated is off, complexity rules never fire on this path.
// A strict-security-policy admin can still create deactivated
// users without a password. Turn everything on and verify.
$this->settings->set([
'pwd_secure_min' => 16,
'pwd_secure_uncommon' => 1,
'pwd_secure_complexity' => 'letters|numbers|case_diff|symbols',
]);
$actor = User::factory()->createUsers()->create();
$this->actingAs($actor)
->from(route('users.create'))
->post(route('users.store'), [
'first_name' => 'Strict',
'last_name' => 'Policy',
'username' => 'strict_no_password',
'email' => 'strict@example.test',
'notes' => 'strict complexity rules should still allow no-password inactive create',
])
->assertSessionHasNoErrors();
$this->assertDatabaseHas('users', ['username' => 'strict_no_password', 'activated' => 0]);
}
public function test_inactive_user_with_a_password_still_gets_it_bcrypted(): void
{
// If the operator DOES supply a password even for an inactive
// user (say they plan to activate later), we should NOT overwrite
// it with the sentinel. The provided password gets hashed
// normally.
$actor = User::factory()->createUsers()->create();
$this->actingAs($actor)
->from(route('users.create'))
->post(route('users.store'), [
'first_name' => 'Inactive',
'last_name' => 'WithPassword',
'username' => 'inactive_with_password',
'email' => 'iw@example.test',
'notes' => 'has a real password despite being inactive',
'password' => 'RealPassword_1234!',
'password_confirmation' => 'RealPassword_1234!',
])
->assertSessionHasNoErrors();
$created = User::where('username', 'inactive_with_password')->first();
$this->assertNotNull($created);
$this->assertNotSame($created->noPassword(), $created->password);
$this->assertTrue(Hash::check('RealPassword_1234!', $created->password));
}
}