diff --git a/app/Http/Controllers/RemoteOidcController.php b/app/Http/Controllers/RemoteOidcController.php index aac3a22b6..2f8228322 100644 --- a/app/Http/Controllers/RemoteOidcController.php +++ b/app/Http/Controllers/RemoteOidcController.php @@ -61,6 +61,12 @@ class RemoteOidcController extends Controller if ($mappedUser) { $this->guarder()->login($mappedUser->user); + // OIDC accounts have a random, unknowable password, so they can + // never satisfy the sudo-mode (RequirePassword / dangerzone) prompt. + // Mark the session password-confirmed at SSO login so they can reach + // dangerzone-gated settings within the normal confirmation window. + $request->session()->passwordConfirmed(); + return redirect('/'); } @@ -77,6 +83,10 @@ class RemoteOidcController extends Controller 'oidc_id' => $userInfoId, ]); + // See note above: mark the freshly-registered OIDC session + // password-confirmed so dangerzone routes are reachable. + $request->session()->passwordConfirmed(); + return redirect('/'); } diff --git a/tests/Feature/RemoteOidcTest.php b/tests/Feature/RemoteOidcTest.php index 3c3a151d5..4fee914be 100644 --- a/tests/Feature/RemoteOidcTest.php +++ b/tests/Feature/RemoteOidcTest.php @@ -109,6 +109,40 @@ it('shows the oidc start redirect', function () { // $this->assertDatabaseCount('users', $originalUserCount); // }); +it('lets an oidc user reach a dangerzone route without a password prompt', function () { + config(['remote-auth.oidc.enabled' => true]); + config(['remote-auth.oidc.field_username' => 'preferred_username']); + + $oauthData = [ + 'sub' => Str::random(10), + 'name' => fake()->name, + 'preferred_username' => 'oidcuser', + 'email' => fake()->unique()->freeEmail, + ]; + + $this->partialMock(UserOidcService::class, function (MockInterface $mock) use ($oauthData) { + $mock->shouldReceive('getAccessToken')->once()->andReturn(new AccessToken(['access_token' => 'token'])); + $mock->shouldReceive('getResourceOwner')->once()->andReturn(new GenericResourceOwner($oauthData, 'sub')); + }); + + // Complete the OIDC login; this marks the session password-confirmed so the + // random-password OIDC account can pass the sudo (dangerzone) gate. + $this->withSession(['oauth2state' => 'abc123']) + ->get('auth/oidc/callback?state=abc123&code=1') + ->assertRedirect('/'); + + $user = UserOidcMapping::where('oidc_id', $oauthData['sub'])->first()->user; + expect($user->register_source)->toBe('oidc'); + + // A real dangerzone-gated route must NOT bounce the OIDC user to the sudo + // password form (which they could never satisfy with a random password). + $response = $this->get('/settings/security'); + + $location = $response->headers->get('Location'); + expect($location === null || ! str_contains($location, 'password'))->toBeTrue(); + expect($location === null || ! str_contains($location, '/i/auth/sudo'))->toBeTrue(); +}); + it('ensures a valid username from the oidc callback', function () { config(['remote-auth.oidc.enabled' => true]); config(['remote-auth.oidc.field_username' => 'preferred_username']);