273 lines
7.7 KiB
PHP
273 lines
7.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace HyperfTest\Cases\Integration\Auth;
|
|
|
|
use App\Model\ApiKey;
|
|
use App\Model\User;
|
|
use HyperfTest\TestCase;
|
|
use Qbhy\HyperfAuth\AuthManager;
|
|
|
|
use function Hyperf\Support\make;
|
|
|
|
/**
|
|
* API Key 认证流程 & CRUD 集成测试
|
|
*
|
|
* @internal
|
|
* @coversNothing
|
|
*/
|
|
class ApiKeyAuthTest extends TestCase
|
|
{
|
|
protected function getAuthToken(User $user): string
|
|
{
|
|
$auth = make(AuthManager::class);
|
|
return $auth->guard('jwt')->login($user);
|
|
}
|
|
|
|
protected function authHeaders(User $user): array
|
|
{
|
|
return ['Authorization' => 'Bearer ' . $this->getAuthToken($user)];
|
|
}
|
|
|
|
protected function createTestUser(array $overrides = []): User
|
|
{
|
|
$suffix = bin2hex(random_bytes(4));
|
|
|
|
return User::query()->create(array_merge([
|
|
'username' => 'apikey_int_' . $suffix,
|
|
'password' => 'Pass_' . $suffix,
|
|
'email' => 'apikey_int_' . $suffix . '@example.com',
|
|
'status' => 1,
|
|
'api_key_enabled' => true,
|
|
], $overrides));
|
|
}
|
|
|
|
// ========== API Key CRUD ==========
|
|
|
|
public function test_generate_api_key_success(): void
|
|
{
|
|
$user = $this->createTestUser();
|
|
|
|
$response = $this->post('/api/v1/me/api-keys', [
|
|
'name' => 'IntTest Key',
|
|
], $this->authHeaders($user));
|
|
|
|
$response->assertStatus(200);
|
|
$response->assertJsonPath('code', 0);
|
|
$response->assertJsonStructure([
|
|
'data' => ['plain_key', 'api_key' => ['id', 'name', 'key_prefix', 'enabled']],
|
|
]);
|
|
}
|
|
|
|
public function test_generate_api_key_without_api_key_enabled_returns_403(): void
|
|
{
|
|
$user = $this->createTestUser(['api_key_enabled' => false]);
|
|
|
|
$response = $this->post('/api/v1/me/api-keys', [
|
|
'name' => 'Blocked Key',
|
|
], $this->authHeaders($user));
|
|
|
|
$response->assertStatus(403);
|
|
$response->assertJsonPath('code', 403);
|
|
}
|
|
|
|
public function test_generate_api_key_missing_name_returns_400(): void
|
|
{
|
|
$user = $this->createTestUser();
|
|
|
|
$response = $this->post('/api/v1/me/api-keys', [], $this->authHeaders($user));
|
|
|
|
$response->assertStatus(400);
|
|
$response->assertJsonPath('code', 400);
|
|
}
|
|
|
|
public function test_generate_api_key_without_auth_returns_401(): void
|
|
{
|
|
$response = $this->post('/api/v1/me/api-keys', [
|
|
'name' => 'No Auth Key',
|
|
]);
|
|
|
|
$response->assertStatus(401);
|
|
}
|
|
|
|
public function test_list_api_keys(): void
|
|
{
|
|
$user = $this->createTestUser();
|
|
|
|
// 先创建一个 key
|
|
$this->post('/api/v1/me/api-keys', [
|
|
'name' => 'List Test Key',
|
|
], $this->authHeaders($user));
|
|
|
|
$response = $this->get('/api/v1/me/api-keys', [], $this->authHeaders($user));
|
|
|
|
$response->assertStatus(200);
|
|
$response->assertJsonPath('code', 0);
|
|
}
|
|
|
|
public function test_delete_api_key_success(): void
|
|
{
|
|
$user = $this->createTestUser();
|
|
|
|
// 创建一个 key
|
|
$create_response = $this->post('/api/v1/me/api-keys', [
|
|
'name' => 'Delete Test Key',
|
|
], $this->authHeaders($user));
|
|
|
|
$key_id = $create_response->json('data.api_key.id');
|
|
|
|
$response = $this->delete('/api/v1/me/api-keys/' . $key_id, [], $this->authHeaders($user));
|
|
|
|
$response->assertStatus(200);
|
|
$response->assertJsonPath('code', 0);
|
|
}
|
|
|
|
public function test_delete_other_users_api_key_returns_404(): void
|
|
{
|
|
$user_a = $this->createTestUser();
|
|
$user_b = $this->createTestUser();
|
|
|
|
// user_a 创建 key
|
|
$create_response = $this->post('/api/v1/me/api-keys', [
|
|
'name' => 'UserA Key',
|
|
], $this->authHeaders($user_a));
|
|
|
|
$key_id = $create_response->json('data.api_key.id');
|
|
|
|
// user_b 尝试删除
|
|
$response = $this->delete('/api/v1/me/api-keys/' . $key_id, [], $this->authHeaders($user_b));
|
|
|
|
$response->assertStatus(404);
|
|
$response->assertJsonPath('code', 404);
|
|
}
|
|
|
|
// ========== API Key 认证 ==========
|
|
|
|
public function test_api_key_auth_access_me_endpoint(): void
|
|
{
|
|
$user = $this->createTestUser();
|
|
|
|
// 通过 JWT 创建 API Key
|
|
$create_response = $this->post('/api/v1/me/api-keys', [
|
|
'name' => 'Auth Test Key',
|
|
], $this->authHeaders($user));
|
|
|
|
$plain_key = $create_response->json('data.plain_key');
|
|
|
|
// 使用 API Key 访问 /me
|
|
$response = $this->get('/api/v1/me', [], [
|
|
'X-API-Key' => $plain_key,
|
|
]);
|
|
|
|
$response->assertStatus(200);
|
|
$response->assertJsonPath('code', 0);
|
|
$response->assertJsonPath('data.id', $user->id);
|
|
}
|
|
|
|
public function test_invalid_api_key_returns_401(): void
|
|
{
|
|
$response = $this->get('/api/v1/me', [], [
|
|
'X-API-Key' => 'invalid_key_that_does_not_exist',
|
|
]);
|
|
|
|
$response->assertStatus(401);
|
|
}
|
|
|
|
public function test_disabled_api_key_returns_401(): void
|
|
{
|
|
$user = $this->createTestUser();
|
|
|
|
$create_response = $this->post('/api/v1/me/api-keys', [
|
|
'name' => 'Disabled Key',
|
|
], $this->authHeaders($user));
|
|
|
|
$plain_key = $create_response->json('data.plain_key');
|
|
$key_id = $create_response->json('data.api_key.id');
|
|
|
|
// 禁用 key
|
|
$api_key = ApiKey::query()->find($key_id);
|
|
$api_key->enabled = false;
|
|
$api_key->save();
|
|
|
|
// 使用已禁用的 key
|
|
$response = $this->get('/api/v1/me', [], [
|
|
'X-API-Key' => $plain_key,
|
|
]);
|
|
|
|
$response->assertStatus(403);
|
|
}
|
|
|
|
public function test_jwt_takes_priority_over_api_key(): void
|
|
{
|
|
$user_a = $this->createTestUser();
|
|
$user_b = $this->createTestUser();
|
|
|
|
// user_b 创建 API Key
|
|
$create_response = $this->post('/api/v1/me/api-keys', [
|
|
'name' => 'Priority Test Key',
|
|
], $this->authHeaders($user_b));
|
|
|
|
$plain_key = $create_response->json('data.plain_key');
|
|
|
|
// 同时携带 JWT (user_a) 和 API Key (user_b),应使用 JWT
|
|
$response = $this->get('/api/v1/me', [], [
|
|
'Authorization' => 'Bearer ' . $this->getAuthToken($user_a),
|
|
'X-API-Key' => $plain_key,
|
|
]);
|
|
|
|
$response->assertStatus(200);
|
|
$response->assertJsonPath('data.id', $user_a->id);
|
|
}
|
|
|
|
public function test_api_key_updates_last_used_at(): void
|
|
{
|
|
$user = $this->createTestUser();
|
|
|
|
$create_response = $this->post('/api/v1/me/api-keys', [
|
|
'name' => 'LastUsed Test Key',
|
|
], $this->authHeaders($user));
|
|
|
|
$plain_key = $create_response->json('data.plain_key');
|
|
$key_id = $create_response->json('data.api_key.id');
|
|
|
|
// 使用 API Key 访问
|
|
$this->get('/api/v1/me', [], [
|
|
'X-API-Key' => $plain_key,
|
|
]);
|
|
|
|
// 验证 last_used_at 已更新
|
|
$api_key = ApiKey::query()->find($key_id);
|
|
$this->assertNotNull($api_key->last_used_at);
|
|
}
|
|
|
|
public function test_disabled_user_api_key_returns_403(): void
|
|
{
|
|
$user = $this->createTestUser();
|
|
|
|
$create_response = $this->post('/api/v1/me/api-keys', [
|
|
'name' => 'Disabled User Key',
|
|
], $this->authHeaders($user));
|
|
|
|
$plain_key = $create_response->json('data.plain_key');
|
|
|
|
// 禁用用户
|
|
$user->status = 0;
|
|
$user->save();
|
|
|
|
// 使用 API Key 访问
|
|
$response = $this->get('/api/v1/me', [], [
|
|
'X-API-Key' => $plain_key,
|
|
]);
|
|
|
|
$response->assertStatus(403);
|
|
}
|
|
|
|
public function test_no_credentials_returns_401(): void
|
|
{
|
|
$response = $this->get('/api/v1/me');
|
|
|
|
$response->assertStatus(401);
|
|
}
|
|
}
|