157 lines
4.9 KiB
PHP
157 lines
4.9 KiB
PHP
<?php
|
|
|
|
use App\Models\Role;
|
|
use App\Models\User;
|
|
use App\Services\SystemConfig\SystemConfigService;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Laravel\Socialite\Contracts\User as SocialiteUserContract;
|
|
use Laravel\Socialite\Facades\Socialite;
|
|
|
|
beforeEach(function () {
|
|
$ref = new ReflectionClass(SystemConfigService::class);
|
|
$prop = $ref->getProperty('resolvedSettings');
|
|
$prop->setAccessible(true);
|
|
$prop->setValue(null, null);
|
|
Cache::flush();
|
|
Role::firstOrCreate(['name' => 'User', 'guard_name' => 'web']);
|
|
});
|
|
|
|
function enableOauth(string $provider): void
|
|
{
|
|
app(SystemConfigService::class)->update(['feature_'.$provider.'_oauth' => true]);
|
|
}
|
|
|
|
function fakeSocialiteUser(array $overrides = []): SocialiteUserContract
|
|
{
|
|
$u = new Laravel\Socialite\Two\User;
|
|
$u->id = $overrides['id'] ?? 'oauth-id-123';
|
|
$u->name = $overrides['name'] ?? 'OAuth User';
|
|
$u->email = $overrides['email'] ?? 'oauth@example.com';
|
|
$u->avatar = $overrides['avatar'] ?? 'https://example.com/avatar.png';
|
|
$u->user = $overrides['user'] ?? [];
|
|
|
|
return $u;
|
|
}
|
|
|
|
test('redirect returns 404 when provider feature is disabled', function () {
|
|
$this->get('/auth/google')->assertNotFound();
|
|
});
|
|
|
|
test('redirect issues a redirect when provider feature is enabled', function () {
|
|
enableOauth('google');
|
|
|
|
Socialite::shouldReceive('driver->redirect')
|
|
->andReturn(redirect('https://accounts.google.com/o/oauth2/auth?fake=1'));
|
|
|
|
$this->get('/auth/google')->assertRedirect();
|
|
expect(session('social_auth_provider'))->toBe('google');
|
|
});
|
|
|
|
test('callback without provider session redirects to login with error', function () {
|
|
$this->get('/auth/callback')
|
|
->assertRedirect('/login')
|
|
->assertSessionHas('error');
|
|
});
|
|
|
|
test('callback rejects unverified email from provider', function () {
|
|
enableOauth('google');
|
|
session(['social_auth_provider' => 'google']);
|
|
|
|
Socialite::shouldReceive('driver->user')->andReturn(fakeSocialiteUser([
|
|
'user' => ['email_verified' => false],
|
|
]));
|
|
|
|
$this->get('/auth/callback')
|
|
->assertRedirect('/login')
|
|
->assertSessionHas('error');
|
|
|
|
$this->assertGuest();
|
|
});
|
|
|
|
test('callback creates a new user via provider id and assigns user role', function () {
|
|
enableOauth('google');
|
|
session(['social_auth_provider' => 'google']);
|
|
|
|
Socialite::shouldReceive('driver->user')->andReturn(fakeSocialiteUser([
|
|
'id' => 'google-uid-9',
|
|
'email' => 'fresh@example.com',
|
|
'name' => 'Fresh User',
|
|
]));
|
|
|
|
$this->get('/auth/callback')->assertRedirect('/dashboard');
|
|
|
|
$user = User::where('email', 'fresh@example.com')->first();
|
|
expect($user)->not->toBeNull();
|
|
expect($user->google_id)->toBe('google-uid-9');
|
|
expect($user->hasRole('User'))->toBeTrue();
|
|
});
|
|
|
|
test('callback links to existing user with matching email when no provider id yet', function () {
|
|
enableOauth('google');
|
|
session(['social_auth_provider' => 'google']);
|
|
$existing = User::factory()->create([
|
|
'email' => 'link@example.com',
|
|
'google_id' => null,
|
|
]);
|
|
|
|
Socialite::shouldReceive('driver->user')->andReturn(fakeSocialiteUser([
|
|
'id' => 'google-uid-link',
|
|
'email' => 'link@example.com',
|
|
]));
|
|
|
|
$this->get('/auth/callback')->assertRedirect('/dashboard');
|
|
|
|
expect($existing->fresh()->google_id)->toBe('google-uid-link');
|
|
$this->assertAuthenticatedAs($existing->fresh());
|
|
});
|
|
|
|
test('callback refuses to overwrite a different existing oauth identity', function () {
|
|
enableOauth('google');
|
|
session(['social_auth_provider' => 'google']);
|
|
|
|
$existing = User::factory()->create([
|
|
'email' => 'taken@example.com',
|
|
'google_id' => 'different-google-id',
|
|
]);
|
|
|
|
Socialite::shouldReceive('driver->user')->andReturn(fakeSocialiteUser([
|
|
'id' => 'attacker-id',
|
|
'email' => 'taken@example.com',
|
|
]));
|
|
|
|
$this->get('/auth/callback')
|
|
->assertRedirect('/login')
|
|
->assertSessionHas('error');
|
|
|
|
expect($existing->fresh()->google_id)->toBe('different-google-id');
|
|
$this->assertGuest();
|
|
});
|
|
|
|
test('callback re-uses user matched by provider id', function () {
|
|
enableOauth('google');
|
|
session(['social_auth_provider' => 'google']);
|
|
|
|
$existing = User::factory()->create(['google_id' => 'stable-id']);
|
|
|
|
Socialite::shouldReceive('driver->user')->andReturn(fakeSocialiteUser([
|
|
'id' => 'stable-id',
|
|
'email' => $existing->email,
|
|
]));
|
|
|
|
$this->get('/auth/callback')->assertRedirect('/dashboard');
|
|
$this->assertAuthenticatedAs($existing->fresh());
|
|
});
|
|
|
|
test('callback on socialite exception redirects to login with error', function () {
|
|
enableOauth('google');
|
|
session(['social_auth_provider' => 'google']);
|
|
|
|
Socialite::shouldReceive('driver->user')->andThrow(new Exception('OAuth boom'));
|
|
|
|
$this->get('/auth/callback')
|
|
->assertRedirect('/login')
|
|
->assertSessionHas('error');
|
|
|
|
$this->assertGuest();
|
|
});
|