From 647bce49c8340f32421c8a1cffbaaea4a5b28824 Mon Sep 17 00:00:00 2001 From: Alexander Schwartz Date: Thu, 4 Apr 2024 10:55:25 +0200 Subject: [PATCH] Add error details to events to be able to track down root causes Closes #28429 Signed-off-by: Alexander Schwartz --- .../java/org/keycloak/broker/oidc/OIDCIdentityProvider.java | 2 ++ .../events/log/JBossLoggingEventListenerProvider.java | 4 ++++ .../main/java/org/keycloak/protocol/oidc/TokenManager.java | 3 ++- .../keycloak/protocol/oidc/endpoints/LogoutEndpoint.java | 3 +++ .../protocol/oidc/endpoints/TokenRevocationEndpoint.java | 4 ++++ .../protocol/oidc/grants/RefreshTokenGrantType.java | 6 +++++- 6 files changed, 20 insertions(+), 2 deletions(-) diff --git a/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java b/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java index 952040f4dfdb..6e66e41b4ae8 100755 --- a/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java +++ b/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java @@ -880,6 +880,8 @@ final protected BrokeredIdentityContext validateJwt(EventBuilder event, String s return context; } catch (IOException e) { logger.debug("Unable to extract identity from identity token", e); + event.detail(Details.REASON, "Unable to extract identity from identity token: " + e.getMessage()); + event.error(Errors.INVALID_TOKEN); throw new ErrorResponseException(OAuthErrorException.INVALID_TOKEN, "invalid token", Response.Status.BAD_REQUEST); } diff --git a/services/src/main/java/org/keycloak/events/log/JBossLoggingEventListenerProvider.java b/services/src/main/java/org/keycloak/events/log/JBossLoggingEventListenerProvider.java index f8b7ec8e0308..fa9b665e3b50 100755 --- a/services/src/main/java/org/keycloak/events/log/JBossLoggingEventListenerProvider.java +++ b/services/src/main/java/org/keycloak/events/log/JBossLoggingEventListenerProvider.java @@ -94,6 +94,10 @@ private void logEvent(Event event) { sanitize(sb, event.getClientId()); sb.append(", userId="); sanitize(sb, event.getUserId()); + if (event.getSessionId() != null) { + sb.append(", sessionId="); + sanitize(sb, event.getSessionId()); + } sb.append(", ipAddress="); sanitize(sb, event.getIpAddress()); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java index d0c379eeb44b..d0fc5d3af95d 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java @@ -1144,8 +1144,9 @@ private void generateRefreshToken(boolean offlineTokenRequested) { if (offlineTokenRequested) { UserSessionManager sessionManager = new UserSessionManager(session); if (!sessionManager.isOfflineTokenAllowed(clientSessionCtx)) { + event.detail(Details.REASON, "Offline tokens not allowed for the user or client"); event.error(Errors.NOT_ALLOWED); - throw new ErrorResponseException("not_allowed", "Offline tokens not allowed for the user or client", Response.Status.BAD_REQUEST); + throw new ErrorResponseException(Errors.NOT_ALLOWED, "Offline tokens not allowed for the user or client", Response.Status.BAD_REQUEST); } refreshToken.type(TokenUtil.TOKEN_TYPE_OFFLINE); if (realm.isOfflineSessionMaxLifespanEnabled()) { diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java index 392c5c4296d6..44fd9defc5f0 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java @@ -433,6 +433,7 @@ private Response doBrowserLogout(AuthenticationSessionModel logoutSession) { } } catch (OAuthErrorException e) { event.event(EventType.LOGOUT); + event.detail(Details.REASON, e.getDescription()); event.error(Errors.INVALID_TOKEN); return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.SESSION_NOT_ACTIVE); } @@ -529,9 +530,11 @@ private Response logoutToken() { } catch (OAuthErrorException e) { // KEYCLOAK-6771 Certificate Bound Token if (MtlsHoKTokenUtil.CERT_VERIFY_ERROR_DESC.equals(e.getDescription())) { + event.detail(Details.REASON, e.getDescription()); event.error(Errors.NOT_ALLOWED); throw new CorsErrorResponseException(cors, e.getError(), e.getDescription(), Response.Status.UNAUTHORIZED); } else { + event.detail(Details.REASON, e.getDescription()); event.error(Errors.INVALID_TOKEN); throw new CorsErrorResponseException(cors, e.getError(), e.getDescription(), Response.Status.BAD_REQUEST); } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenRevocationEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenRevocationEndpoint.java index b0cb3fab3306..4d4dae73e089 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenRevocationEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenRevocationEndpoint.java @@ -171,6 +171,7 @@ private void checkToken() { String encodedToken = formParams.getFirst(PARAM_TOKEN); if (encodedToken == null) { + event.detail(Details.REASON, "Token not provided"); event.error(Errors.INVALID_REQUEST); throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_REQUEST, "Token not provided", Response.Status.BAD_REQUEST); @@ -184,6 +185,7 @@ private void checkToken() { } if (!(TokenUtil.TOKEN_TYPE_REFRESH.equals(token.getType()) || TokenUtil.TOKEN_TYPE_OFFLINE.equals(token.getType()) || TokenUtil.TOKEN_TYPE_BEARER.equals(token.getType())|| TokenUtil.TOKEN_TYPE_DPOP.equals(token.getType()))) { + event.detail(Details.REASON, "Unsupported token type"); event.error(Errors.INVALID_TOKEN_TYPE); throw new CorsErrorResponseException(cors, OAuthErrorException.UNSUPPORTED_TOKEN_TYPE, "Unsupported token type", Response.Status.BAD_REQUEST); @@ -193,11 +195,13 @@ private void checkToken() { private void checkIssuedFor() { String issuedFor = token.getIssuedFor(); if (issuedFor == null) { + event.detail(Details.REASON, "Issued for not set"); event.error(Errors.INVALID_TOKEN); throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_TOKEN, "Invalid token", Response.Status.OK); } if (!client.getClientId().equals(issuedFor)) { + event.detail(Details.REASON, "Unmatching clients"); event.error(Errors.INVALID_REQUEST); throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_REQUEST, "Unmatching clients", Response.Status.BAD_REQUEST); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/grants/RefreshTokenGrantType.java b/services/src/main/java/org/keycloak/protocol/oidc/grants/RefreshTokenGrantType.java index 882e3c7620a6..efafc320cecb 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/grants/RefreshTokenGrantType.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/grants/RefreshTokenGrantType.java @@ -25,10 +25,10 @@ import org.keycloak.OAuth2Constants; import org.keycloak.OAuthErrorException; import org.keycloak.common.Profile; +import org.keycloak.events.Details; import org.keycloak.events.Errors; import org.keycloak.events.EventType; import org.keycloak.models.AuthenticatedClientSessionModel; -import org.keycloak.models.KeycloakSession; import org.keycloak.models.UserSessionModel; import org.keycloak.protocol.oidc.TokenManager; import org.keycloak.representations.AccessTokenResponse; @@ -65,6 +65,7 @@ public Response process(Context context) { session.clientPolicy().triggerOnEvent(new TokenRefreshContext(formParams)); refreshToken = formParams.getFirst(OAuth2Constants.REFRESH_TOKEN); } catch (ClientPolicyException cpe) { + event.detail(Details.REASON, cpe.getErrorDetail()); event.error(cpe.getError()); throw new CorsErrorResponseException(cors, cpe.getError(), cpe.getErrorDetail(), cpe.getErrorStatus()); } @@ -91,13 +92,16 @@ public Response process(Context context) { logger.trace(e.getMessage(), e); // KEYCLOAK-6771 Certificate Bound Token if (MtlsHoKTokenUtil.CERT_VERIFY_ERROR_DESC.equals(e.getDescription())) { + event.detail(Details.REASON, e.getDescription()); event.error(Errors.NOT_ALLOWED); throw new CorsErrorResponseException(cors, e.getError(), e.getDescription(), Response.Status.UNAUTHORIZED); } else { + event.detail(Details.REASON, e.getDescription()); event.error(Errors.INVALID_TOKEN); throw new CorsErrorResponseException(cors, e.getError(), e.getDescription(), Response.Status.BAD_REQUEST); } } catch (ClientPolicyException cpe) { + event.detail(Details.REASON, cpe.getErrorDetail()); event.error(cpe.getError()); throw new CorsErrorResponseException(cors, cpe.getError(), cpe.getErrorDetail(), cpe.getErrorStatus()); }