Optimize LogoutEndpoint.backchannelLogout endpoint

closes #32683

Signed-off-by: mposolda <mposolda@gmail.com>
This commit is contained in:
mposolda 2024-09-05 10:34:37 +02:00 committed by Alexander Schwartz
parent 4b95b42590
commit 866101e72e
4 changed files with 81 additions and 23 deletions

View file

@ -21,4 +21,8 @@ public enum LogoutTokenValidationCode {
public String getErrorMessage() { public String getErrorMessage() {
return errorMessage; return errorMessage;
} }
LogoutTokenValidationContext toCtx() {
return new LogoutTokenValidationContext(this);
}
} }

View file

@ -0,0 +1,58 @@
/*
* Copyright 2024 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.keycloak.protocol.oidc;
import java.util.List;
import java.util.stream.Stream;
import org.keycloak.broker.oidc.OIDCIdentityProvider;
import org.keycloak.representations.LogoutToken;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class LogoutTokenValidationContext {
private final LogoutToken logoutToken;
private final LogoutTokenValidationCode status;
private final List<OIDCIdentityProvider> validIdentityProviders;
LogoutTokenValidationContext(LogoutTokenValidationCode status) {
this(status, null, null);
}
LogoutTokenValidationContext(LogoutTokenValidationCode status, LogoutToken logoutToken, List<OIDCIdentityProvider> validIdentityProviders) {
this.logoutToken = logoutToken;
this.status = status;
this.validIdentityProviders = validIdentityProviders;
}
public LogoutToken getLogoutToken() {
return logoutToken;
}
public LogoutTokenValidationCode getStatus() {
return status;
}
public List<OIDCIdentityProvider> getValidIdentityProviders() {
return validIdentityProviders;
}
}

View file

@ -1454,45 +1454,45 @@ public class TokenManager {
} }
} }
public LogoutTokenValidationCode verifyLogoutToken(KeycloakSession session, String encodedLogoutToken) { public LogoutTokenValidationContext verifyLogoutToken(KeycloakSession session, String encodedLogoutToken) {
Optional<LogoutToken> logoutTokenOptional = toLogoutToken(encodedLogoutToken); Optional<LogoutToken> logoutTokenOptional = toLogoutToken(encodedLogoutToken);
if (logoutTokenOptional.isEmpty()) { if (logoutTokenOptional.isEmpty()) {
return LogoutTokenValidationCode.DECODE_TOKEN_FAILED; return LogoutTokenValidationCode.DECODE_TOKEN_FAILED.toCtx();
} }
LogoutToken logoutToken = logoutTokenOptional.get(); LogoutToken logoutToken = logoutTokenOptional.get();
List<OIDCIdentityProvider> identityProviders = getOIDCIdentityProviders(logoutToken, session).toList(); List<OIDCIdentityProvider> identityProviders = getOIDCIdentityProviders(logoutToken, session).toList();
if (identityProviders.isEmpty()) { if (identityProviders.isEmpty()) {
return LogoutTokenValidationCode.COULD_NOT_FIND_IDP; return LogoutTokenValidationCode.COULD_NOT_FIND_IDP.toCtx();
} }
Stream<OIDCIdentityProvider> validOidcIdentityProviders = List<OIDCIdentityProvider> validOidcIdentityProviders =
validateLogoutTokenAgainstIdpProvider(identityProviders.stream(), encodedLogoutToken); validateLogoutTokenAgainstIdpProvider(identityProviders.stream(), encodedLogoutToken).toList();
if (validOidcIdentityProviders.findAny().isEmpty()) { if (validOidcIdentityProviders.isEmpty()) {
return LogoutTokenValidationCode.TOKEN_VERIFICATION_WITH_IDP_FAILED; return LogoutTokenValidationCode.TOKEN_VERIFICATION_WITH_IDP_FAILED.toCtx();
} }
if (logoutToken.getSubject() == null && logoutToken.getSid() == null) { if (logoutToken.getSubject() == null && logoutToken.getSid() == null) {
return LogoutTokenValidationCode.MISSING_SID_OR_SUBJECT; return LogoutTokenValidationCode.MISSING_SID_OR_SUBJECT.toCtx();
} }
if (!checkLogoutTokenForEvents(logoutToken)) { if (!checkLogoutTokenForEvents(logoutToken)) {
return LogoutTokenValidationCode.BACKCHANNEL_LOGOUT_EVENT_MISSING; return LogoutTokenValidationCode.BACKCHANNEL_LOGOUT_EVENT_MISSING.toCtx();
} }
if (logoutToken.getOtherClaims().get(NONCE) != null) { if (logoutToken.getOtherClaims().get(NONCE) != null) {
return LogoutTokenValidationCode.NONCE_CLAIM_IN_TOKEN; return LogoutTokenValidationCode.NONCE_CLAIM_IN_TOKEN.toCtx();
} }
if (logoutToken.getId() == null) { if (logoutToken.getId() == null) {
return LogoutTokenValidationCode.LOGOUT_TOKEN_ID_MISSING; return LogoutTokenValidationCode.LOGOUT_TOKEN_ID_MISSING.toCtx();
} }
if (logoutToken.getIat() == null) { if (logoutToken.getIat() == null) {
return LogoutTokenValidationCode.MISSING_IAT_CLAIM; return LogoutTokenValidationCode.MISSING_IAT_CLAIM.toCtx();
} }
return LogoutTokenValidationCode.VALIDATION_SUCCESS; return new LogoutTokenValidationContext(LogoutTokenValidationCode.VALIDATION_SUCCESS, logoutToken, validOidcIdentityProviders);
} }
public Optional<LogoutToken> toLogoutToken(String encodedLogoutToken) { public Optional<LogoutToken> toLogoutToken(String encodedLogoutToken) {
@ -1505,11 +1505,6 @@ public class TokenManager {
} }
public Stream<OIDCIdentityProvider> getValidOIDCIdentityProvidersForBackchannelLogout(KeycloakSession session, String encodedLogoutToken, LogoutToken logoutToken) {
return validateLogoutTokenAgainstIdpProvider(getOIDCIdentityProviders(logoutToken, session), encodedLogoutToken);
}
public Stream<OIDCIdentityProvider> validateLogoutTokenAgainstIdpProvider(Stream<OIDCIdentityProvider> oidcIdps, String encodedLogoutToken) { public Stream<OIDCIdentityProvider> validateLogoutTokenAgainstIdpProvider(Stream<OIDCIdentityProvider> oidcIdps, String encodedLogoutToken) {
return oidcIdps return oidcIdps
.filter(oidcIdp -> { .filter(oidcIdp -> {

View file

@ -46,6 +46,7 @@ import org.keycloak.models.UserSessionModel;
import org.keycloak.models.utils.SystemClientUtil; import org.keycloak.models.utils.SystemClientUtil;
import org.keycloak.protocol.oidc.BackchannelLogoutResponse; import org.keycloak.protocol.oidc.BackchannelLogoutResponse;
import org.keycloak.protocol.oidc.LogoutTokenValidationCode; import org.keycloak.protocol.oidc.LogoutTokenValidationCode;
import org.keycloak.protocol.oidc.LogoutTokenValidationContext;
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper; import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.TokenManager; import org.keycloak.protocol.oidc.TokenManager;
@ -554,18 +555,18 @@ public class LogoutEndpoint {
Response.Status.BAD_REQUEST); Response.Status.BAD_REQUEST);
} }
LogoutTokenValidationCode validationCode = tokenManager.verifyLogoutToken(session, encodedLogoutToken); LogoutTokenValidationContext validationCtx = tokenManager.verifyLogoutToken(session, encodedLogoutToken);
if (!validationCode.equals(LogoutTokenValidationCode.VALIDATION_SUCCESS)) { if (!validationCtx.getStatus().equals(LogoutTokenValidationCode.VALIDATION_SUCCESS)) {
String errorMessage = validationCode.getErrorMessage(); String errorMessage = validationCtx.getStatus().getErrorMessage();
event.detail(Details.REASON, errorMessage); event.detail(Details.REASON, errorMessage);
event.error(Errors.INVALID_TOKEN); event.error(Errors.INVALID_TOKEN);
throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, errorMessage, throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, errorMessage,
Response.Status.BAD_REQUEST); Response.Status.BAD_REQUEST);
} }
LogoutToken logoutToken = tokenManager.toLogoutToken(encodedLogoutToken).get(); LogoutToken logoutToken = validationCtx.getLogoutToken();
Stream<String> identityProviderAliases = tokenManager.getValidOIDCIdentityProvidersForBackchannelLogout(session, encodedLogoutToken, logoutToken) Stream<String> identityProviderAliases = validationCtx.getValidIdentityProviders().stream()
.map(idp -> idp.getConfig().getAlias()); .map(idp -> idp.getConfig().getAlias());
boolean logoutOfflineSessions = Boolean.parseBoolean(logoutToken.getEvents() boolean logoutOfflineSessions = Boolean.parseBoolean(logoutToken.getEvents()