singleloginsystem/tests/Feature/EntraOAuthTest.php
2026-06-10 13:18:46 +00:00

202 lines
6.2 KiB
PHP

<?php
declare(strict_types=1);
use App\Models\{OauthToken, User};
use Illuminate\Support\Facades\Http;
function assignAzureFdAccess(User $user): void
{
$protocol = App\Models\ConnectionProtocol::query()->where('name', 'url')->first();
if (! $protocol) {
$protocol = new App\Models\ConnectionProtocol();
$protocol->name = 'url';
$protocol->save();
}
$provider = App\Models\ConnectionProvider::query()->where('slug', 'azure-fd')->first();
if (! $provider) {
$provider = new App\Models\ConnectionProvider();
$provider->name = 'Azure FD';
$provider->slug = 'azure-fd';
$provider->save();
}
$status = App\Models\ConnectionStatus::query()->where('name', 'connected')->first();
if (! $status) {
$status = new App\Models\ConnectionStatus();
$status->name = 'connected';
$status->save();
}
$app = App\Models\ConnectedApp::query()->create([
'name' => 'Azure FD Portal',
'slug' => 'azure-fd-portal',
'connection_protocol_id' => $protocol->id,
'connection_provider_id' => $provider->id,
'connection_status_id' => $status->id,
'settings' => [
'url' => [
'redirect_url' => 'https://azure.microsoft.com',
],
],
]);
$role = App\Models\Role::findOrCreate('Azure User');
$role->update(['priority' => 10]);
$role->apps()->attach($app->id, ['duration' => now()->addYear()->toDateTimeString()]);
$user->assignRole($role);
}
it('redirects unauthenticated users to login when connecting', function (): void {
$this->get(route('entra.connect'))
->assertRedirect(route('login'));
});
it('returns 403 forbidden if user does not have permission for azure-fd app', function (): void {
$user = User::factory()->create();
$this->actingAs($user)
->get(route('entra.connect'))
->assertStatus(403);
});
it('redirects to Microsoft login on connect when authorized', function (): void {
$user = User::factory()->create();
assignAzureFdAccess($user);
$response = $this->actingAs($user)
->get(route('entra.connect'));
$response->assertRedirectContains('login.microsoftonline.com');
$response->assertRedirectContains('oauth2/v2.0/authorize');
});
it('stores token and redirects to dashboard on successful callback when authorized', function (): void {
$user = User::factory()->create();
assignAzureFdAccess($user);
Http::fake([
'login.microsoftonline.com/*' => Http::response([
'access_token' => 'fake-access-token',
'refresh_token' => 'fake-refresh-token',
'token_type' => 'Bearer',
'expires_in' => 3600,
]),
]);
$state = 'test-state-value';
$response = $this->actingAs($user)
->withSession(['oauth_state' => $state])
->get(route('entra.callback', [
'code' => 'fake-auth-code',
'state' => $state,
]));
$response->assertRedirect(route('dashboard.user'));
$response->assertSessionHas('entra_success', 'Microsoft connected successfully!');
$this->assertDatabaseHas('oauth_tokens', [
'user_id' => $user->id,
'provider' => 'microsoft',
'token_type' => 'Bearer',
]);
});
it('redirects to dashboard with error on invalid state when authorized', function (): void {
$user = User::factory()->create();
assignAzureFdAccess($user);
$response = $this->actingAs($user)
->withSession(['oauth_state' => 'correct-state'])
->get(route('entra.callback', [
'code' => 'fake-code',
'state' => 'wrong-state',
]));
$response->assertRedirect(route('dashboard.user'));
$response->assertSessionHas('entra_error');
});
it('redirects to dashboard with error when Microsoft returns error and authorized', function (): void {
$user = User::factory()->create();
assignAzureFdAccess($user);
$state = 'test-state';
$response = $this->actingAs($user)
->withSession(['oauth_state' => $state])
->get(route('entra.callback', [
'error' => 'access_denied',
'error_description' => 'User cancelled the request',
'state' => $state,
]));
$response->assertRedirect(route('dashboard.user'));
$response->assertSessionHas('entra_error', 'User cancelled the request');
});
it('disconnects and removes the token when authorized', function (): void {
$user = User::factory()->create();
assignAzureFdAccess($user);
OauthToken::create([
'user_id' => $user->id,
'provider' => 'microsoft',
'access_token' => 'fake-token',
'refresh_token' => 'fake-refresh',
'token_type' => 'Bearer',
'expires_at' => now()->addHour(),
]);
$response = $this->actingAs($user)
->delete(route('entra.disconnect'));
$response->assertRedirect(route('dashboard.user'));
$response->assertSessionHas('entra_success', 'Microsoft disconnected.');
$this->assertDatabaseMissing('oauth_tokens', [
'user_id' => $user->id,
'provider' => 'microsoft',
]);
});
it('updates existing token on re-connect when authorized', function (): void {
$user = User::factory()->create();
assignAzureFdAccess($user);
OauthToken::create([
'user_id' => $user->id,
'provider' => 'microsoft',
'access_token' => 'old-token',
'refresh_token' => 'old-refresh',
'token_type' => 'Bearer',
'expires_at' => now()->subHour(),
]);
Http::fake([
'login.microsoftonline.com/*' => Http::response([
'access_token' => 'new-access-token',
'refresh_token' => 'new-refresh-token',
'token_type' => 'Bearer',
'expires_in' => 3600,
]),
]);
$state = 'reconnect-state';
$this->actingAs($user)
->withSession(['oauth_state' => $state])
->get(route('entra.callback', [
'code' => 'new-auth-code',
'state' => $state,
]));
expect(OauthToken::where('user_id', $user->id)->where('provider', 'microsoft')->count())->toBe(1);
$token = $user->oauthToken('microsoft');
expect($token->access_token)->toBe('new-access-token');
expect($token->refresh_token)->toBe('new-refresh-token');
});