-<?php namespace Tests\Auth;
+<?php
+
+namespace Tests\Auth;
use BookStack\Actions\ActivityType;
-use BookStack\Auth\Access\Oidc\OidcService;
use BookStack\Auth\User;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;
-use Illuminate\Filesystem\Cache;
+use Illuminate\Testing\TestResponse;
use Tests\Helpers\OidcJwtHelper;
use Tests\TestCase;
-use Tests\TestResponse;
class OidcTest extends TestCase
{
- protected $keyFilePath;
+ protected string $keyFilePath;
protected $keyFile;
- public function setUp(): void
+ protected function setUp(): void
{
parent::setUp();
// Set default config for OpenID Connect
file_put_contents($this->keyFilePath, OidcJwtHelper::publicPemKey());
config()->set([
- 'auth.method' => 'oidc',
- 'auth.defaults.guard' => 'oidc',
- 'oidc.name' => 'SingleSignOn-Testing',
- 'oidc.display_name_claims' => ['name'],
- 'oidc.client_id' => OidcJwtHelper::defaultClientId(),
- 'oidc.client_secret' => 'testpass',
- 'oidc.jwt_public_key' => $this->keyFilePath,
- 'oidc.issuer' => OidcJwtHelper::defaultIssuer(),
+ 'auth.method' => 'oidc',
+ 'auth.defaults.guard' => 'oidc',
+ 'oidc.name' => 'SingleSignOn-Testing',
+ 'oidc.display_name_claims' => ['name'],
+ 'oidc.client_id' => OidcJwtHelper::defaultClientId(),
+ 'oidc.client_secret' => 'testpass',
+ 'oidc.jwt_public_key' => $this->keyFilePath,
+ 'oidc.issuer' => OidcJwtHelper::defaultIssuer(),
'oidc.authorization_endpoint' => 'https://oidc.local/auth',
- 'oidc.token_endpoint' => 'https://oidc.local/token',
- 'oidc.discover' => false,
- 'oidc.dump_user_details' => false,
+ 'oidc.token_endpoint' => 'https://oidc.local/token',
+ 'oidc.discover' => false,
+ 'oidc.dump_user_details' => false,
]);
}
- public function tearDown(): void
+ protected function tearDown(): void
{
parent::tearDown();
if (file_exists($this->keyFilePath)) {
{
$req = $this->get('/login');
$req->assertSeeText('SingleSignOn-Testing');
- $req->assertElementExists('form[action$="/oidc/login"][method=POST] button');
+ $this->withHtml($req)->assertElementExists('form[action$="/oidc/login"][method=POST] button');
}
public function test_oidc_routes_are_only_active_if_oidc_enabled()
public function test_logout_route_functions()
{
$this->actingAs($this->getEditor());
- $this->get('/logout');
+ $this->post('/logout');
$this->assertFalse(auth()->check());
}
$transactions = &$this->mockHttpClient([$this->getMockAuthorizationResponse([
- 'sub' => 'benny1010101'
+ 'sub' => 'benny1010101',
])]);
// Callback from auth provider
$this->assertStringContainsString('code=SplxlOBeZQQYbYS6WxSbIA', $tokenRequest->getBody());
$this->assertStringContainsString('redirect_uri=' . urlencode(url('/oidc/callback')), $tokenRequest->getBody());
-
$this->assertTrue(auth()->check());
$this->assertDatabaseHas('users', [
'external_auth_id' => 'benny1010101',
- 'email_confirmed' => false,
+ 'email_confirmed' => false,
]);
$resp = $this->runLogin([
- 'sub' => 'benny505'
+ 'sub' => 'benny505',
]);
$resp->assertStatus(200);
$resp->assertJson([
- 'sub' => 'benny505',
- "iss" => OidcJwtHelper::defaultIssuer(),
- "aud" => OidcJwtHelper::defaultClientId(),
+ 'sub' => 'benny505',
+ 'iss' => OidcJwtHelper::defaultIssuer(),
+ 'aud' => OidcJwtHelper::defaultClientId(),
]);
$this->assertFalse(auth()->check());
}
{
$this->runLogin([
'email' => '',
- 'sub' => 'benny505'
+ 'sub' => 'benny505',
]);
$this->assertSessionError('Could not find an email address, for this user, in the data provided by the external authentication system');
$this->runLogin([
- 'sub' => 'benny505'
+ 'sub' => 'benny505',
]);
$this->assertSessionError('Already logged in');
$this->runLogin([
- 'sub' => 'benny505'
+ 'sub' => 'benny505',
]);
$this->assertTrue(auth()->check());
$this->assertFalse(auth()->check());
- $this->runLogin([
+ $resp = $this->runLogin([
'email' => $editor->email,
- 'sub' => 'benny505'
+ 'sub' => 'benny505',
]);
+ $resp = $this->followRedirects($resp);
- $this->assertSessionError('A user with the email ' . $editor->email . ' already exists but with different credentials.');
+ $resp->assertSeeText('A user with the email ' . $editor->email . ' already exists but with different credentials.');
$this->assertFalse(auth()->check());
}
public function test_auth_login_with_invalid_token_fails()
{
- $this->runLogin([
+ $resp = $this->runLogin([
'sub' => null,
]);
+ $resp = $this->followRedirects($resp);
- $this->assertSessionError('ID token validate failed with error: Missing token subject value');
+ $resp->assertSeeText('ID token validate failed with error: Missing token subject value');
$this->assertFalse(auth()->check());
}
new Response(404, [], 'Not found'),
]);
- $this->runLogin();
+ $resp = $this->followRedirects($this->runLogin());
$this->assertFalse(auth()->check());
- $this->assertSessionError('Login using SingleSignOn-Testing failed, system did not provide successful authorization');
+ $resp->assertSeeText('Login using SingleSignOn-Testing failed, system did not provide successful authorization');
}
public function test_autodiscovery_calls_are_cached()
$this->getAutoDiscoveryResponse(),
$this->getJwksResponse(),
$this->getAutoDiscoveryResponse([
- 'issuer' => 'https://auto.example.com'
+ 'issuer' => 'https://auto.example.com',
]),
$this->getJwksResponse(),
]);
$this->assertCount(4, $transactions);
}
+ public function test_auth_login_with_autodiscovery_with_keys_that_do_not_have_alg_property()
+ {
+ $this->withAutodiscovery();
+
+ $keyArray = OidcJwtHelper::publicJwkKeyArray();
+ unset($keyArray['alg']);
+
+ $this->mockHttpClient([
+ $this->getAutoDiscoveryResponse(),
+ new Response(200, [
+ 'Content-Type' => 'application/json',
+ 'Cache-Control' => 'no-cache, no-store',
+ 'Pragma' => 'no-cache',
+ ], json_encode([
+ 'keys' => [
+ $keyArray,
+ ],
+ ])),
+ ]);
+
+ $this->assertFalse(auth()->check());
+ $this->runLogin();
+ $this->assertTrue(auth()->check());
+ }
+
protected function withAutodiscovery()
{
config()->set([
- 'oidc.issuer' => OidcJwtHelper::defaultIssuer(),
- 'oidc.discover' => true,
+ 'oidc.issuer' => OidcJwtHelper::defaultIssuer(),
+ 'oidc.discover' => true,
'oidc.authorization_endpoint' => null,
- 'oidc.token_endpoint' => null,
- 'oidc.jwt_public_key' => null,
+ 'oidc.token_endpoint' => null,
+ 'oidc.jwt_public_key' => null,
]);
}
protected function getAutoDiscoveryResponse($responseOverrides = []): Response
{
return new Response(200, [
- 'Content-Type' => 'application/json',
+ 'Content-Type' => 'application/json',
'Cache-Control' => 'no-cache, no-store',
- 'Pragma' => 'no-cache'
+ 'Pragma' => 'no-cache',
], json_encode(array_merge([
- 'token_endpoint' => OidcJwtHelper::defaultIssuer() . '/oidc/token',
+ 'token_endpoint' => OidcJwtHelper::defaultIssuer() . '/oidc/token',
'authorization_endpoint' => OidcJwtHelper::defaultIssuer() . '/oidc/authorize',
- 'jwks_uri' => OidcJwtHelper::defaultIssuer() . '/oidc/keys',
- 'issuer' => OidcJwtHelper::defaultIssuer()
+ 'jwks_uri' => OidcJwtHelper::defaultIssuer() . '/oidc/keys',
+ 'issuer' => OidcJwtHelper::defaultIssuer(),
], $responseOverrides)));
}
protected function getJwksResponse(): Response
{
return new Response(200, [
- 'Content-Type' => 'application/json',
+ 'Content-Type' => 'application/json',
'Cache-Control' => 'no-cache, no-store',
- 'Pragma' => 'no-cache'
+ 'Pragma' => 'no-cache',
], json_encode([
'keys' => [
- OidcJwtHelper::publicJwkKeyArray()
- ]
+ OidcJwtHelper::publicJwkKeyArray(),
+ ],
]));
}
protected function getMockAuthorizationResponse($claimOverrides = []): Response
{
return new Response(200, [
- 'Content-Type' => 'application/json',
+ 'Content-Type' => 'application/json',
'Cache-Control' => 'no-cache, no-store',
- 'Pragma' => 'no-cache'
+ 'Pragma' => 'no-cache',
], json_encode([
'access_token' => 'abc123',
- 'token_type' => 'Bearer',
- 'expires_in' => 3600,
- 'id_token' => OidcJwtHelper::idToken($claimOverrides)
+ 'token_type' => 'Bearer',
+ 'expires_in' => 3600,
+ 'id_token' => OidcJwtHelper::idToken($claimOverrides),
]));
}
}