Skip to content

Commit

Permalink
fix: support id_token_hint parameter on oidc logout
Browse files Browse the repository at this point in the history
- populate the id_token from an external oidc login into the
authentication login hint
- include the id_token in an external oidc logout via the
id_token_hint parameter

Change-Id: I7e4655ac8f9867a86fac2d28fdb5639386f5f54c
  • Loading branch information
mikeroda committed Sep 16, 2024
1 parent 3daae63 commit 8106ef9
Show file tree
Hide file tree
Showing 8 changed files with 75 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.cloudfoundry.identity.uaa.authentication;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

Expand All @@ -11,6 +12,8 @@
public class UaaLoginHint implements Serializable {
private static final long serialVersionUID = 4021539346161285037L;
private String origin;
@JsonIgnore
private String idToken;
private static ObjectMapper mapper = new ObjectMapper();

public static UaaLoginHint parseRequestParameter(String loginHint) {
Expand All @@ -32,6 +35,11 @@ public UaaLoginHint(String origin) {
this.origin = origin;
}

public UaaLoginHint(String origin, String idToken) {
this.origin = origin;
this.idToken = idToken;
}

public String getOrigin() {
return origin;
}
Expand All @@ -40,6 +48,14 @@ public void setOrigin(String origin) {
this.origin = origin;
}

public String getIdToken() {
return idToken;
}

public void setIdToken(String idToken) {
this.idToken = idToken;
}

@Override
public String toString() {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ protected String determineTargetUrl(HttpServletRequest request, HttpServletRespo
if (logoutUrl == null) {
return getZoneHandler().determineTargetUrl(request, response);
} else {
return externalOAuthLogoutHandler.constructOAuthProviderLogoutUrl(request, logoutUrl, oauthConfig);
return externalOAuthLogoutHandler.constructOAuthProviderLogoutUrl(request, logoutUrl, oauthConfig, authentication);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.ObjectUtils;
import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication;
import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails;
import org.cloudfoundry.identity.uaa.authentication.UaaLoginHint;
import org.cloudfoundry.identity.uaa.authentication.manager.ExternalGroupAuthorizationEvent;
import org.cloudfoundry.identity.uaa.authentication.manager.ExternalLoginAuthenticationManager;
import org.cloudfoundry.identity.uaa.authentication.manager.InvitedUserAuthenticatedEvent;
Expand Down Expand Up @@ -353,6 +355,12 @@ protected void populateAuthenticationAttributes(UaaAuthentication authentication
authentication.setAuthenticationMethods(new HashSet<>());
}
authentication.getAuthenticationMethods().add("oauth");
ExternalOAuthCodeToken externalOAuthCodeToken = (ExternalOAuthCodeToken) request;
if (externalOAuthCodeToken.getIdToken() != null && authentication.getDetails() != null) {
UaaLoginHint loginHint = new UaaLoginHint(externalOAuthCodeToken.getOrigin(), externalOAuthCodeToken.getIdToken());
UaaAuthenticationDetails details = (UaaAuthenticationDetails)authentication.getDetails();
details.setLoginHint(loginHint);
}
super.populateAuthenticationAttributes(authentication, request, authenticationData);
}

Expand Down Expand Up @@ -557,6 +565,7 @@ else if ("code".equals(config.getResponseType()))
protected Map<String, Object> getClaimsFromToken(ExternalOAuthCodeToken codeToken,
AbstractExternalOAuthIdentityProviderDefinition config) {
String idToken = getTokenFromCode(codeToken, config);
codeToken.setIdToken(idToken);
return getClaimsFromToken(idToken, config);
}

Expand Down Expand Up @@ -822,7 +831,6 @@ public String getUsername() {
return username;
}


public List<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package org.cloudfoundry.identity.uaa.provider.oauth;

import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication;
import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails;
import org.cloudfoundry.identity.uaa.authentication.UaaLoginHint;
import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal;
import org.cloudfoundry.identity.uaa.constants.OriginKeys;
import org.cloudfoundry.identity.uaa.provider.AbstractExternalOAuthIdentityProviderDefinition;
Expand Down Expand Up @@ -52,11 +54,12 @@ protected String determineTargetUrl(final HttpServletRequest request, final Http
return defaultUrl;
}

return this.constructOAuthProviderLogoutUrl(request, logoutUrl, oauthConfig);
return this.constructOAuthProviderLogoutUrl(request, logoutUrl, oauthConfig, authentication);
}

public String constructOAuthProviderLogoutUrl(final HttpServletRequest request, final String logoutUrl,
final AbstractExternalOAuthIdentityProviderDefinition<OIDCIdentityProviderDefinition> oauthConfig) {
final AbstractExternalOAuthIdentityProviderDefinition<OIDCIdentityProviderDefinition> oauthConfig,
final Authentication authentication) {
final StringBuilder oauthLogoutUriBuilder = new StringBuilder(request.getRequestURL());
if (StringUtils.hasText(request.getQueryString())) {
oauthLogoutUriBuilder.append("?");
Expand All @@ -68,6 +71,14 @@ public String constructOAuthProviderLogoutUrl(final HttpServletRequest request,
sb.append(oauthLogoutUri);
sb.append("&client_id=");
sb.append(oauthConfig.getRelyingPartyId());

if (authentication instanceof UaaAuthentication && authentication.getDetails() != null) {
UaaLoginHint loginHint = ((UaaAuthenticationDetails)authentication.getDetails()).getLoginHint();
if (loginHint != null && loginHint.getIdToken() != null) {
sb.append("&id_token_hint=");
sb.append(loginHint.getIdToken());
}
}
return sb.toString();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,11 @@ public void testParseHintOrigin() {
assertNotNull(hint);
assertEquals("ldap", hint.getOrigin());
}

@Test
public void testParseHintIdTokenNotDeserialized() {
UaaLoginHint hint = UaaLoginHint.parseRequestParameter("{\"idToken\":\"token\"}");
assertNotNull(hint);
assertNull(hint.getOrigin());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ public void test_external_client_redirect() {
configuration.getLinks().getLogout().setWhitelist(Collections.singletonList("http://somethingelse.com"));
configuration.getLinks().getLogout().setDisableRedirectParameter(false);
when(oAuthLogoutHandler.getLogoutUrl(null)).thenReturn("");
when(oAuthLogoutHandler.constructOAuthProviderLogoutUrl(request, "", null)).thenReturn("/login");
when(oAuthLogoutHandler.constructOAuthProviderLogoutUrl(request, "", null, null)).thenReturn("/login");
request.setParameter("redirect", "http://testing.com");
request.setParameter(CLIENT_ID, CLIENT_ID);
assertEquals("/login", handler.determineTargetUrl(request, response));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import org.apache.commons.lang3.RandomStringUtils;
import org.cloudfoundry.identity.uaa.authentication.AccountNotPreCreatedException;
import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication;
import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails;
import org.cloudfoundry.identity.uaa.authentication.UaaLoginHint;
import org.cloudfoundry.identity.uaa.authentication.event.IdentityProviderAuthenticationSuccessEvent;
import org.cloudfoundry.identity.uaa.authentication.manager.ExternalGroupAuthorizationEvent;
import org.cloudfoundry.identity.uaa.authentication.manager.InvitedUserAuthenticatedEvent;
Expand Down Expand Up @@ -1104,6 +1106,18 @@ void authentication_context_transfers_to_authentication() {
assertThat(authentication.getAuthContextClassRef(), containsInAnyOrder("urn:oasis:names:tc:SAML:2.0:ac:classes:Password"));
}

@Test
void login_hint_transfers_to_authentication() {
addTheUserOnAuth();
mockToken();
UaaAuthentication authentication = (UaaAuthentication) externalOAuthAuthenticationManager.authenticate(xCodeToken);
UaaLoginHint loginHint = ((UaaAuthenticationDetails)authentication.getDetails()).getLoginHint();
assertNotNull(authentication);
assertNotNull(loginHint);
assertEquals(ORIGIN, loginHint.getOrigin());
assertEquals(xCodeToken.getIdToken(), loginHint.getIdToken());
}

@Test
void authentication_context_when_missing() {
addTheUserOnAuth();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package org.cloudfoundry.identity.uaa.provider.oauth;

import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication;
import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails;
import org.cloudfoundry.identity.uaa.authentication.UaaLoginHint;
import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal;
import org.cloudfoundry.identity.uaa.constants.OriginKeys;
import org.cloudfoundry.identity.uaa.provider.IdentityProvider;
Expand Down Expand Up @@ -40,6 +42,7 @@ class ExternalOAuthLogoutHandlerTest {
private IdentityProviderProvisioning providerProvisioning = mock(IdentityProviderProvisioning.class);
private OidcMetadataFetcher oidcMetadataFetcher = mock(OidcMetadataFetcher.class);
private UaaAuthentication uaaAuthentication = mock(UaaAuthentication.class);
private UaaAuthenticationDetails uaaAuthenticationDetails = mock(UaaAuthenticationDetails.class);
private UaaPrincipal uaaPrincipal = mock(UaaPrincipal.class);
private IdentityZoneManager identityZoneManager = mock(IdentityZoneManager.class);

Expand Down Expand Up @@ -71,6 +74,7 @@ public void setUp() throws MalformedURLException {
when(providerProvisioning.retrieveByOrigin("test", "uaa")).thenReturn(identityProvider);
when(uaaAuthentication.getPrincipal()).thenReturn(uaaPrincipal);
when(uaaAuthentication.getAuthenticationMethods()).thenReturn(Set.of("ext", "oauth"));
when(uaaAuthentication.getDetails()).thenReturn(uaaAuthenticationDetails);
when(uaaPrincipal.getOrigin()).thenReturn("test");
when(uaaPrincipal.getZoneId()).thenReturn("uaa");
when(identityZoneManager.getCurrentIdentityZone()).thenReturn(uaaZone);
Expand All @@ -94,6 +98,15 @@ void determineTargetUrl() {
oAuthLogoutHandler.determineTargetUrl(request, response, uaaAuthentication));
}

@Test
void determineTargetUrlWithIdTokenHint() {
request.setQueryString("parameter=value");
UaaLoginHint loginHint = new UaaLoginHint("test", "token");
when(uaaAuthenticationDetails.getLoginHint()).thenReturn(loginHint);
assertEquals("http://localhost:8080/uaa/logout.do?post_logout_redirect_uri=http%3A%2F%2Flocalhost%3Fparameter%3Dvalue&client_id=id&id_token_hint=token",
oAuthLogoutHandler.determineTargetUrl(request, response, uaaAuthentication));
}

@Test
void determineDefaultTargetUrl() {
oAuthIdentityProviderDefinition.setLogoutUrl(null);
Expand All @@ -104,7 +117,7 @@ void determineDefaultTargetUrl() {

@Test
void constructOAuthProviderLogoutUrl() {
oAuthLogoutHandler.constructOAuthProviderLogoutUrl(request, "", oAuthIdentityProviderDefinition);
oAuthLogoutHandler.constructOAuthProviderLogoutUrl(request, "", oAuthIdentityProviderDefinition, uaaAuthentication);
}

@Test
Expand Down

0 comments on commit 8106ef9

Please sign in to comment.