mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-10-01 01:36:00 -04:00
OIDC Userinfo: Added additional tests to cover jwks usage
This commit is contained in:
parent
b18cee3dc4
commit
0958909cd9
@ -59,7 +59,7 @@ class OidcJwtWithClaims implements ProvidesClaims
|
|||||||
*
|
*
|
||||||
* @throws OidcInvalidTokenException
|
* @throws OidcInvalidTokenException
|
||||||
*/
|
*/
|
||||||
protected function validateCommonTokenDetails(): bool
|
public function validateCommonTokenDetails(): bool
|
||||||
{
|
{
|
||||||
$this->validateTokenStructure();
|
$this->validateTokenStructure();
|
||||||
$this->validateTokenSignature();
|
$this->validateTokenSignature();
|
||||||
@ -151,7 +151,7 @@ class OidcJwtWithClaims implements ProvidesClaims
|
|||||||
*
|
*
|
||||||
* @throws OidcInvalidTokenException
|
* @throws OidcInvalidTokenException
|
||||||
*/
|
*/
|
||||||
public function validateCommonClaims(): void
|
protected function validateCommonClaims(): void
|
||||||
{
|
{
|
||||||
// 1. The Issuer Identifier for the OpenID Provider (which is typically obtained during Discovery)
|
// 1. The Issuer Identifier for the OpenID Provider (which is typically obtained during Discovery)
|
||||||
// MUST exactly match the value of the iss (issuer) Claim.
|
// MUST exactly match the value of the iss (issuer) Claim.
|
||||||
|
@ -20,11 +20,6 @@ class OidcUserinfoResponse implements ProvidesClaims
|
|||||||
$this->jwt = new OidcJwtWithClaims($response->getBody()->getContents(), $issuer, $keys);
|
$this->jwt = new OidcJwtWithClaims($response->getBody()->getContents(), $issuer, $keys);
|
||||||
$this->claims = $this->jwt->getAllClaims();
|
$this->claims = $this->jwt->getAllClaims();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO - Response validation (5.3.4):
|
|
||||||
// TODO - Verify that the OP that responded was the intended OP through a TLS server certificate check, per RFC 6125 [RFC6125].
|
|
||||||
// TODO - If the Client has provided a userinfo_encrypted_response_alg parameter during Registration, decrypt the UserInfo Response using the keys specified during Registration.
|
|
||||||
// TODO - If the response was signed, the Client SHOULD validate the signature according to JWS [JWS].
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -33,7 +28,7 @@ class OidcUserinfoResponse implements ProvidesClaims
|
|||||||
public function validate(string $idTokenSub): bool
|
public function validate(string $idTokenSub): bool
|
||||||
{
|
{
|
||||||
if (!is_null($this->jwt)) {
|
if (!is_null($this->jwt)) {
|
||||||
$this->jwt->validateCommonClaims();
|
$this->jwt->validateCommonTokenDetails();
|
||||||
}
|
}
|
||||||
|
|
||||||
$sub = $this->getClaim('sub');
|
$sub = $this->getClaim('sub');
|
||||||
@ -49,6 +44,14 @@ class OidcUserinfoResponse implements ProvidesClaims
|
|||||||
throw new OidcInvalidTokenException("Subject value provided in the userinfo endpoint does not match the provided ID token value");
|
throw new OidcInvalidTokenException("Subject value provided in the userinfo endpoint does not match the provided ID token value");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Spec v1.0 5.3.4 Defines the following:
|
||||||
|
// Verify that the OP that responded was the intended OP through a TLS server certificate check, per RFC 6125 [RFC6125].
|
||||||
|
// This is effectively done as part of the HTTP request we're making through CURLOPT_SSL_VERIFYHOST on the request.
|
||||||
|
// If the Client has provided a userinfo_encrypted_response_alg parameter during Registration, decrypt the UserInfo Response using the keys specified during Registration.
|
||||||
|
// We don't currently support JWT encryption for OIDC
|
||||||
|
// If the response was signed, the Client SHOULD validate the signature according to JWS [JWS].
|
||||||
|
// This is done as part of the validateCommonClaims above.
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -787,6 +787,68 @@ class OidcTest extends TestCase
|
|||||||
$this->assertTrue($user->hasRole($roleA->id));
|
$this->assertTrue($user->hasRole($roleA->id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_userinfo_endpoint_jwks_response_handled()
|
||||||
|
{
|
||||||
|
$userinfoResponseData = OidcJwtHelper::idToken(['name' => 'Barry Jwks']);
|
||||||
|
$userinfoResponse = new Response(200, ['Content-Type' => 'application/jwt'], $userinfoResponseData);
|
||||||
|
|
||||||
|
$resp = $this->runLogin(['name' => null], [$userinfoResponse]);
|
||||||
|
$resp->assertRedirect('/');
|
||||||
|
|
||||||
|
$user = User::where('email', OidcJwtHelper::defaultPayload()['email'])->first();
|
||||||
|
$this->assertEquals('Barry Jwks', $user->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_userinfo_endpoint_jwks_response_returning_no_sub_throws()
|
||||||
|
{
|
||||||
|
$userinfoResponseData = OidcJwtHelper::idToken(['sub' => null]);
|
||||||
|
$userinfoResponse = new Response(200, ['Content-Type' => 'application/jwt'], $userinfoResponseData);
|
||||||
|
|
||||||
|
$resp = $this->runLogin(['name' => null], [$userinfoResponse]);
|
||||||
|
$resp->assertRedirect('/login');
|
||||||
|
$this->assertSessionError('Userinfo endpoint response validation failed with error: No valid subject value found in userinfo data');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_userinfo_endpoint_jwks_response_returning_non_matching_sub_throws()
|
||||||
|
{
|
||||||
|
$userinfoResponseData = OidcJwtHelper::idToken(['sub' => 'zzz123']);
|
||||||
|
$userinfoResponse = new Response(200, ['Content-Type' => 'application/jwt'], $userinfoResponseData);
|
||||||
|
|
||||||
|
$resp = $this->runLogin(['name' => null], [$userinfoResponse]);
|
||||||
|
$resp->assertRedirect('/login');
|
||||||
|
$this->assertSessionError('Userinfo endpoint response validation failed with error: Subject value provided in the userinfo endpoint does not match the provided ID token value');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_userinfo_endpoint_jwks_response_with_invalid_signature_throws()
|
||||||
|
{
|
||||||
|
$userinfoResponseData = OidcJwtHelper::idToken();
|
||||||
|
$exploded = explode('.', $userinfoResponseData);
|
||||||
|
$exploded[2] = base64_encode(base64_decode($exploded[2]) . 'ABC');
|
||||||
|
$userinfoResponse = new Response(200, ['Content-Type' => 'application/jwt'], implode('.', $exploded));
|
||||||
|
|
||||||
|
$resp = $this->runLogin(['name' => null], [$userinfoResponse]);
|
||||||
|
$resp->assertRedirect('/login');
|
||||||
|
$this->assertSessionError('Userinfo endpoint response validation failed with error: Token signature could not be validated using the provided keys');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_userinfo_endpoint_jwks_response_with_invalid_signature_alg_throws()
|
||||||
|
{
|
||||||
|
$userinfoResponseData = OidcJwtHelper::idToken([], ['alg' => 'ZZ512']);
|
||||||
|
$userinfoResponse = new Response(200, ['Content-Type' => 'application/jwt'], $userinfoResponseData);
|
||||||
|
|
||||||
|
$resp = $this->runLogin(['name' => null], [$userinfoResponse]);
|
||||||
|
$resp->assertRedirect('/login');
|
||||||
|
$this->assertSessionError('Userinfo endpoint response validation failed with error: Only RS256 signature validation is supported. Token reports using ZZ512');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_userinfo_endpoint_response_with_invalid_content_type_throws()
|
||||||
|
{
|
||||||
|
$userinfoResponse = new Response(200, ['Content-Type' => 'application/beans'], json_encode(OidcJwtHelper::defaultPayload()));
|
||||||
|
$resp = $this->runLogin(['name' => null], [$userinfoResponse]);
|
||||||
|
$resp->assertRedirect('/login');
|
||||||
|
$this->assertSessionError('Userinfo endpoint response validation failed with error: No valid subject value found in userinfo data');
|
||||||
|
}
|
||||||
|
|
||||||
protected function withAutodiscovery(): void
|
protected function withAutodiscovery(): void
|
||||||
{
|
{
|
||||||
config()->set([
|
config()->set([
|
||||||
|
Loading…
Reference in New Issue
Block a user