diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaLoginHint.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaLoginHint.java index fdd963b9910..8fe3e449e05 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaLoginHint.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaLoginHint.java @@ -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; @@ -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) { @@ -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; } @@ -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 { diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/ZoneAwareWhitelistLogoutHandler.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/ZoneAwareWhitelistLogoutHandler.java index 66a2ef4e2a7..048857b2c42 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/ZoneAwareWhitelistLogoutHandler.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/ZoneAwareWhitelistLogoutHandler.java @@ -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); } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManager.java index 60c1f504a1e..09bca6edcf4 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManager.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManager.java @@ -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; @@ -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); } @@ -557,6 +565,7 @@ else if ("code".equals(config.getResponseType())) protected Map getClaimsFromToken(ExternalOAuthCodeToken codeToken, AbstractExternalOAuthIdentityProviderDefinition config) { String idToken = getTokenFromCode(codeToken, config); + codeToken.setIdToken(idToken); return getClaimsFromToken(idToken, config); } @@ -822,7 +831,6 @@ public String getUsername() { return username; } - public List getAuthorities() { return authorities; } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthLogoutHandler.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthLogoutHandler.java index 417e2f40aa4..700871c41cd 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthLogoutHandler.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthLogoutHandler.java @@ -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; @@ -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 oauthConfig) { + final AbstractExternalOAuthIdentityProviderDefinition oauthConfig, + final Authentication authentication) { final StringBuilder oauthLogoutUriBuilder = new StringBuilder(request.getRequestURL()); if (StringUtils.hasText(request.getQueryString())) { oauthLogoutUriBuilder.append("?"); @@ -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(); } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/UaaLoginHintTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/UaaLoginHintTest.java index dd23b5a6aa5..c315b2ab2e6 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/UaaLoginHintTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/UaaLoginHintTest.java @@ -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()); + } } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/ZoneAwareWhitelistLogoutHandlerTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/ZoneAwareWhitelistLogoutHandlerTests.java index 6b3e16d87d5..4b7754e07fc 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/ZoneAwareWhitelistLogoutHandlerTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/ZoneAwareWhitelistLogoutHandlerTests.java @@ -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)); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManagerIT.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManagerIT.java index 0a47ca5b5d6..7b2cd765f0e 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManagerIT.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManagerIT.java @@ -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; @@ -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(); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthLogoutHandlerTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthLogoutHandlerTest.java index 5e756edd8da..58b4e9158b7 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthLogoutHandlerTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthLogoutHandlerTest.java @@ -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; @@ -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); @@ -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); @@ -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); @@ -104,7 +117,7 @@ void determineDefaultTargetUrl() { @Test void constructOAuthProviderLogoutUrl() { - oAuthLogoutHandler.constructOAuthProviderLogoutUrl(request, "", oAuthIdentityProviderDefinition); + oAuthLogoutHandler.constructOAuthProviderLogoutUrl(request, "", oAuthIdentityProviderDefinition, uaaAuthentication); } @Test