202 lines
6.2 KiB
PHP
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');
|
|
});
|