From ad376b064c5579644ba5fe9db53decda4aef9ce6 Mon Sep 17 00:00:00 2001 From: R Searls Date: Thu, 7 Nov 2024 16:30:48 -0500 Subject: [PATCH 1/6] logout file with debug stmts --- .../security/http/oidc/LogoutHandler.java | 15 +++++++++++++++ .../http/oidc/OidcAuthenticationMechanism.java | 15 ++++++++++++++- .../security/http/oidc/OidcHttpFacade.java | 17 +++++++++++++++++ .../security/http/oidc/AbstractLogoutTest.java | 2 +- 4 files changed, 47 insertions(+), 2 deletions(-) diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/LogoutHandler.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/LogoutHandler.java index 68ae38f7ac..ed1577373b 100644 --- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/LogoutHandler.java +++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/LogoutHandler.java @@ -60,10 +60,12 @@ protected boolean removeEldestEntry(Map.Entry e }); boolean tryLogout(OidcHttpFacade facade) { + log.trace("## LogoutHandler.tryLogout ENTERED"); RefreshableOidcSecurityContext securityContext = getSecurityContext(facade); if (securityContext == null) { // no active session + log.trace("## LogoutHandler.tryLogout no active session"); return false; } @@ -75,15 +77,19 @@ boolean tryLogout(OidcHttpFacade facade) { } if (isRpInitiatedLogoutUri(facade)) { + log.trace("## LogoutHandler.tryLogout isRpInitiatedLogoutUri"); redirectEndSessionEndpoint(facade); return true; } if (isLogoutCallbackUri(facade)) { + log.trace("## LogoutHandler.tryLogout isLogoutCallbackUri"); if (isFrontChannel(facade)) { + log.trace("## LogoutHandler.tryLogout isFrontChannel"); handleFrontChannelLogoutRequest(facade); return true; } else { + log.trace("## LogoutHandler.tryLogout !isFrontChannel"); // we have an active session, should have received a GET logout request facade.getResponse().setStatus(HttpStatus.SC_METHOD_NOT_ALLOWED); facade.authenticationFailed(); @@ -94,11 +100,16 @@ boolean tryLogout(OidcHttpFacade facade) { } boolean tryBackChannelLogout(OidcHttpFacade facade) { + log.trace("## LogoutHandler.tryBackChannelLogout ENTERED"); + log.trace(facade.rlsGetSessionIds()); if (isLogoutCallbackUri(facade)) { + log.trace("## LogoutHandler.tryBackChannelLogout isLogoutCallbackUri"); if (isBackChannel(facade)) { + log.trace("## LogoutHandler.tryBackChannelLogout isBackChannel"); handleBackChannelLogoutRequest(facade); return true; } else { + log.trace("## LogoutHandler.tryBackChannelLogout ! isBackChannel"); // no active session, should have received a POST logout request facade.getResponse().setStatus(HttpStatus.SC_METHOD_NOT_ALLOWED); facade.authenticationFailed(); @@ -222,11 +233,15 @@ private String getRedirectUri(OidcHttpFacade facade) { private boolean isLogoutCallbackUri(OidcHttpFacade facade) { String path = facade.getRequest().getRelativePath(); + log.trace("## LogoutHandler.isLogoutCallbackUri path: " + + path + ", logoutCallbackUri: " + getLogoutCallbackUri(facade)); return path.endsWith(getLogoutCallbackUri(facade)); } private boolean isRpInitiatedLogoutUri(OidcHttpFacade facade) { String path = facade.getRequest().getRelativePath(); + log.trace("## LogoutHandler.isRpInitiatedLogoutUri path: " + + path + ", logoutUri: " + getLogoutUri(facade)); return path.endsWith(getLogoutUri(facade)); } diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcAuthenticationMechanism.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcAuthenticationMechanism.java index 602cf23d3b..36457b1dd9 100644 --- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcAuthenticationMechanism.java +++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcAuthenticationMechanism.java @@ -42,6 +42,7 @@ final class OidcAuthenticationMechanism implements HttpServerAuthenticationMechanism { private static LogoutHandler logoutHandler = new LogoutHandler(); + private final Map properties; private final CallbackHandler callbackHandler; private final OidcClientContext oidcClientContext; @@ -59,6 +60,9 @@ public String getMechanismName() { @Override public void evaluateRequest(HttpServerRequest request) throws HttpAuthenticationException { + log.trace("## OidcAuthenticationMechanism.evaluateRequest requestURI: " + + request.getRequestURI().toString()); + String tmpURI = request.getRequestURI().toString(); OidcClientContext oidcClientContext = getOidcClientContext(request); if (oidcClientContext == null) { log.debugf("Ignoring request for path [%s] from mechanism [%s]. No client configuration context found.", request.getRequestURI(), getMechanismName()); @@ -69,6 +73,7 @@ public void evaluateRequest(HttpServerRequest request) throws HttpAuthentication OidcHttpFacade httpFacade = new OidcHttpFacade(request, oidcClientContext, callbackHandler); OidcClientConfiguration oidcClientConfiguration = httpFacade.getOidcClientConfiguration(); if (! oidcClientConfiguration.isConfigured()) { + log.trace("## OidcAuthenticationMechanism.evaluateRequest !isConfigured()"); request.noAuthenticationInProgress(); return; } @@ -84,16 +89,22 @@ public void evaluateRequest(HttpServerRequest request) throws HttpAuthentication AuthOutcome outcome = authenticator.authenticate(); if (AuthOutcome.AUTHENTICATED.equals(outcome)) { - if (new AuthenticatedActionsHandler(oidcClientConfiguration, httpFacade).handledRequest() || logoutHandler.tryLogout(httpFacade)) { + log.trace("## OidcAuthenticationMechanism.evaluateRequest AuthOutcome.AUTHENTICATED outcome"); + if (new AuthenticatedActionsHandler(oidcClientConfiguration, httpFacade).handledRequest() + || logoutHandler.tryLogout(httpFacade)) { + log.trace("## OidcAuthenticationMechanism.evaluateRequest call authenticationInProgress()"); httpFacade.authenticationInProgress(); } else { + log.trace("## OidcAuthenticationMechanism.evaluateRequest call authenticationComplete()"); httpFacade.authenticationComplete(); } return; } if (AuthOutcome.NOT_ATTEMPTED.equals(outcome)) { + log.trace("## OidcAuthenticationMechanism.evaluateRequest AuthOutcome.NOT_ATTEMPTED"); if (logoutHandler.tryBackChannelLogout(httpFacade)) { + log.trace("## OidcAuthenticationMechanism.evaluateRequest tryBackChannelLogout returned true"); httpFacade.authenticationInProgress(); return; } @@ -101,10 +112,12 @@ public void evaluateRequest(HttpServerRequest request) throws HttpAuthentication AuthChallenge challenge = authenticator.getChallenge(); if (challenge != null) { + log.trace("## OidcAuthenticationMechanism.evaluateRequest challenge != null"); httpFacade.noAuthenticationInProgress(challenge); return; } if (Oidc.AuthOutcome.FAILED.equals(outcome)) { + log.trace("## OidcAuthenticationMechanism.evaluateRequest AuthOutcome.FAILED"); httpFacade.getResponse().setStatus(HttpStatus.SC_FORBIDDEN); httpFacade.authenticationFailed(); return; diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcHttpFacade.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcHttpFacade.java index 1c6f03fa7a..45def472a8 100644 --- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcHttpFacade.java +++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcHttpFacade.java @@ -550,4 +550,21 @@ public String getPath() { return path; } } + + // rls debug only + public String rlsGetSessionIds() { + Collection sessions = request.getScopeIds(Scope.SESSION); + if (sessions == null) { + return "## OidcHttpFacade Scope.SESSION sessionIds is null."; + } else if (sessions.isEmpty()){ + return "Scope.SESSION sessionIds is empty."; + } + StringBuffer sb = new StringBuffer(); + sb.append("Scope.SESSION sessionIds: ["); + for (String s : sessions) { + sb.append(s + ", "); + } + sb.append("]\n"); + return sb.toString(); + } } diff --git a/http/oidc/src/test/java/org/wildfly/security/http/oidc/AbstractLogoutTest.java b/http/oidc/src/test/java/org/wildfly/security/http/oidc/AbstractLogoutTest.java index fc139a4a2b..4be6ffa9b8 100644 --- a/http/oidc/src/test/java/org/wildfly/security/http/oidc/AbstractLogoutTest.java +++ b/http/oidc/src/test/java/org/wildfly/security/http/oidc/AbstractLogoutTest.java @@ -76,7 +76,7 @@ public static void generalCleanup() { } @Before - public void onBefore() throws IOException { + public void onBefore() throws Exception { OidcBaseTest.client = new MockWebServer(); OidcBaseTest.client.start(new InetSocketAddress(0).getAddress(), CLIENT_PORT); configureDispatcher(); From 3affa81d9e9ccd58b9e6e9425ec765384cda7f84 Mon Sep 17 00:00:00 2001 From: R Searls Date: Tue, 12 Nov 2024 07:15:46 -0500 Subject: [PATCH 2/6] working code for rpinitiated logout. contains tmp debug stmts --- .../oidc/AuthenticatedActionsHandler.java | 7 +- .../http/oidc/AuthenticationError.java | 4 +- .../security/http/oidc/ElytronMessages.java | 7 ++ .../security/http/oidc/LogoutHandler.java | 108 +++++++--------- .../org/wildfly/security/http/oidc/Oidc.java | 4 + .../oidc/OidcAuthenticationMechanism.java | 19 +-- .../http/oidc/OidcClientConfiguration.java | 65 +++++----- .../oidc/OidcClientConfigurationBuilder.java | 51 ++++++++ .../http/oidc/RequestAuthenticator.java | 1 + .../http/oidc/AbstractLogoutTest.java | 23 ++-- .../http/oidc/BackChannelLogoutTest.java | 14 +-- .../http/oidc/FrontChannelLogoutTest.java | 8 +- .../oidc/LogoutConfigurationOptionsTest.java | 118 ++++++++++++++++++ .../security/http/oidc/OidcBaseTest.java | 13 +- .../http/impl/AbstractBaseHttpTest.java | 110 +++++++--------- 15 files changed, 346 insertions(+), 206 deletions(-) create mode 100644 http/oidc/src/test/java/org/wildfly/security/http/oidc/LogoutConfigurationOptionsTest.java diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/AuthenticatedActionsHandler.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/AuthenticatedActionsHandler.java index d754642db8..305d825570 100644 --- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/AuthenticatedActionsHandler.java +++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/AuthenticatedActionsHandler.java @@ -1,6 +1,6 @@ /* * JBoss, Home of Professional Open Source. - * Copyright 2021 Red Hat, Inc., and individual contributors + * Copyright 2024 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -37,6 +37,7 @@ */ public class AuthenticatedActionsHandler { + private static LogoutHandler logoutHandler = new LogoutHandler(); private OidcClientConfiguration deployment; private OidcHttpFacade facade; @@ -54,6 +55,10 @@ public boolean handledRequest() { return true; } + if (logoutHandler.tryLogout(facade)) { + return true; + } + return false; } diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/AuthenticationError.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/AuthenticationError.java index cc99a48d12..789e1f4b4d 100644 --- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/AuthenticationError.java +++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/AuthenticationError.java @@ -36,7 +36,9 @@ public enum Reason { INVALID_TOKEN, STALE_TOKEN, NO_AUTHORIZATION_HEADER, - NO_QUERY_PARAMETER_ACCESS_TOKEN + NO_QUERY_PARAMETER_ACCESS_TOKEN, + NO_SESSION_ID, + METHOD_NOT_ALLOWED } private Reason reason; diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/ElytronMessages.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/ElytronMessages.java index e836cc3b46..ce3d976dcd 100644 --- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/ElytronMessages.java +++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/ElytronMessages.java @@ -278,5 +278,12 @@ interface ElytronMessages extends BasicLogger { @Message(id = 23070, value = "Authentication request format must be one of the following: oauth2, request, request_uri.") RuntimeException invalidAuthenticationRequestFormat(); + + @Message(id = 23071, value = "%s is not a valid value for %s") + RuntimeException invalidLogoutPath(String pathValue, String pathName); + + @Message(id = 23072, value = "The end substring of %s: %s can not be identical to %s: %s") + RuntimeException invalidLogoutCallbackPath(String callbackPathTitle, String callbacPathkValue, + String logoutPathTitle, String logoutPathValue); } diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/LogoutHandler.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/LogoutHandler.java index ed1577373b..c00d53aa4d 100644 --- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/LogoutHandler.java +++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/LogoutHandler.java @@ -36,12 +36,12 @@ */ final class LogoutHandler { - private static final String POST_LOGOUT_REDIRECT_URI_PARAM = "post_logout_redirect_uri"; - private static final String ID_TOKEN_HINT_PARAM = "id_token_hint"; + public static final String POST_LOGOUT_REDIRECT_URI_PARAM = "post_logout_redirect_uri"; + public static final String ID_TOKEN_HINT_PARAM = "id_token_hint"; private static final String LOGOUT_TOKEN_PARAM = "logout_token"; private static final String LOGOUT_TOKEN_TYPE = "Logout"; - private static final String SID = "sid"; - private static final String ISS = "iss"; + public static final String SID = "sid"; + public static final String ISS = "iss"; /** * A bounded map to store sessions marked for invalidation after receiving logout requests through the back-channel @@ -60,12 +60,10 @@ protected boolean removeEldestEntry(Map.Entry e }); boolean tryLogout(OidcHttpFacade facade) { - log.trace("## LogoutHandler.tryLogout ENTERED"); RefreshableOidcSecurityContext securityContext = getSecurityContext(facade); if (securityContext == null) { // no active session - log.trace("## LogoutHandler.tryLogout no active session"); return false; } @@ -76,48 +74,19 @@ boolean tryLogout(OidcHttpFacade facade) { return true; } - if (isRpInitiatedLogoutUri(facade)) { - log.trace("## LogoutHandler.tryLogout isRpInitiatedLogoutUri"); + if (isRpInitiatedLogoutPath(facade)) { redirectEndSessionEndpoint(facade); return true; } - if (isLogoutCallbackUri(facade)) { - log.trace("## LogoutHandler.tryLogout isLogoutCallbackUri"); - if (isFrontChannel(facade)) { - log.trace("## LogoutHandler.tryLogout isFrontChannel"); - handleFrontChannelLogoutRequest(facade); - return true; - } else { - log.trace("## LogoutHandler.tryLogout !isFrontChannel"); - // we have an active session, should have received a GET logout request - facade.getResponse().setStatus(HttpStatus.SC_METHOD_NOT_ALLOWED); - facade.authenticationFailed(); - } + if (isLogoutCallbackPath(facade)) { + handleLogoutRequest(facade); + return true; } return false; } - boolean tryBackChannelLogout(OidcHttpFacade facade) { - log.trace("## LogoutHandler.tryBackChannelLogout ENTERED"); - log.trace(facade.rlsGetSessionIds()); - if (isLogoutCallbackUri(facade)) { - log.trace("## LogoutHandler.tryBackChannelLogout isLogoutCallbackUri"); - if (isBackChannel(facade)) { - log.trace("## LogoutHandler.tryBackChannelLogout isBackChannel"); - handleBackChannelLogoutRequest(facade); - return true; - } else { - log.trace("## LogoutHandler.tryBackChannelLogout ! isBackChannel"); - // no active session, should have received a POST logout request - facade.getResponse().setStatus(HttpStatus.SC_METHOD_NOT_ALLOWED); - facade.authenticationFailed(); - } - } - return false; - } - private boolean isSessionMarkedForInvalidation(OidcHttpFacade facade) { RefreshableOidcSecurityContext securityContext = getSecurityContext(facade); IDToken idToken = securityContext.getIDToken(); @@ -132,15 +101,16 @@ private boolean isSessionMarkedForInvalidation(OidcHttpFacade facade) { private void redirectEndSessionEndpoint(OidcHttpFacade facade) { RefreshableOidcSecurityContext securityContext = getSecurityContext(facade); OidcClientConfiguration clientConfiguration = securityContext.getOidcClientConfiguration(); + String logoutUri; try { URIBuilder redirectUriBuilder = new URIBuilder(clientConfiguration.getEndSessionEndpointUrl()) .addParameter(ID_TOKEN_HINT_PARAM, securityContext.getIDTokenString()); - String postLogoutUri = clientConfiguration.getPostLogoutUri(); - - if (postLogoutUri != null) { - redirectUriBuilder.addParameter(POST_LOGOUT_REDIRECT_URI_PARAM, getRedirectUri(facade) + postLogoutUri); + String postLogoutPath = clientConfiguration.getPostLogoutPath(); + if (postLogoutPath != null) { + redirectUriBuilder.addParameter(POST_LOGOUT_REDIRECT_URI_PARAM, + getRedirectUri(facade) + postLogoutPath); } logoutUri = redirectUriBuilder.build().toString(); @@ -153,7 +123,20 @@ private void redirectEndSessionEndpoint(OidcHttpFacade facade) { facade.getResponse().setHeader(HttpConstants.LOCATION, logoutUri); } + private void handleLogoutRequest(OidcHttpFacade facade) { + if (isFrontChannel(facade)) { + handleFrontChannelLogoutRequest(facade); + } else if (isBackChannel(facade)) { + handleBackChannelLogoutRequest(facade); + } else { + // logout requests should arrive either as a HTTP GET or POST + facade.getResponse().setStatus(HttpStatus.SC_METHOD_NOT_ALLOWED); + facade.authenticationFailed(); + } + } + private void handleBackChannelLogoutRequest(OidcHttpFacade facade) { + RefreshableOidcSecurityContext securityContext = getSecurityContext(facade); String logoutToken = facade.getRequest().getFirstParam(LOGOUT_TOKEN_PARAM); TokenValidator tokenValidator = TokenValidator.builder(facade.getOidcClientConfiguration()) .setSkipExpirationValidator() @@ -186,7 +169,7 @@ private void handleBackChannelLogoutRequest(OidcHttpFacade facade) { } log.debug("Marking session for invalidation during back-channel logout"); - sessionsMarkedForInvalidation.put(sessionId, facade.getOidcClientConfiguration()); + sessionsMarkedForInvalidation.put(sessionId, securityContext.getOidcClientConfiguration()); } private void handleFrontChannelLogoutRequest(OidcHttpFacade facade) { @@ -221,8 +204,7 @@ private String getRedirectUri(OidcHttpFacade facade) { if (uri.indexOf('?') != -1) { uri = uri.substring(0, uri.indexOf('?')); } - - int logoutPathIndex = uri.indexOf(getLogoutUri(facade)); + int logoutPathIndex = uri.indexOf(getLogoutPath(facade)); if (logoutPathIndex != -1) { uri = uri.substring(0, logoutPathIndex); @@ -231,22 +213,29 @@ private String getRedirectUri(OidcHttpFacade facade) { return uri; } - private boolean isLogoutCallbackUri(OidcHttpFacade facade) { + private boolean isLogoutCallbackPath(OidcHttpFacade facade) { String path = facade.getRequest().getRelativePath(); - log.trace("## LogoutHandler.isLogoutCallbackUri path: " - + path + ", logoutCallbackUri: " + getLogoutCallbackUri(facade)); - return path.endsWith(getLogoutCallbackUri(facade)); + String xx = getLogoutCallbackPath(facade); // rls debug only + return path.endsWith(getLogoutCallbackPath(facade)); } - private boolean isRpInitiatedLogoutUri(OidcHttpFacade facade) { + private boolean isRpInitiatedLogoutPath(OidcHttpFacade facade) { String path = facade.getRequest().getRelativePath(); - log.trace("## LogoutHandler.isRpInitiatedLogoutUri path: " - + path + ", logoutUri: " + getLogoutUri(facade)); - return path.endsWith(getLogoutUri(facade)); + return path.endsWith(getLogoutPath(facade)); } private boolean isSessionRequiredOnLogout(OidcHttpFacade facade) { - return facade.getOidcClientConfiguration().isSessionRequiredOnLogout(); + return getOidcClientConfiguration(facade).isSessionRequiredOnLogout(); + } + + private OidcClientConfiguration getOidcClientConfiguration(OidcHttpFacade facade) { + RefreshableOidcSecurityContext securityContext = getSecurityContext(facade); + + if (securityContext == null) { + return null; + } + + return securityContext.getOidcClientConfiguration(); } private RefreshableOidcSecurityContext getSecurityContext(OidcHttpFacade facade) { @@ -261,12 +250,11 @@ private RefreshableOidcSecurityContext getSecurityContext(OidcHttpFacade facade) return securityContext; } - private String getLogoutUri(OidcHttpFacade facade) { - return facade.getOidcClientConfiguration().getLogoutUrl(); + private String getLogoutPath(OidcHttpFacade facade) { + return getOidcClientConfiguration(facade).getLogoutPath(); } - - private String getLogoutCallbackUri(OidcHttpFacade facade) { - return facade.getOidcClientConfiguration().getLogoutCallbackUrl(); + private String getLogoutCallbackPath(OidcHttpFacade facade) { + return getOidcClientConfiguration(facade).getLogoutCallbackPath(); } private boolean isBackChannel(OidcHttpFacade facade) { diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/Oidc.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/Oidc.java index c6b38c9ef4..cbff772366 100644 --- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/Oidc.java +++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/Oidc.java @@ -173,6 +173,10 @@ public class Oidc { public static final String CONFIDENTIAL_PORT = "confidential-port"; public static final String ENABLE_BASIC_AUTH = "enable-basic-auth"; public static final String PROVIDER_URL = "provider-url"; + public static final String LOGOUT_PATH = "logout-path"; + public static final String LOGOUT_CALLBACK_PATH = "logout-callback-path"; + public static final String POST_LOGOUT_PATH = "post-logout-path"; + public static final String LOGOUT_SESSION_REQUIRED = "logout-session-required"; /** * Bearer token pattern. diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcAuthenticationMechanism.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcAuthenticationMechanism.java index 36457b1dd9..fecde05ee5 100644 --- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcAuthenticationMechanism.java +++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcAuthenticationMechanism.java @@ -60,8 +60,6 @@ public String getMechanismName() { @Override public void evaluateRequest(HttpServerRequest request) throws HttpAuthenticationException { - log.trace("## OidcAuthenticationMechanism.evaluateRequest requestURI: " + - request.getRequestURI().toString()); String tmpURI = request.getRequestURI().toString(); OidcClientContext oidcClientContext = getOidcClientContext(request); if (oidcClientContext == null) { @@ -73,7 +71,6 @@ public void evaluateRequest(HttpServerRequest request) throws HttpAuthentication OidcHttpFacade httpFacade = new OidcHttpFacade(request, oidcClientContext, callbackHandler); OidcClientConfiguration oidcClientConfiguration = httpFacade.getOidcClientConfiguration(); if (! oidcClientConfiguration.isConfigured()) { - log.trace("## OidcAuthenticationMechanism.evaluateRequest !isConfigured()"); request.noAuthenticationInProgress(); return; } @@ -89,35 +86,21 @@ public void evaluateRequest(HttpServerRequest request) throws HttpAuthentication AuthOutcome outcome = authenticator.authenticate(); if (AuthOutcome.AUTHENTICATED.equals(outcome)) { - log.trace("## OidcAuthenticationMechanism.evaluateRequest AuthOutcome.AUTHENTICATED outcome"); if (new AuthenticatedActionsHandler(oidcClientConfiguration, httpFacade).handledRequest() - || logoutHandler.tryLogout(httpFacade)) { - log.trace("## OidcAuthenticationMechanism.evaluateRequest call authenticationInProgress()"); + ) { httpFacade.authenticationInProgress(); } else { - log.trace("## OidcAuthenticationMechanism.evaluateRequest call authenticationComplete()"); httpFacade.authenticationComplete(); } return; } - if (AuthOutcome.NOT_ATTEMPTED.equals(outcome)) { - log.trace("## OidcAuthenticationMechanism.evaluateRequest AuthOutcome.NOT_ATTEMPTED"); - if (logoutHandler.tryBackChannelLogout(httpFacade)) { - log.trace("## OidcAuthenticationMechanism.evaluateRequest tryBackChannelLogout returned true"); - httpFacade.authenticationInProgress(); - return; - } - } - AuthChallenge challenge = authenticator.getChallenge(); if (challenge != null) { - log.trace("## OidcAuthenticationMechanism.evaluateRequest challenge != null"); httpFacade.noAuthenticationInProgress(challenge); return; } if (Oidc.AuthOutcome.FAILED.equals(outcome)) { - log.trace("## OidcAuthenticationMechanism.evaluateRequest AuthOutcome.FAILED"); httpFacade.getResponse().setStatus(HttpStatus.SC_FORBIDDEN); httpFacade.authenticationFailed(); return; diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcClientConfiguration.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcClientConfiguration.java index e7bc0943ec..0fc0334107 100644 --- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcClientConfiguration.java +++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcClientConfiguration.java @@ -143,14 +143,12 @@ public enum RelativeUrlsUsed { protected String requestObjectSigningKeyAlias; protected String requestObjectSigningKeyStoreType; protected JWKEncPublicKeyLocator encryptionPublicKeyLocator; + private boolean logoutSessionRequired = true; - private String postLogoutUri; - + private String postLogoutPath; private boolean sessionRequiredOnLogout = true; - - private String logoutUrl = "/logout"; - - private String logoutCallbackUrl = "/logout/callback"; + private String logoutPath = "/logout"; + private String logoutCallbackPath = "/logout/callback"; private int logoutSessionWaitingLimit = 100; @@ -338,6 +336,10 @@ public String getEndSessionEndpointUrl() { return endSessionEndpointUrl; } + public String getLogoutPath() { + return logoutPath; + } + public String getAccountUrl() { resolveUrls(); return accountUrl; @@ -702,14 +704,6 @@ public String getTokenSignatureAlgorithm() { return tokenSignatureAlgorithm; } - public void setPostLogoutUri(String postLogoutUri) { - this.postLogoutUri = postLogoutUri; - } - - public String getPostLogoutUri() { - return postLogoutUri; - } - public boolean isSessionRequiredOnLogout() { return sessionRequiredOnLogout; } @@ -718,21 +712,6 @@ public void setSessionRequiredOnLogout(boolean sessionRequiredOnLogout) { this.sessionRequiredOnLogout = sessionRequiredOnLogout; } - public String getLogoutUrl() { - return logoutUrl; - } - - public void setLogoutUrl(String logoutUrl) { - this.logoutUrl = logoutUrl; - } - - public String getLogoutCallbackUrl() { - return logoutCallbackUrl; - } - - public void setLogoutCallbackUrl(String logoutCallbackUrl) { - this.logoutCallbackUrl = logoutCallbackUrl; - } public int getLogoutSessionWaitingLimit() { return logoutSessionWaitingLimit; } @@ -828,4 +807,32 @@ public void setEncryptionPublicKeyLocator(JWKEncPublicKeyLocator publicKeySetExt public JWKEncPublicKeyLocator getEncryptionPublicKeyLocator() { return this.encryptionPublicKeyLocator; } + + public void setPostLogoutPath(String postLogoutPath) { + this.postLogoutPath = postLogoutPath; + } + + public String getPostLogoutPath() { + return postLogoutPath; + } + + public boolean isLogoutSessionRequired() { + return logoutSessionRequired; + } + + public void setLogoutSessionRequired(boolean logoutSessionRequired) { + this.logoutSessionRequired = logoutSessionRequired; + } + + public void setLogoutPath(String logoutPath) { + this.logoutPath = logoutPath; + } + + public String getLogoutCallbackPath() { + return logoutCallbackPath; + } + + public void setLogoutCallbackPath(String logoutCallbackPath) { + this.logoutCallbackPath = logoutCallbackPath; + } } diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcClientConfigurationBuilder.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcClientConfigurationBuilder.java index 43bebace9f..fc80ef0135 100644 --- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcClientConfigurationBuilder.java +++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcClientConfigurationBuilder.java @@ -25,6 +25,10 @@ import static org.wildfly.security.http.oidc.Oidc.AuthenticationRequestFormat.REQUEST_URI; import static org.wildfly.security.http.oidc.Oidc.SSLRequired; import static org.wildfly.security.http.oidc.Oidc.TokenStore; +import static org.wildfly.security.http.oidc.Oidc.LOGOUT_PATH; +import static org.wildfly.security.http.oidc.Oidc.LOGOUT_CALLBACK_PATH; +import static org.wildfly.security.http.oidc.Oidc.POST_LOGOUT_PATH; +import static org.wildfly.security.http.oidc.Oidc.LOGOUT_SESSION_REQUIRED; import java.io.IOException; import java.io.InputStream; @@ -195,6 +199,46 @@ protected OidcClientConfiguration internalBuild(final OidcJsonConfiguration oidc oidcClientConfiguration.setTokenSignatureAlgorithm(oidcJsonConfiguration.getTokenSignatureAlgorithm()); + String tmpLogoutPath = System.getProperty(LOGOUT_PATH); + if (tmpLogoutPath != null) { + if (isValidPath(tmpLogoutPath)) { + oidcClientConfiguration.setLogoutPath(tmpLogoutPath); + } else { + throw log.invalidLogoutPath(tmpLogoutPath, LOGOUT_PATH); + } + } + + + String tmpLogoutCallbackPath = System.getProperty(LOGOUT_CALLBACK_PATH); + if (tmpLogoutCallbackPath != null) { + if (isValidPath(tmpLogoutCallbackPath) + && !tmpLogoutCallbackPath.endsWith(oidcClientConfiguration.getLogoutPath())) { + oidcClientConfiguration.setLogoutCallbackPath(tmpLogoutCallbackPath); + } else { + if (!isValidPath(tmpLogoutCallbackPath)) { + throw log.invalidLogoutPath(tmpLogoutPath, LOGOUT_CALLBACK_PATH); + } else { + throw log.invalidLogoutCallbackPath(LOGOUT_CALLBACK_PATH, tmpLogoutCallbackPath, + LOGOUT_PATH, oidcClientConfiguration.getLogoutPath()); + } + } + } + + String tmpPostLogoutPath = System.getProperty(POST_LOGOUT_PATH); + if (tmpPostLogoutPath != null) { + if (isValidPath(tmpPostLogoutPath)) { + oidcClientConfiguration.setPostLogoutPath(tmpPostLogoutPath); + } else { + throw log.invalidLogoutPath(tmpLogoutPath, POST_LOGOUT_PATH); + } + } + + String tmpLogoutSessionRequired = System.getProperty(LOGOUT_SESSION_REQUIRED); + if (tmpLogoutSessionRequired != null) { + oidcClientConfiguration.setLogoutSessionRequired( + Boolean.valueOf(tmpLogoutSessionRequired)); + } + return oidcClientConfiguration; } @@ -236,4 +280,11 @@ public static OidcClientConfiguration build(OidcJsonConfiguration oidcJsonConfig return new OidcClientConfigurationBuilder().internalBuild(oidcJsonConfiguration); } + private boolean isValidPath(String path) { + String tmpPath = path.trim(); + if (tmpPath.length() > 1 && tmpPath.startsWith("/")) { + return true; + } + return false; + } } diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/RequestAuthenticator.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/RequestAuthenticator.java index 87b18e0abe..f635b7e79a 100644 --- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/RequestAuthenticator.java +++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/RequestAuthenticator.java @@ -55,6 +55,7 @@ public RequestAuthenticator(OidcHttpFacade facade, OidcClientConfiguration deplo public AuthOutcome authenticate() { AuthOutcome authenticate = doAuthenticate(); if (AuthOutcome.AUTHENTICATED.equals(authenticate) && !facade.isAuthorized()) { + log.trace("## RequestAutenticator.authenticate AUTHENTICATED but NOT Autorized"); return AuthOutcome.FAILED; } return authenticate; diff --git a/http/oidc/src/test/java/org/wildfly/security/http/oidc/AbstractLogoutTest.java b/http/oidc/src/test/java/org/wildfly/security/http/oidc/AbstractLogoutTest.java index 4be6ffa9b8..35adc45ad1 100644 --- a/http/oidc/src/test/java/org/wildfly/security/http/oidc/AbstractLogoutTest.java +++ b/http/oidc/src/test/java/org/wildfly/security/http/oidc/AbstractLogoutTest.java @@ -7,15 +7,14 @@ * 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 + * 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. + * 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.wildfly.security.http.oidc; import static org.junit.Assert.assertNotNull; @@ -54,8 +53,6 @@ public abstract class AbstractLogoutTest extends OidcBaseTest { private ElytronDispatcher dispatcher; private OidcClientConfiguration clientConfig; - static boolean IS_BACK_CHANNEL_TEST = false; - static final String BACK_CHANNEL_LOGOUT_URL = "/logout/callback"; @BeforeClass public static void onBeforeClass() { @@ -80,7 +77,7 @@ public void onBefore() throws Exception { OidcBaseTest.client = new MockWebServer(); OidcBaseTest.client.start(new InetSocketAddress(0).getAddress(), CLIENT_PORT); configureDispatcher(); - RealmRepresentation realm = KeycloakConfiguration.getRealmRepresentation(TEST_REALM, CLIENT_ID, CLIENT_SECRET, CLIENT_HOST_NAME, CLIENT_PORT, CLIENT_APP, CONFIGURE_CLIENT_SCOPES); + RealmRepresentation realm = KeycloakConfiguration.getRealmRepresentation(TEST_REALM, CLIENT_ID, CLIENT_SECRET, CLIENT_HOST_NAME, CLIENT_PORT, CLIENT_APP, false); realm.setAccessTokenLifespan(100); realm.setSsoSessionMaxLifespan(100); @@ -173,11 +170,7 @@ public MockResponse dispatch(RecordedRequest serverRequest) throws InterruptedEx MockResponse mockResponse = new MockResponse(); try { - if (IS_BACK_CHANNEL_TEST && serverRequest.getRequestUrl().toString().endsWith(BACK_CHANNEL_LOGOUT_URL)) { - currentRequest = new TestingHttpServerRequest(serverRequest, null); - } else { - currentRequest = new TestingHttpServerRequest(serverRequest, sessionScope); - } + currentRequest = new TestingHttpServerRequest(serverRequest, sessionScope); mechanism.evaluateRequest(currentRequest); diff --git a/http/oidc/src/test/java/org/wildfly/security/http/oidc/BackChannelLogoutTest.java b/http/oidc/src/test/java/org/wildfly/security/http/oidc/BackChannelLogoutTest.java index 8540209498..e4fed15513 100644 --- a/http/oidc/src/test/java/org/wildfly/security/http/oidc/BackChannelLogoutTest.java +++ b/http/oidc/src/test/java/org/wildfly/security/http/oidc/BackChannelLogoutTest.java @@ -29,25 +29,21 @@ import com.gargoylesoftware.htmlunit.Page; import com.gargoylesoftware.htmlunit.WebClient; import org.apache.http.HttpStatus; -import org.junit.BeforeClass; import org.junit.Test; import org.keycloak.representations.idm.ClientRepresentation; public class BackChannelLogoutTest extends AbstractLogoutTest { - @BeforeClass - public static void setUp() { - IS_BACK_CHANNEL_TEST = true; - } - @Override protected void doConfigureClient(ClientRepresentation client) { List redirectUris = client.getRedirectUris(); String redirectUri = redirectUris.get(0); + OidcClientConfiguration config = new OidcClientConfiguration(); client.setFrontchannelLogout(false); client.getAttributes().put("backchannel.logout.session.required", "true"); - client.getAttributes().put("backchannel.logout.url", rewriteHost(redirectUri) + BACK_CHANNEL_LOGOUT_URL); + client.getAttributes().put("backchannel.logout.url", rewriteHost(redirectUri) + + config.getLogoutCallbackPath()); } private static String rewriteHost(String redirectUri) { @@ -76,8 +72,8 @@ public void testRPInitiatedLogout() throws Exception { // logged out after finishing the redirections during frontchannel logout assertUserAuthenticated(); - webClient.getPage(getClientUrl() + "/logout"); - //assertUserAuthenticated(); + webClient.getPage(getClientUrl() + getClientConfig().getLogoutPath()); + assertUserAuthenticated(); webClient.getPage(getClientUrl()); assertUserNotAuthenticated(); } diff --git a/http/oidc/src/test/java/org/wildfly/security/http/oidc/FrontChannelLogoutTest.java b/http/oidc/src/test/java/org/wildfly/security/http/oidc/FrontChannelLogoutTest.java index dd4ca58c74..5137d40413 100644 --- a/http/oidc/src/test/java/org/wildfly/security/http/oidc/FrontChannelLogoutTest.java +++ b/http/oidc/src/test/java/org/wildfly/security/http/oidc/FrontChannelLogoutTest.java @@ -50,7 +50,9 @@ protected void doConfigureClient(ClientRepresentation client) { List redirectUris = client.getRedirectUris(); String redirectUri = redirectUris.get(0); - client.getAttributes().put("frontchannel.logout.url", redirectUri + "/logout/callback"); + OidcClientConfiguration ocConfig = new OidcClientConfiguration(); + client.getAttributes().put("frontchannel.logout.url", redirectUri + + ocConfig.getLogoutCallbackPath()); } @Test @@ -71,14 +73,14 @@ public void testRPInitiatedLogout() throws Exception { // logged out after finishing the redirections during frontchannel logout assertUserAuthenticated(); - webClient.getPage(getClientUrl() + "/logout"); + webClient.getPage(getClientUrl() + getClientConfig().getLogoutPath()); assertUserNotAuthenticated(); } @Test public void testRPInitiatedLogoutWithPostLogoutUri() throws Exception { OidcClientConfiguration oidcClientConfiguration = getClientConfig(); - oidcClientConfiguration.setPostLogoutUri("/post-logout"); + oidcClientConfiguration.setPostLogoutPath("/post-logout"); configureDispatcher(oidcClientConfiguration, new Dispatcher() { @Override public MockResponse dispatch(RecordedRequest request) { diff --git a/http/oidc/src/test/java/org/wildfly/security/http/oidc/LogoutConfigurationOptionsTest.java b/http/oidc/src/test/java/org/wildfly/security/http/oidc/LogoutConfigurationOptionsTest.java new file mode 100644 index 0000000000..c6fb24cf75 --- /dev/null +++ b/http/oidc/src/test/java/org/wildfly/security/http/oidc/LogoutConfigurationOptionsTest.java @@ -0,0 +1,118 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2024 Red Hat, Inc., and individual 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.wildfly.security.http.oidc; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +/* + Verify that invalid logout config options values are flagged. + */ +public class LogoutConfigurationOptionsTest { + private OidcJsonConfiguration oidcJsonConfiguration; + + @Before + public void before() { + oidcJsonConfiguration = new OidcJsonConfiguration(); + // minimum required options + oidcJsonConfiguration.setRealm("realm"); + oidcJsonConfiguration.setResource("resource"); + oidcJsonConfiguration.setClientId("clientId"); + oidcJsonConfiguration.setAuthServerUrl("AuthServerUrl"); + } + + @After + public void after() { + System.clearProperty(Oidc.LOGOUT_PATH); + System.clearProperty(Oidc.LOGOUT_CALLBACK_PATH); + System.clearProperty(Oidc.POST_LOGOUT_PATH); + } + + @Test + public void testLogoutPath() { + + try { + System.setProperty(Oidc.LOGOUT_PATH, " "); + OidcClientConfigurationBuilder.build(oidcJsonConfiguration); + fail("Empty " +Oidc.LOGOUT_PATH+ " is invalid"); + } catch (Exception e) { + assertTrue(e.getMessage().endsWith(Oidc.LOGOUT_PATH)); + } + + try { + System.setProperty(Oidc.LOGOUT_PATH, "/"); + OidcClientConfigurationBuilder.build(oidcJsonConfiguration); + fail("/ in " +Oidc.LOGOUT_PATH+ " is invalid"); + } catch (Exception e) { + assertTrue(e.getMessage().endsWith(Oidc.LOGOUT_PATH)); + } + } + + @Test + public void testCallbackLogoutPath() { + + try { + System.setProperty(Oidc.LOGOUT_CALLBACK_PATH, " "); + OidcClientConfigurationBuilder.build(oidcJsonConfiguration); + fail("Empty " + Oidc.LOGOUT_CALLBACK_PATH + " is invalid"); + } catch (Exception e) { + assertTrue(e.getMessage().endsWith(Oidc.LOGOUT_CALLBACK_PATH)); + } + + try { + System.setProperty(Oidc.LOGOUT_CALLBACK_PATH, "/"); + OidcClientConfigurationBuilder.build(oidcJsonConfiguration); + fail("/ in " + Oidc.LOGOUT_CALLBACK_PATH + " is invalid"); + } catch (Exception e) { + assertTrue(e.getMessage().endsWith(Oidc.LOGOUT_CALLBACK_PATH)); + } + + try { + System.setProperty(Oidc.LOGOUT_PATH, "/mylogout"); + System.setProperty(Oidc.LOGOUT_CALLBACK_PATH, "/more/mylogout"); + OidcClientConfigurationBuilder.build(oidcJsonConfiguration); + fail("Identical paths is invalid"); + } catch (Exception e) { + assertTrue(e.getMessage().contains("ELY23072")); + } + } + + @Test + public void testPostLogoutPath() { + + try { + System.setProperty(Oidc.POST_LOGOUT_PATH, " "); + OidcClientConfigurationBuilder.build(oidcJsonConfiguration); + fail("Empty " +Oidc.POST_LOGOUT_PATH+ " is invalid"); + } catch (Exception e) { + assertTrue(e.getMessage().endsWith(Oidc.POST_LOGOUT_PATH)); + } + + try { + System.setProperty(Oidc.POST_LOGOUT_PATH, "/"); + OidcClientConfigurationBuilder.build(oidcJsonConfiguration); + fail("/ in " + Oidc.POST_LOGOUT_PATH + " is invalid"); + } catch (Exception e) { + assertTrue(e.getMessage().endsWith(Oidc.POST_LOGOUT_PATH)); + } + } +} diff --git a/http/oidc/src/test/java/org/wildfly/security/http/oidc/OidcBaseTest.java b/http/oidc/src/test/java/org/wildfly/security/http/oidc/OidcBaseTest.java index b8a14b0d98..4f1028f829 100644 --- a/http/oidc/src/test/java/org/wildfly/security/http/oidc/OidcBaseTest.java +++ b/http/oidc/src/test/java/org/wildfly/security/http/oidc/OidcBaseTest.java @@ -1,6 +1,6 @@ /* * JBoss, Home of Professional Open Source. - * Copyright 2022 Red Hat, Inc., and individual contributors + * Copyright 2024 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -212,7 +212,7 @@ public MockResponse dispatch(RecordedRequest recordedRequest) throws Interrupted } protected static Dispatcher createAppResponse(HttpServerAuthenticationMechanism mechanism, int expectedStatusCode, String expectedLocation, String clientPageText, - Map attachments) { + Map sessionScopeAttachments) { return new Dispatcher() { @Override public MockResponse dispatch(RecordedRequest recordedRequest) throws InterruptedException { @@ -225,8 +225,8 @@ public MockResponse dispatch(RecordedRequest recordedRequest) throws Interrupted TestingHttpServerResponse response = request.getResponse(); assertEquals(expectedStatusCode, response.getStatusCode()); assertEquals(expectedLocation, response.getLocation()); - for (String key : request.getAttachments().keySet()) { - attachments.put(key, request.getAttachments().get(key)); + for (String key : request.getSessionScopeAttachments().keySet()) { + sessionScopeAttachments.put(key, request.getSessionScopeAttachments().get(key)); } return new MockResponse().setBody(clientPageText); } catch (Exception e) { @@ -240,7 +240,7 @@ public MockResponse dispatch(RecordedRequest recordedRequest) throws Interrupted } protected static Dispatcher createAppResponse(HttpServerAuthenticationMechanism mechanism, String clientPageText, - Map attachments, String tenant, boolean sameTenant) { + Map sessionScopeAttachments, String tenant, boolean sameTenant) { return new Dispatcher() { @Override public MockResponse dispatch(RecordedRequest recordedRequest) throws InterruptedException { @@ -248,7 +248,7 @@ public MockResponse dispatch(RecordedRequest recordedRequest) throws Interrupted if (path.contains("/" + CLIENT_APP + "/" + tenant)) { try { TestingHttpServerRequest request = new TestingHttpServerRequest(new String[0], - new URI(recordedRequest.getRequestUrl().toString()), attachments); + new URI(recordedRequest.getRequestUrl().toString()), sessionScopeAttachments); mechanism.evaluateRequest(request); TestingHttpServerResponse response = request.getResponse(); if (sameTenant) { @@ -279,6 +279,7 @@ static WebClient getWebClient() { WebClient webClient = new WebClient(); webClient.setCssErrorHandler(new SilentCssErrorHandler()); webClient.setJavaScriptErrorListener(new SilentJavaScriptErrorListener()); + webClient.getOptions().setMaxInMemory(50000 * 1024); return webClient; } diff --git a/tests/base/src/test/java/org/wildfly/security/http/impl/AbstractBaseHttpTest.java b/tests/base/src/test/java/org/wildfly/security/http/impl/AbstractBaseHttpTest.java index 9fe171c921..962c13fef0 100644 --- a/tests/base/src/test/java/org/wildfly/security/http/impl/AbstractBaseHttpTest.java +++ b/tests/base/src/test/java/org/wildfly/security/http/impl/AbstractBaseHttpTest.java @@ -1,6 +1,6 @@ /* * JBoss, Home of Professional Open Source. - * Copyright 2017 Red Hat, Inc., and individual contributors + * Copyright 2024 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,9 +27,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.net.InetSocketAddress; -import java.net.MalformedURLException; import java.net.URI; -import java.net.URISyntaxException; import java.security.NoSuchAlgorithmException; import java.security.Principal; import java.security.cert.Certificate; @@ -157,10 +155,9 @@ protected static class TestingHttpServerRequest implements HttpServerRequest { private List cookies; private String requestMethod = "GET"; private Map> requestHeaders = new HashMap<>(); - private Map attachments = new HashMap<>(); - private Map scopes = new HashMap<>(); - private HttpScope sessionScope; private X500Principal testPrincipal = null; + private Map sessionScopeAttachments = new HashMap<>(); + private HttpScope sessionScope; public TestingHttpServerRequest(String[] authorization) { if (authorization != null) { @@ -188,14 +185,14 @@ public TestingHttpServerRequest(String[] authorization, URI requestURI) { this.cookies = new ArrayList<>(); } - public TestingHttpServerRequest(String[] authorization, URI requestURI, Map attachments) { + public TestingHttpServerRequest(String[] authorization, URI requestURI, Map sessionScopeAttachments) { if (authorization != null) { requestHeaders.put(AUTHORIZATION, Arrays.asList(authorization)); } this.remoteUser = null; this.requestURI = requestURI; this.cookies = new ArrayList<>(); - this.attachments = attachments; + this.sessionScopeAttachments = sessionScopeAttachments; } public TestingHttpServerRequest(String[] authorization, URI requestURI, List cookies) { @@ -216,10 +213,6 @@ public TestingHttpServerRequest(Map> requestHeaders, URI re } public TestingHttpServerRequest(String[] authorization, URI requestURI, String cookie) { - this(authorization, requestURI, cookie, null); - } - - public TestingHttpServerRequest(String[] authorization, URI requestURI, String cookie, HttpScope sessionScope) { if (authorization != null) { requestHeaders.put(AUTHORIZATION, Arrays.asList(authorization)); } @@ -231,14 +224,14 @@ public TestingHttpServerRequest(String[] authorization, URI requestURI, String c final String cookieValue = cookie.substring(cookie.indexOf('=') + 1); cookies.add(HttpServerCookie.getInstance(cookieName, cookieValue, null, -1, "/", false, 0, true)); } - this.sessionScope = sessionScope; } - public TestingHttpServerRequest(RecordedRequest serverRequest, HttpScope sessionScope) throws URISyntaxException { - this(new String[0], new URI(serverRequest.getRequestUrl().toString()), serverRequest.getHeader("Cookie"), sessionScope); - this.requestMethod = serverRequest.getMethod(); - this.body = serverRequest.getBody().readUtf8(); - this.contentType = serverRequest.getHeader("Content-Type"); + public TestingHttpServerRequest(RecordedRequest request, HttpScope sessionScope) { + this(new String[0], request.getRequestUrl().uri(), request.getHeader("Cookie")); + this.requestMethod = request.getMethod(); + this.body = request.getBody().readUtf8(); + this.contentType = request.getHeader("Content-Type"); + this.sessionScope = sessionScope; } public Status getResult() { @@ -312,11 +305,7 @@ public URI getRequestURI() { } public String getRequestPath() { - try { - return requestURI.toURL().getPath(); - } catch (MalformedURLException cause) { - throw new RuntimeException("Mal-formed request URL", cause); - } + return requestURI.getPath(); } public Map> getParameters() { @@ -371,52 +360,48 @@ public boolean resumeRequest() { public HttpScope getScope(Scope scope) { if (scope.equals(Scope.SSL_SESSION)) { return null; - } - - if (Scope.SESSION.equals(scope) && sessionScope != null) { + } else if (sessionScope != null) { return sessionScope; } - HttpScope httpScope = scopes.get(scope); + return new HttpScope() { - if (httpScope == null) { - return new HttpScope() { - - @Override - public boolean exists() { - return true; - } + @Override + public boolean exists() { + return true; + } - @Override - public boolean create() { - return false; - } + @Override + public boolean create() { + return false; + } - @Override - public boolean supportsAttachments() { - return true; - } + @Override + public boolean supportsAttachments() { + return true; + } - @Override - public boolean supportsInvalidation() { - return true; - } + @Override + public boolean supportsInvalidation() { + return false; + } - @Override - public void setAttachment(String key, Object value) { - attachments.put(key, value); + @Override + public void setAttachment(String key, Object value) { + if (scope.equals(Scope.SESSION)) { + sessionScopeAttachments.put(key, value); } + } - @Override - public Object getAttachment(String key) { - return attachments.get(key); + @Override + public Object getAttachment(String key) { + if (scope.equals(Scope.SESSION)) { + return sessionScopeAttachments.get(key); + } else { + return null; } - - }; - } - scopes.put(scope, httpScope); - - return httpScope; + } + }; } public Collection getScopeIds(Scope scope) { @@ -424,10 +409,7 @@ public Collection getScopeIds(Scope scope) { } public HttpScope getScope(Scope scope, String id) { - if (Scope.SESSION.equals(scope) && sessionScope != null) { - return sessionScope; - } - return scopes.get(scope); + throw new IllegalStateException(); } public void setRemoteUser(String remoteUser) { @@ -439,8 +421,8 @@ public String getRemoteUser() { return remoteUser; } - public Map getAttachments() { - return attachments; + public Map getSessionScopeAttachments() { + return sessionScopeAttachments; } } From 509a811e2a976bd6d9aaa1f2c4af12e2406b821f Mon Sep 17 00:00:00 2001 From: Farah Juma Date: Fri, 29 Nov 2024 17:12:02 -0500 Subject: [PATCH 3/6] [ELY-2534] Restore the changes made so that back-channel logout handling doesn't rely on an active session and ensure the session will get invalidated appropriately upon subsequent requests --- .../oidc/AuthenticatedActionsHandler.java | 5 -- .../security/http/oidc/LogoutHandler.java | 51 ++++++++----------- .../oidc/OidcAuthenticationMechanism.java | 9 +++- 3 files changed, 29 insertions(+), 36 deletions(-) diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/AuthenticatedActionsHandler.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/AuthenticatedActionsHandler.java index 305d825570..8197c38a92 100644 --- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/AuthenticatedActionsHandler.java +++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/AuthenticatedActionsHandler.java @@ -37,7 +37,6 @@ */ public class AuthenticatedActionsHandler { - private static LogoutHandler logoutHandler = new LogoutHandler(); private OidcClientConfiguration deployment; private OidcHttpFacade facade; @@ -55,10 +54,6 @@ public boolean handledRequest() { return true; } - if (logoutHandler.tryLogout(facade)) { - return true; - } - return false; } diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/LogoutHandler.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/LogoutHandler.java index c00d53aa4d..92faf97757 100644 --- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/LogoutHandler.java +++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/LogoutHandler.java @@ -61,7 +61,6 @@ protected boolean removeEldestEntry(Map.Entry e boolean tryLogout(OidcHttpFacade facade) { RefreshableOidcSecurityContext securityContext = getSecurityContext(facade); - if (securityContext == null) { // no active session return false; @@ -80,8 +79,14 @@ boolean tryLogout(OidcHttpFacade facade) { } if (isLogoutCallbackPath(facade)) { - handleLogoutRequest(facade); - return true; + if (isFrontChannel(facade)) { + handleFrontChannelLogoutRequest(facade); + return true; + } else { + // we have an active session, should have received a GET logout request + facade.getResponse().setStatus(HttpStatus.SC_METHOD_NOT_ALLOWED); + facade.authenticationFailed(); + } } return false; @@ -89,12 +94,12 @@ boolean tryLogout(OidcHttpFacade facade) { private boolean isSessionMarkedForInvalidation(OidcHttpFacade facade) { RefreshableOidcSecurityContext securityContext = getSecurityContext(facade); + IDToken idToken = securityContext.getIDToken(); if (idToken == null) { return false; } - return sessionsMarkedForInvalidation.remove(idToken.getSid()) != null; } @@ -123,20 +128,17 @@ private void redirectEndSessionEndpoint(OidcHttpFacade facade) { facade.getResponse().setHeader(HttpConstants.LOCATION, logoutUri); } - private void handleLogoutRequest(OidcHttpFacade facade) { - if (isFrontChannel(facade)) { - handleFrontChannelLogoutRequest(facade); - } else if (isBackChannel(facade)) { - handleBackChannelLogoutRequest(facade); - } else { - // logout requests should arrive either as a HTTP GET or POST - facade.getResponse().setStatus(HttpStatus.SC_METHOD_NOT_ALLOWED); - facade.authenticationFailed(); + boolean tryBackChannelLogout(OidcHttpFacade facade) { + if (isLogoutCallbackPath(facade)) { + if (isBackChannel(facade)) { + handleBackChannelLogoutRequest(facade); + return true; + } } + return false; } private void handleBackChannelLogoutRequest(OidcHttpFacade facade) { - RefreshableOidcSecurityContext securityContext = getSecurityContext(facade); String logoutToken = facade.getRequest().getFirstParam(LOGOUT_TOKEN_PARAM); TokenValidator tokenValidator = TokenValidator.builder(facade.getOidcClientConfiguration()) .setSkipExpirationValidator() @@ -169,7 +171,7 @@ private void handleBackChannelLogoutRequest(OidcHttpFacade facade) { } log.debug("Marking session for invalidation during back-channel logout"); - sessionsMarkedForInvalidation.put(sessionId, securityContext.getOidcClientConfiguration()); + sessionsMarkedForInvalidation.put(sessionId, facade.getOidcClientConfiguration()); } private void handleFrontChannelLogoutRequest(OidcHttpFacade facade) { @@ -213,9 +215,8 @@ private String getRedirectUri(OidcHttpFacade facade) { return uri; } - private boolean isLogoutCallbackPath(OidcHttpFacade facade) { + boolean isLogoutCallbackPath(OidcHttpFacade facade) { String path = facade.getRequest().getRelativePath(); - String xx = getLogoutCallbackPath(facade); // rls debug only return path.endsWith(getLogoutCallbackPath(facade)); } @@ -225,17 +226,7 @@ private boolean isRpInitiatedLogoutPath(OidcHttpFacade facade) { } private boolean isSessionRequiredOnLogout(OidcHttpFacade facade) { - return getOidcClientConfiguration(facade).isSessionRequiredOnLogout(); - } - - private OidcClientConfiguration getOidcClientConfiguration(OidcHttpFacade facade) { - RefreshableOidcSecurityContext securityContext = getSecurityContext(facade); - - if (securityContext == null) { - return null; - } - - return securityContext.getOidcClientConfiguration(); + return facade.getOidcClientConfiguration().isSessionRequiredOnLogout(); } private RefreshableOidcSecurityContext getSecurityContext(OidcHttpFacade facade) { @@ -251,10 +242,10 @@ private RefreshableOidcSecurityContext getSecurityContext(OidcHttpFacade facade) } private String getLogoutPath(OidcHttpFacade facade) { - return getOidcClientConfiguration(facade).getLogoutPath(); + return facade.getOidcClientConfiguration().getLogoutPath(); } private String getLogoutCallbackPath(OidcHttpFacade facade) { - return getOidcClientConfiguration(facade).getLogoutCallbackPath(); + return facade.getOidcClientConfiguration().getLogoutCallbackPath(); } private boolean isBackChannel(OidcHttpFacade facade) { diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcAuthenticationMechanism.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcAuthenticationMechanism.java index fecde05ee5..29c4ecb743 100644 --- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcAuthenticationMechanism.java +++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcAuthenticationMechanism.java @@ -87,7 +87,7 @@ public void evaluateRequest(HttpServerRequest request) throws HttpAuthentication AuthOutcome outcome = authenticator.authenticate(); if (AuthOutcome.AUTHENTICATED.equals(outcome)) { if (new AuthenticatedActionsHandler(oidcClientConfiguration, httpFacade).handledRequest() - ) { + || logoutHandler.tryLogout(httpFacade)) { httpFacade.authenticationInProgress(); } else { httpFacade.authenticationComplete(); @@ -95,6 +95,13 @@ public void evaluateRequest(HttpServerRequest request) throws HttpAuthentication return; } + if (AuthOutcome.NOT_ATTEMPTED.equals(outcome)) { + if (logoutHandler.tryBackChannelLogout(httpFacade)) { + httpFacade.authenticationInProgress(); + return; + } + } + AuthChallenge challenge = authenticator.getChallenge(); if (challenge != null) { httpFacade.noAuthenticationInProgress(challenge); From 77d49117816c6645645ada110313b22e594b5342 Mon Sep 17 00:00:00 2001 From: Farah Juma Date: Tue, 3 Dec 2024 12:25:57 -0500 Subject: [PATCH 4/6] [ELY-2534] Fix back-channel logout by moving the isSessionMarkedForInvalidation check to before the authenticator#authenticate call and update the check to not remove the session from the map to ensure that the user gets logged out from any other apps too --- .../security/http/oidc/LogoutHandler.java | 21 +++++++++---------- .../oidc/OidcAuthenticationMechanism.java | 5 +++++ 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/LogoutHandler.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/LogoutHandler.java index 92faf97757..361991eab5 100644 --- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/LogoutHandler.java +++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/LogoutHandler.java @@ -29,6 +29,8 @@ import org.apache.http.client.utils.URIBuilder; import org.jose4j.jwt.JwtClaims; import org.wildfly.security.http.HttpConstants; +import org.wildfly.security.http.HttpScope; +import org.wildfly.security.http.Scope; import org.wildfly.security.http.oidc.OidcHttpFacade.Request; /** @@ -66,13 +68,6 @@ boolean tryLogout(OidcHttpFacade facade) { return false; } - if (isSessionMarkedForInvalidation(facade)) { - // session marked for invalidation, invalidate it - log.debug("Invalidating pending logout session"); - facade.getTokenStore().logout(false); - return true; - } - if (isRpInitiatedLogoutPath(facade)) { redirectEndSessionEndpoint(facade); return true; @@ -92,15 +87,19 @@ boolean tryLogout(OidcHttpFacade facade) { return false; } - private boolean isSessionMarkedForInvalidation(OidcHttpFacade facade) { - RefreshableOidcSecurityContext securityContext = getSecurityContext(facade); - + boolean isSessionMarkedForInvalidation(OidcHttpFacade facade) { + HttpScope session = facade.getScope(Scope.SESSION); + if (session == null || ! session.exists()) return false; + RefreshableOidcSecurityContext securityContext = (RefreshableOidcSecurityContext) session.getAttachment(OidcSecurityContext.class.getName()); + if (securityContext == null) { + return false; + } IDToken idToken = securityContext.getIDToken(); if (idToken == null) { return false; } - return sessionsMarkedForInvalidation.remove(idToken.getSid()) != null; + return sessionsMarkedForInvalidation.containsKey(idToken.getSid()); } private void redirectEndSessionEndpoint(OidcHttpFacade facade) { diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcAuthenticationMechanism.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcAuthenticationMechanism.java index 29c4ecb743..1a4963ea6e 100644 --- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcAuthenticationMechanism.java +++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcAuthenticationMechanism.java @@ -76,6 +76,11 @@ public void evaluateRequest(HttpServerRequest request) throws HttpAuthentication } RequestAuthenticator authenticator = createRequestAuthenticator(httpFacade, oidcClientConfiguration); + if (logoutHandler.isSessionMarkedForInvalidation(httpFacade)) { + // session marked for invalidation, invalidate it + log.debug("Invalidating pending logout session"); + httpFacade.getTokenStore().logout(false); + } httpFacade.getTokenStore().checkCurrentToken(); if ((oidcClientConfiguration.getAuthServerBaseUrl() != null && keycloakPreActions(httpFacade, oidcClientConfiguration)) || preflightCors(httpFacade, oidcClientConfiguration)) { From 3aebc41a8142a29f4327587ec53300ad6e0ba0f6 Mon Sep 17 00:00:00 2001 From: Farah Juma Date: Tue, 3 Dec 2024 17:12:36 -0500 Subject: [PATCH 5/6] [ELY-2534] Use the client ID and sid from the ID token in the key for the sessionsMarkedForInvalidation map --- .../org/wildfly/security/http/oidc/LogoutHandler.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/LogoutHandler.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/LogoutHandler.java index 361991eab5..ac33649bb9 100644 --- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/LogoutHandler.java +++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/LogoutHandler.java @@ -42,6 +42,7 @@ final class LogoutHandler { public static final String ID_TOKEN_HINT_PARAM = "id_token_hint"; private static final String LOGOUT_TOKEN_PARAM = "logout_token"; private static final String LOGOUT_TOKEN_TYPE = "Logout"; + private static final String CLIENT_ID_SID_SEPARATOR = "-"; public static final String SID = "sid"; public static final String ISS = "iss"; @@ -99,7 +100,7 @@ boolean isSessionMarkedForInvalidation(OidcHttpFacade facade) { if (idToken == null) { return false; } - return sessionsMarkedForInvalidation.containsKey(idToken.getSid()); + return sessionsMarkedForInvalidation.remove(getSessionKey(facade, idToken.getSid())) != null; } private void redirectEndSessionEndpoint(OidcHttpFacade facade) { @@ -170,7 +171,11 @@ private void handleBackChannelLogoutRequest(OidcHttpFacade facade) { } log.debug("Marking session for invalidation during back-channel logout"); - sessionsMarkedForInvalidation.put(sessionId, facade.getOidcClientConfiguration()); + sessionsMarkedForInvalidation.put(getSessionKey(facade, sessionId), facade.getOidcClientConfiguration()); + } + + private String getSessionKey(OidcHttpFacade facade, String sessionId) { + return facade.getOidcClientConfiguration().getClientId() + CLIENT_ID_SID_SEPARATOR + sessionId; } private void handleFrontChannelLogoutRequest(OidcHttpFacade facade) { From 9a61d67d64622049c8f5fca98fe1606720a4ff0e Mon Sep 17 00:00:00 2001 From: R Searls Date: Thu, 7 Nov 2024 16:30:48 -0500 Subject: [PATCH 6/6] [ELY-2534] OIDC logout support --- .../security/http/oidc/LogoutHandler.java | 12 ++++++++++-- .../http/oidc/OidcAuthenticationMechanism.java | 2 +- .../oidc/OidcClientConfigurationBuilder.java | 3 +++ .../http/oidc/OidcRequestAuthenticator.java | 1 - .../http/oidc/OidcSessionTokenStore.java | 17 ++++++++++++----- .../security/http/oidc/AbstractLogoutTest.java | 7 ++++--- .../http/oidc/BackChannelLogoutTest.java | 7 +++---- .../http/oidc/FrontChannelLogoutTest.java | 9 ++++----- .../http/impl/AbstractBaseHttpTest.java | 3 +++ 9 files changed, 40 insertions(+), 21 deletions(-) diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/LogoutHandler.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/LogoutHandler.java index ac33649bb9..cda713dc61 100644 --- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/LogoutHandler.java +++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/LogoutHandler.java @@ -63,19 +63,24 @@ protected boolean removeEldestEntry(Map.Entry e }); boolean tryLogout(OidcHttpFacade facade) { + log.trace("tryLogout entered"); RefreshableOidcSecurityContext securityContext = getSecurityContext(facade); if (securityContext == null) { // no active session + log.trace("tryLogout securityContext == null"); return false; } if (isRpInitiatedLogoutPath(facade)) { + log.trace("isRpInitiatedLogoutPath"); redirectEndSessionEndpoint(facade); return true; } if (isLogoutCallbackPath(facade)) { + log.trace("isLogoutCallbackPath"); if (isFrontChannel(facade)) { + log.trace("isFrontChannel"); handleFrontChannelLogoutRequest(facade); return true; } else { @@ -84,7 +89,6 @@ boolean tryLogout(OidcHttpFacade facade) { facade.authenticationFailed(); } } - return false; } @@ -119,6 +123,7 @@ private void redirectEndSessionEndpoint(OidcHttpFacade facade) { } logoutUri = redirectUriBuilder.build().toString(); + log.trace("redirectEndSessionEndpoint path: " + redirectUriBuilder.toString()); } catch (URISyntaxException e) { throw new RuntimeException(e); } @@ -129,8 +134,11 @@ private void redirectEndSessionEndpoint(OidcHttpFacade facade) { } boolean tryBackChannelLogout(OidcHttpFacade facade) { + log.trace("tryBackChannelLogout entered"); if (isLogoutCallbackPath(facade)) { + log.trace("isLogoutCallbackPath"); if (isBackChannel(facade)) { + log.trace("isBackChannel"); handleBackChannelLogoutRequest(facade); return true; } @@ -219,7 +227,7 @@ private String getRedirectUri(OidcHttpFacade facade) { return uri; } - boolean isLogoutCallbackPath(OidcHttpFacade facade) { + private boolean isLogoutCallbackPath(OidcHttpFacade facade) { String path = facade.getRequest().getRelativePath(); return path.endsWith(getLogoutCallbackPath(facade)); } diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcAuthenticationMechanism.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcAuthenticationMechanism.java index 1a4963ea6e..a33d6b261d 100644 --- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcAuthenticationMechanism.java +++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcAuthenticationMechanism.java @@ -60,7 +60,7 @@ public String getMechanismName() { @Override public void evaluateRequest(HttpServerRequest request) throws HttpAuthenticationException { - String tmpURI = request.getRequestURI().toString(); + log.debug("evaluateRequest uri: " + request.getRequestURI().toString()); OidcClientContext oidcClientContext = getOidcClientContext(request); if (oidcClientContext == null) { log.debugf("Ignoring request for path [%s] from mechanism [%s]. No client configuration context found.", request.getRequestURI(), getMechanismName()); diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcClientConfigurationBuilder.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcClientConfigurationBuilder.java index fc80ef0135..52d59e1381 100644 --- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcClientConfigurationBuilder.java +++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcClientConfigurationBuilder.java @@ -200,6 +200,7 @@ protected OidcClientConfiguration internalBuild(final OidcJsonConfiguration oidc oidcClientConfiguration.setTokenSignatureAlgorithm(oidcJsonConfiguration.getTokenSignatureAlgorithm()); String tmpLogoutPath = System.getProperty(LOGOUT_PATH); + log.debug("sysProp LOGOUT_PATH: " + (tmpLogoutPath == null ? "NULL" : tmpLogoutPath)); if (tmpLogoutPath != null) { if (isValidPath(tmpLogoutPath)) { oidcClientConfiguration.setLogoutPath(tmpLogoutPath); @@ -210,6 +211,7 @@ protected OidcClientConfiguration internalBuild(final OidcJsonConfiguration oidc String tmpLogoutCallbackPath = System.getProperty(LOGOUT_CALLBACK_PATH); + log.debug("sysProp LOGOUT_CALLBACK_PATH: " + (tmpLogoutCallbackPath == null ? "NULL" : tmpLogoutCallbackPath)); if (tmpLogoutCallbackPath != null) { if (isValidPath(tmpLogoutCallbackPath) && !tmpLogoutCallbackPath.endsWith(oidcClientConfiguration.getLogoutPath())) { @@ -225,6 +227,7 @@ protected OidcClientConfiguration internalBuild(final OidcJsonConfiguration oidc } String tmpPostLogoutPath = System.getProperty(POST_LOGOUT_PATH); + log.debug("sysProp POST_LOGOUT_PATH: " + (tmpPostLogoutPath == null ? "NULL" : tmpPostLogoutPath)); if (tmpPostLogoutPath != null) { if (isValidPath(tmpPostLogoutPath)) { oidcClientConfiguration.setPostLogoutPath(tmpPostLogoutPath); diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcRequestAuthenticator.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcRequestAuthenticator.java index 5ef5c26122..0588c16568 100644 --- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcRequestAuthenticator.java +++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcRequestAuthenticator.java @@ -184,7 +184,6 @@ protected String getCode() { protected String getRedirectUri(String state) { String url = getRequestUrl(); log.debugf("callback uri: %s", url); - try { if (! facade.getRequest().isSecure() && deployment.getSSLRequired().isRequired(facade.getRequest().getRemoteAddr())) { int port = getSSLRedirectPort(); diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcSessionTokenStore.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcSessionTokenStore.java index cb6206177c..6762afc0f4 100644 --- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcSessionTokenStore.java +++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcSessionTokenStore.java @@ -45,21 +45,29 @@ public OidcSessionTokenStore(OidcHttpFacade httpFacade) { @Override public void checkCurrentToken() { HttpScope session = httpFacade.getScope(Scope.SESSION); - if (session == null || ! session.exists()) return; + if (session == null || ! session.exists()) { + return; + } RefreshableOidcSecurityContext securityContext = (RefreshableOidcSecurityContext) session.getAttachment(OidcSecurityContext.class.getName()); - if (securityContext == null) return; + if (securityContext == null) { + return; + } // just in case session got serialized if (securityContext.getOidcClientConfiguration() == null) { securityContext.setCurrentRequestInfo(httpFacade.getOidcClientConfiguration(), this); } - if (securityContext.isActive() && ! securityContext.getOidcClientConfiguration().isAlwaysRefreshToken()) return; + if (securityContext.isActive() && ! securityContext.getOidcClientConfiguration().isAlwaysRefreshToken()) { + return; + } // FYI: A refresh requires same scope, so same roles will be set. Otherwise, refresh will fail and token will // not be updated boolean success = securityContext.refreshToken(false); - if (success && securityContext.isActive()) return; + if (success && securityContext.isActive()) { + return; + } // Refresh failed, so user is already logged out from keycloak. Cleanup and expire our session session.setAttachment(OidcSecurityContext.class.getName(), null); @@ -132,7 +140,6 @@ public void saveAccountInfo(OidcAccount account) { } }); } - session.setAttachment(OidcAccount.class.getName(), account); session.setAttachment(OidcSecurityContext.class.getName(), account.getOidcSecurityContext()); diff --git a/http/oidc/src/test/java/org/wildfly/security/http/oidc/AbstractLogoutTest.java b/http/oidc/src/test/java/org/wildfly/security/http/oidc/AbstractLogoutTest.java index 35adc45ad1..85a270cfd8 100644 --- a/http/oidc/src/test/java/org/wildfly/security/http/oidc/AbstractLogoutTest.java +++ b/http/oidc/src/test/java/org/wildfly/security/http/oidc/AbstractLogoutTest.java @@ -77,7 +77,9 @@ public void onBefore() throws Exception { OidcBaseTest.client = new MockWebServer(); OidcBaseTest.client.start(new InetSocketAddress(0).getAddress(), CLIENT_PORT); configureDispatcher(); - RealmRepresentation realm = KeycloakConfiguration.getRealmRepresentation(TEST_REALM, CLIENT_ID, CLIENT_SECRET, CLIENT_HOST_NAME, CLIENT_PORT, CLIENT_APP, false); + RealmRepresentation realm = KeycloakConfiguration.getRealmRepresentation( + TEST_REALM, CLIENT_ID, CLIENT_SECRET, CLIENT_HOST_NAME, CLIENT_PORT, + CLIENT_APP, false); realm.setAccessTokenLifespan(100); realm.setSsoSessionMaxLifespan(100); @@ -161,7 +163,6 @@ public ElytronDispatcher(HttpServerAuthenticationMechanism mechanism, Dispatcher public MockResponse dispatch(RecordedRequest serverRequest) throws InterruptedException { if (beforeDispatcher != null) { MockResponse response = beforeDispatcher.dispatch(serverRequest); - if (response != null) { return response; } @@ -225,7 +226,7 @@ protected void configureDispatcher(OidcClientConfiguration clientConfig, Dispatc } protected void assertUserNotAuthenticated() { - assertNull(getCurrentSession().getAttachment(OidcAccount.class.getName())); + assertNull(getCurrentSession()); } protected void assertUserAuthenticated() { diff --git a/http/oidc/src/test/java/org/wildfly/security/http/oidc/BackChannelLogoutTest.java b/http/oidc/src/test/java/org/wildfly/security/http/oidc/BackChannelLogoutTest.java index e4fed15513..d39e5af418 100644 --- a/http/oidc/src/test/java/org/wildfly/security/http/oidc/BackChannelLogoutTest.java +++ b/http/oidc/src/test/java/org/wildfly/security/http/oidc/BackChannelLogoutTest.java @@ -55,9 +55,10 @@ private static String rewriteHost(String redirectUri) { } @Test - public void testRPInitiatedLogout() throws Exception { + public void testBackChannelLogout() throws Exception { URI requestUri = new URI(getClientUrl()); WebClient webClient = getWebClient(); + webClient.getPage(getClientUrl()); TestingHttpServerResponse response = getCurrentResponse(); assertEquals(HttpStatus.SC_MOVED_TEMPORARILY, response.getStatusCode()); @@ -73,8 +74,6 @@ public void testRPInitiatedLogout() throws Exception { // logged out after finishing the redirections during frontchannel logout assertUserAuthenticated(); webClient.getPage(getClientUrl() + getClientConfig().getLogoutPath()); - assertUserAuthenticated(); - webClient.getPage(getClientUrl()); assertUserNotAuthenticated(); } -} \ No newline at end of file +} diff --git a/http/oidc/src/test/java/org/wildfly/security/http/oidc/FrontChannelLogoutTest.java b/http/oidc/src/test/java/org/wildfly/security/http/oidc/FrontChannelLogoutTest.java index 5137d40413..ec4eeff3c5 100644 --- a/http/oidc/src/test/java/org/wildfly/security/http/oidc/FrontChannelLogoutTest.java +++ b/http/oidc/src/test/java/org/wildfly/security/http/oidc/FrontChannelLogoutTest.java @@ -49,10 +49,9 @@ protected void doConfigureClient(ClientRepresentation client) { client.setFrontchannelLogout(true); List redirectUris = client.getRedirectUris(); String redirectUri = redirectUris.get(0); - - OidcClientConfiguration ocConfig = new OidcClientConfiguration(); + OidcClientConfiguration config = new OidcClientConfiguration(); client.getAttributes().put("frontchannel.logout.url", redirectUri - + ocConfig.getLogoutCallbackPath()); + + config.getLogoutCallbackPath()); } @Test @@ -101,7 +100,7 @@ public MockResponse dispatch(RecordedRequest request) { assertTrue(page.getWebResponse().getContentAsString().contains("Welcome, authenticated user")); assertUserAuthenticated(); - HtmlPage continueLogout = webClient.getPage(getClientUrl() + "/logout"); + HtmlPage continueLogout = webClient.getPage(getClientUrl() + getClientConfig().getLogoutPath()); page = continueLogout.getElementById("continue").click(); assertUserNotAuthenticated(); assertTrue(page.getWebResponse().getContentAsString().contains("you are logged out from app")); @@ -126,4 +125,4 @@ public void testFrontChannelLogout() throws Exception { client.setDispatcher(new QueueDispatcher()); } } -} \ No newline at end of file +} diff --git a/tests/base/src/test/java/org/wildfly/security/http/impl/AbstractBaseHttpTest.java b/tests/base/src/test/java/org/wildfly/security/http/impl/AbstractBaseHttpTest.java index 962c13fef0..0938a6297e 100644 --- a/tests/base/src/test/java/org/wildfly/security/http/impl/AbstractBaseHttpTest.java +++ b/tests/base/src/test/java/org/wildfly/security/http/impl/AbstractBaseHttpTest.java @@ -358,6 +358,9 @@ public boolean resumeRequest() { } public HttpScope getScope(Scope scope) { + if (requestURI != null && "/clientApp/logout/callback".equals(requestURI.getPath())){ + return null; + } if (scope.equals(Scope.SSL_SESSION)) { return null; } else if (sessionScope != null) {