Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated this branch to preserve some individual commits for better readability #2

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,22 @@
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;

/**
* @author <a href="mailto:[email protected]">Pedro Igor</a>
*/
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";
private static final String CLIENT_ID_SID_SEPARATOR = "-";
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
Expand All @@ -60,27 +63,24 @@ protected boolean removeEldestEntry(Map.Entry<String, OidcClientConfiguration> 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 (isSessionMarkedForInvalidation(facade)) {
// session marked for invalidation, invalidate it
log.debug("Invalidating pending logout session");
facade.getTokenStore().logout(false);
return true;
}

if (isRpInitiatedLogoutUri(facade)) {
if (isRpInitiatedLogoutPath(facade)) {
log.trace("isRpInitiatedLogoutPath");
redirectEndSessionEndpoint(facade);
return true;
}

if (isLogoutCallbackUri(facade)) {
if (isLogoutCallbackPath(facade)) {
log.trace("isLogoutCallbackPath");
if (isFrontChannel(facade)) {
log.trace("isFrontChannel");
handleFrontChannelLogoutRequest(facade);
return true;
} else {
Expand All @@ -89,50 +89,41 @@ boolean tryLogout(OidcHttpFacade facade) {
facade.authenticationFailed();
}
}

return false;
}

boolean tryBackChannelLogout(OidcHttpFacade facade) {
if (isLogoutCallbackUri(facade)) {
if (isBackChannel(facade)) {
handleBackChannelLogoutRequest(facade);
return true;
} else {
// no active session, should have received a POST logout request
facade.getResponse().setStatus(HttpStatus.SC_METHOD_NOT_ALLOWED);
facade.authenticationFailed();
}
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;
}
return false;
}

private boolean isSessionMarkedForInvalidation(OidcHttpFacade facade) {
RefreshableOidcSecurityContext securityContext = getSecurityContext(facade);
IDToken idToken = securityContext.getIDToken();

if (idToken == null) {
return false;
}

return sessionsMarkedForInvalidation.remove(idToken.getSid()) != null;
return sessionsMarkedForInvalidation.remove(getSessionKey(facade, idToken.getSid())) != null;
}

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();
log.trace("redirectEndSessionEndpoint path: " + redirectUriBuilder.toString());
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
Expand All @@ -142,6 +133,19 @@ private void redirectEndSessionEndpoint(OidcHttpFacade facade) {
facade.getResponse().setHeader(HttpConstants.LOCATION, logoutUri);
}

boolean tryBackChannelLogout(OidcHttpFacade facade) {
log.trace("tryBackChannelLogout entered");
if (isLogoutCallbackPath(facade)) {
log.trace("isLogoutCallbackPath");
if (isBackChannel(facade)) {
log.trace("isBackChannel");
handleBackChannelLogoutRequest(facade);
return true;
}
}
return false;
}

private void handleBackChannelLogoutRequest(OidcHttpFacade facade) {
String logoutToken = facade.getRequest().getFirstParam(LOGOUT_TOKEN_PARAM);
TokenValidator tokenValidator = TokenValidator.builder(facade.getOidcClientConfiguration())
Expand Down Expand Up @@ -175,7 +179,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) {
Expand Down Expand Up @@ -210,8 +218,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);
Expand All @@ -220,14 +227,14 @@ private String getRedirectUri(OidcHttpFacade facade) {
return uri;
}

private boolean isLogoutCallbackUri(OidcHttpFacade facade) {
private boolean isLogoutCallbackPath(OidcHttpFacade facade) {
String path = facade.getRequest().getRelativePath();
return path.endsWith(getLogoutCallbackUri(facade));
return path.endsWith(getLogoutCallbackPath(facade));
}

private boolean isRpInitiatedLogoutUri(OidcHttpFacade facade) {
private boolean isRpInitiatedLogoutPath(OidcHttpFacade facade) {
String path = facade.getRequest().getRelativePath();
return path.endsWith(getLogoutUri(facade));
return path.endsWith(getLogoutPath(facade));
}

private boolean isSessionRequiredOnLogout(OidcHttpFacade facade) {
Expand All @@ -246,12 +253,11 @@ private RefreshableOidcSecurityContext getSecurityContext(OidcHttpFacade facade)
return securityContext;
}

private String getLogoutUri(OidcHttpFacade facade) {
return facade.getOidcClientConfiguration().getLogoutUrl();
private String getLogoutPath(OidcHttpFacade facade) {
return facade.getOidcClientConfiguration().getLogoutPath();
}

private String getLogoutCallbackUri(OidcHttpFacade facade) {
return facade.getOidcClientConfiguration().getLogoutCallbackUrl();
private String getLogoutCallbackPath(OidcHttpFacade facade) {
return facade.getOidcClientConfiguration().getLogoutCallbackPath();
}

private boolean isBackChannel(OidcHttpFacade facade) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
final class OidcAuthenticationMechanism implements HttpServerAuthenticationMechanism {

private static LogoutHandler logoutHandler = new LogoutHandler();

private final Map<String, ?> properties;
private final CallbackHandler callbackHandler;
private final OidcClientContext oidcClientContext;
Expand All @@ -59,6 +60,7 @@ public String getMechanismName() {

@Override
public void evaluateRequest(HttpServerRequest request) throws HttpAuthenticationException {
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());
Expand All @@ -74,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)) {
Expand All @@ -84,7 +91,8 @@ 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)) {
if (new AuthenticatedActionsHandler(oidcClientConfiguration, httpFacade).handledRequest()
|| logoutHandler.tryLogout(httpFacade)) {
httpFacade.authenticationInProgress();
} else {
httpFacade.authenticationComplete();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -338,6 +336,10 @@ public String getEndSessionEndpointUrl() {
return endSessionEndpointUrl;
}

public String getLogoutPath() {
return logoutPath;
}

public String getAccountUrl() {
resolveUrls();
return accountUrl;
Expand Down Expand Up @@ -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;
}
Expand All @@ -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;
}
Expand Down Expand Up @@ -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;
}
}
Loading