new funcionality
This commit is contained in:
123
tests/Feature/ClientSecretRotationTest.php
Normal file
123
tests/Feature/ClientSecretRotationTest.php
Normal file
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Testing\TestResponse;
|
||||
use Laravel\Passport\Client;
|
||||
use Tests\TestCase;
|
||||
|
||||
class ClientSecretRotationTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
private User $owner;
|
||||
private Client $client;
|
||||
private string $originalSecret;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->owner = User::factory()->approved()->create();
|
||||
|
||||
// Se crea vía el endpoint real para tener un secret en texto plano
|
||||
// válido y un cliente client_credentials real, igual que en producción.
|
||||
$response = $this->actingAs($this->owner)->postJson('/web-api/clients', [
|
||||
'name' => 'Rotation Test Client',
|
||||
'grant_type' => 'client_credentials',
|
||||
]);
|
||||
|
||||
$response->assertCreated();
|
||||
$this->originalSecret = $response->json('plain_secret');
|
||||
$this->client = Client::find($response->json('client.id'));
|
||||
}
|
||||
|
||||
public function test_original_secret_works_before_rotation(): void
|
||||
{
|
||||
$this->requestToken($this->originalSecret)->assertOk()->assertJsonStructure(['access_token']);
|
||||
}
|
||||
|
||||
public function test_rotating_returns_a_new_secret_and_grace_period(): void
|
||||
{
|
||||
$response = $this->actingAs($this->owner)->postJson("/web-api/clients/{$this->client->id}/rotate-secret");
|
||||
|
||||
$response->assertOk()->assertJsonStructure(['plain_secret', 'previous_secret_expires_at']);
|
||||
$this->assertNotEmpty($response->json('plain_secret'));
|
||||
$this->assertNotSame($this->originalSecret, $response->json('plain_secret'));
|
||||
|
||||
$this->assertDatabaseHas('audit_logs', [
|
||||
'action' => 'client.secret_rotated',
|
||||
'subject_id' => $this->client->id,
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_both_old_and_new_secret_work_during_the_grace_period(): void
|
||||
{
|
||||
$response = $this->actingAs($this->owner)->postJson("/web-api/clients/{$this->client->id}/rotate-secret");
|
||||
$newSecret = $response->json('plain_secret');
|
||||
|
||||
$this->requestToken($newSecret)->assertOk();
|
||||
$this->requestToken($this->originalSecret)->assertOk();
|
||||
}
|
||||
|
||||
public function test_old_secret_stops_working_after_the_grace_period_expires(): void
|
||||
{
|
||||
$this->actingAs($this->owner)->postJson("/web-api/clients/{$this->client->id}/rotate-secret");
|
||||
|
||||
$this->client->forceFill(['previous_secret_expires_at' => now()->subMinute()])->save();
|
||||
|
||||
$this->requestToken($this->originalSecret)->assertStatus(401);
|
||||
}
|
||||
|
||||
public function test_rotating_again_invalidates_the_first_old_secret_immediately(): void
|
||||
{
|
||||
// Solo se guarda UN secret anterior — rotar de nuevo pisa el que
|
||||
// había, no acumula un historial de secrets viejos válidos.
|
||||
$firstResponse = $this->actingAs($this->owner)->postJson("/web-api/clients/{$this->client->id}/rotate-secret");
|
||||
$secondSecret = $firstResponse->json('plain_secret');
|
||||
|
||||
$this->actingAs($this->owner)->postJson("/web-api/clients/{$this->client->id}/rotate-secret");
|
||||
|
||||
$this->requestToken($this->originalSecret)->assertStatus(401);
|
||||
$this->requestToken($secondSecret)->assertOk();
|
||||
}
|
||||
|
||||
public function test_non_owner_cannot_rotate_another_users_client_secret(): void
|
||||
{
|
||||
$stranger = User::factory()->approved()->create();
|
||||
|
||||
$this->actingAs($stranger)
|
||||
->postJson("/web-api/clients/{$this->client->id}/rotate-secret")
|
||||
->assertNotFound();
|
||||
|
||||
$this->requestToken($this->originalSecret)->assertOk();
|
||||
}
|
||||
|
||||
public function test_cannot_rotate_a_public_clients_secret(): void
|
||||
{
|
||||
$response = $this->actingAs($this->owner)->postJson('/web-api/clients', [
|
||||
'name' => 'Public Client',
|
||||
'grant_type' => 'authorization_code',
|
||||
'redirect_uris' => ['https://example.test/callback'],
|
||||
'confidential' => false,
|
||||
]);
|
||||
$response->assertCreated();
|
||||
$publicClient = Client::find($response->json('client.id'));
|
||||
|
||||
$this->actingAs($this->owner)
|
||||
->postJson("/web-api/clients/{$publicClient->id}/rotate-secret")
|
||||
->assertStatus(422);
|
||||
}
|
||||
|
||||
private function requestToken(string $secret): TestResponse
|
||||
{
|
||||
return $this->postJson('/oauth/token', [
|
||||
'grant_type' => 'client_credentials',
|
||||
'client_id' => $this->client->id,
|
||||
'client_secret' => $secret,
|
||||
'scope' => 'users:read',
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user