Handle ClientData parsing errors in SessionCodeChecks gracefully

- Move ClientData parsing out of SessionCodeChecks ctor
- Respond with a bad request if invalid client data is presented

Closes #32515

Signed-off-by: Thomas Darimont <thomas.darimont@googlemail.com>
Signed-off-by: Alexander Schwartz <aschwart@redhat.com>
Co-authored-by: Alexander Schwartz <aschwart@redhat.com>
This commit is contained in:
Thomas Darimont 2024-09-05 10:50:27 +02:00 committed by GitHub
parent 83a57892ea
commit 693a63b532
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 24 additions and 17 deletions

View file

@ -96,17 +96,12 @@ public class ClientData {
return String.format("ClientData [ redirectUri=%s, responseType=%s, responseMode=%s, state=%s ]", redirectUri, responseType, responseMode, state); return String.format("ClientData [ redirectUri=%s, responseType=%s, responseMode=%s, state=%s ]", redirectUri, responseType, responseMode, state);
} }
public static ClientData decodeClientDataFromParameter(String clientDataParam) { public static ClientData decodeClientDataFromParameter(String clientDataParam) throws IOException {
try { if (ObjectUtil.isBlank(clientDataParam)) {
if (ObjectUtil.isBlank(clientDataParam)) {
return null;
} else {
byte[] cdataJson = Base64Url.decode(clientDataParam);
return JsonSerialization.readValue(cdataJson, ClientData.class);
}
} catch (IOException ioe) {
logger.warnf("ClientData parameter in invalid format. ClientData parameter was %s", clientDataParam);
return null; return null;
} else {
byte[] cdataJson = Base64Url.decode(clientDataParam);
return JsonSerialization.readValue(cdataJson, ClientData.class);
} }
} }

View file

@ -6,6 +6,8 @@ import org.keycloak.models.ClientModel;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.protocol.ClientData; import org.keycloak.protocol.ClientData;
import java.io.IOException;
public class IdentityBrokerStateTest { public class IdentityBrokerStateTest {
@ -48,7 +50,7 @@ public class IdentityBrokerStateTest {
} }
@Test @Test
public void testDecodedWithClientIdAnActualUuidBASE64UriFriendly() { public void testDecodedWithClientIdAnActualUuidBASE64UriFriendly() throws IOException {
// Given // Given
String state = "gNrGamIDGKpKSI9yOrcFzYTKoFGH779_WNCacAelkhk"; String state = "gNrGamIDGKpKSI9yOrcFzYTKoFGH779_WNCacAelkhk";
@ -90,7 +92,7 @@ public class IdentityBrokerStateTest {
} }
@Test @Test
public void testEncodedWithClientIdNotUUid() { public void testEncodedWithClientIdNotUUid() throws IOException {
// Given // Given
String encoded = "gNrGamIDGKpKSI9yOrcFzYTKoFGH779_WNCacAelkhk.vpISZLVDAc0.aHR0cDovL2kuYW0uYW4udXJs"; String encoded = "gNrGamIDGKpKSI9yOrcFzYTKoFGH779_WNCacAelkhk.vpISZLVDAc0.aHR0cDovL2kuYW0uYW4udXJs";
String clientId = "http://i.am.an.url"; String clientId = "http://i.am.an.url";
@ -107,7 +109,7 @@ public class IdentityBrokerStateTest {
} }
@Test @Test
public void testEncodedWithClientData() { public void testEncodedWithClientData() throws IOException {
// Given // Given
String encoded = "gNrGamIDGKpKSI9yOrcFzYTKoFGH779_WNCacAelkhk.vpISZLVDAc0.aHR0cDovL2kuYW0uYW4udXJs.eyJydSI6Imh0dHBzOi8vbXktcmVkaXJlY3QtdXJpIiwicnQiOiJjb2RlIiwicm0iOiJxdWVyeSIsInN0Ijoic29tZS1zdGF0ZSJ9"; String encoded = "gNrGamIDGKpKSI9yOrcFzYTKoFGH779_WNCacAelkhk.vpISZLVDAc0.aHR0cDovL2kuYW0uYW4udXJs.eyJydSI6Imh0dHBzOi8vbXktcmVkaXJlY3QtdXJpIiwicnQiOiJjb2RlIiwicm0iOiJxdWVyeSIsInN0Ijoic29tZS1zdGF0ZSJ9";
String clientId = "http://i.am.an.url"; String clientId = "http://i.am.an.url";

View file

@ -19,6 +19,7 @@ package org.keycloak.services.resources;
import static org.keycloak.services.managers.AuthenticationManager.authenticateIdentityCookie; import static org.keycloak.services.managers.AuthenticationManager.authenticateIdentityCookie;
import java.io.IOException;
import java.net.URI; import java.net.URI;
import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response;
@ -42,8 +43,6 @@ import org.keycloak.protocol.AuthorizationEndpointBase;
import org.keycloak.protocol.ClientData; import org.keycloak.protocol.ClientData;
import org.keycloak.protocol.LoginProtocol; import org.keycloak.protocol.LoginProtocol;
import org.keycloak.protocol.RestartLoginCookie; import org.keycloak.protocol.RestartLoginCookie;
import org.keycloak.protocol.oidc.endpoints.AuthorizationEndpointChecker;
import org.keycloak.protocol.oidc.endpoints.request.AuthorizationEndpointRequest;
import org.keycloak.services.ErrorPage; import org.keycloak.services.ErrorPage;
import org.keycloak.services.ServicesLogger; import org.keycloak.services.ServicesLogger;
import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.managers.AuthenticationManager;
@ -76,7 +75,7 @@ public class SessionCodeChecks {
private final String code; private final String code;
private final String execution; private final String execution;
private final String clientId; private final String clientId;
private final ClientData clientData; private final String clientDataString;
private final String tabId; private final String tabId;
private final String flowPath; private final String flowPath;
private final String authSessionId; private final String authSessionId;
@ -97,7 +96,7 @@ public class SessionCodeChecks {
this.tabId = tabId; this.tabId = tabId;
this.flowPath = flowPath; this.flowPath = flowPath;
this.authSessionId = authSessionId; this.authSessionId = authSessionId;
this.clientData = ClientData.decodeClientDataFromParameter(clientData); this.clientDataString = clientData;
} }
@ -174,6 +173,17 @@ public class SessionCodeChecks {
} }
ClientData clientData;
try {
clientData = ClientData.decodeClientDataFromParameter(clientDataString);
} catch (RuntimeException | IOException e) {
logger.debugf(e, "ClientData parameter in invalid format. ClientData parameter was %s", clientDataString);
event.detail(Details.REASON, "Invalid client data: " + e.getMessage());
event.error(Errors.INVALID_REQUEST);
response = ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.INVALID_REQUEST);
return null;
}
if (authSession != null) { if (authSession != null) {
session.getProvider(LoginFormsProvider.class).setAuthenticationSession(authSession); session.getProvider(LoginFormsProvider.class).setAuthenticationSession(authSession);
return authSession; return authSession;