Skip to content

Commit

Permalink
Show sad cloud page with message from exception if token cannot be
Browse files Browse the repository at this point in the history
fetched.

[#115082069] https://www.pivotaltracker.com/story/show/115082069

Signed-off-by: Madhura Bhave <mbhave@pivotal.io>
  • Loading branch information
Priyata25 authored and cf-identity committed Mar 24, 2016
1 parent a5210a9 commit 828e690
Show file tree
Hide file tree
Showing 16 changed files with 191 additions and 74 deletions.
Expand Up @@ -183,6 +183,8 @@ public static void test_fetch_token_from_authorization_code(UaaContextFactory fa
TokenRequest fetchTokenRequest = factory.tokenRequest()
.setGrantType(FETCH_TOKEN_FROM_CODE)
.setRedirectUri(new URI(redirectUri))
.setClientId(clientId)
.setClientSecret(clientSecret)
.setAuthorizationCode(code);
if (idToken) {
fetchTokenRequest.withIdToken();
Expand Down
Expand Up @@ -14,6 +14,7 @@

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.client.utils.URIBuilder;
import org.cloudfoundry.identity.uaa.client.ClientMetadata;
import org.cloudfoundry.identity.uaa.client.JdbcClientMetadataProvisioning;
import org.cloudfoundry.identity.uaa.constants.OriginKeys;
Expand All @@ -28,6 +29,11 @@
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.security.Principal;
import java.util.ArrayList;
import java.util.HashMap;
Expand Down Expand Up @@ -140,7 +146,12 @@ public String error404(Model model) {
public String error401(Model model, HttpServletRequest request) {
AuthenticationException exception = (AuthenticationException) request.getSession().getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
model.addAttribute("saml_error", exception.getMessage());
return "saml_error";
return "external_auth_error";
}

@RequestMapping("/oauth_error")
public String error_oauth() throws URISyntaxException {
return "external_auth_error";
}

private static class TileData {
Expand Down
Expand Up @@ -463,7 +463,7 @@ public String performAutologin(HttpSession session) {
}

@RequestMapping(value = "/login/callback/{origin}", method = GET)
public String performCallback(HttpSession session) {
public String handleXOAuthCallback(HttpSession session) {
String redirectLocation = "/home";
SavedRequest savedRequest = (SavedRequest) session.getAttribute("SPRING_SECURITY_SAVED_REQUEST");
if (savedRequest != null && savedRequest.getRedirectUrl() != null) {
Expand Down
Expand Up @@ -14,7 +14,9 @@

import org.apache.commons.httpclient.util.URIUtil;
import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;

import javax.servlet.Filter;
Expand Down Expand Up @@ -51,9 +53,14 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo
String redirectUrl = request.getRequestURL().toString();
XOAuthCodeToken codeToken = new XOAuthCodeToken(code, origin, redirectUrl);
codeToken.setDetails(new UaaAuthenticationDetails(request));
Authentication authentication = xOAuthAuthenticationManager.authenticate(codeToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(request, response);
try {
Authentication authentication = xOAuthAuthenticationManager.authenticate(codeToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(request, response);
} catch (Exception ex) {
String errorMessage = "There was an error when authenticating against the external identity provider: " + ex.getMessage();
response.sendRedirect(request.getContextPath() + "/oauth_error?error=" + errorMessage);
}
}

@Override
Expand Down
Expand Up @@ -33,11 +33,14 @@
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.HttpServerErrorException;
import org.springframework.web.client.RestTemplate;

import java.net.URI;
Expand Down Expand Up @@ -72,6 +75,9 @@ protected UaaUser getUser(Authentication request) {

if (provider != null && provider.getConfig() instanceof AbstractXOAuthIdentityProviderDefinition) {
Claims claims = getClaimsFromToken(codeToken, (AbstractXOAuthIdentityProviderDefinition) provider.getConfig());
if (claims == null) {
return null;
}
String email = claims.getEmail();
String username = claims.getUserName();
if (email == null) {
Expand Down Expand Up @@ -124,10 +130,12 @@ private String getResponseType(AbstractXOAuthIdentityProviderDefinition config)

private Claims getClaimsFromToken(XOAuthCodeToken codeToken, AbstractXOAuthIdentityProviderDefinition config) {
String id_token = getTokenFromCode(codeToken, config);
if(id_token == null) {
return null;
}
Jwt decodeIdToken = JwtHelper.decode(id_token);

Claims claims = JsonUtils.readValue(decodeIdToken.getClaims(), Claims.class);
return claims;
return JsonUtils.readValue(decodeIdToken.getClaims(), Claims.class);
}

private String getTokenFromCode(XOAuthCodeToken codeToken, AbstractXOAuthIdentityProviderDefinition config) {
Expand All @@ -140,7 +148,6 @@ private String getTokenFromCode(XOAuthCodeToken codeToken, AbstractXOAuthIdentit
HttpHeaders headers = new HttpHeaders();
String clientAuth = new String(Base64.encodeBase64((config.getRelyingPartyId() + ":" + config.getRelyingPartySecret()).getBytes()));
headers.put("Authorization", Collections.singletonList("Basic " + clientAuth));
headers.put("Content-Type", Collections.singletonList("application/json"));
headers.put("Accept", Collections.singletonList("application/json"));

URI requestUri;
Expand All @@ -151,7 +158,11 @@ private String getTokenFromCode(XOAuthCodeToken codeToken, AbstractXOAuthIdentit
return null;
}

ResponseEntity<Map<String, String>> responseEntity = restTemplate.exchange(requestUri, HttpMethod.POST, requestEntity, new ParameterizedTypeReference<Map<String, String>>() {});
return responseEntity.getBody().get(ID_TOKEN);
try {
ResponseEntity<Map<String, String>> responseEntity = restTemplate.exchange(requestUri, HttpMethod.POST, requestEntity, new ParameterizedTypeReference<Map<String, String>>() {});
return responseEntity.getBody().get(ID_TOKEN);
} catch (HttpServerErrorException|HttpClientErrorException ex) {
throw ex;
}
}
}
Expand Up @@ -26,6 +26,8 @@
</div>
<h2 th:text="${saml_error}">
</h2>
<h2 th:if="${param.error}" th:text="${param.error[0]}">Error Message
</h2>
</div>
</body>
</html>
Expand Up @@ -23,15 +23,19 @@
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
import org.springframework.security.providers.ExpiringUsernameAuthenticationToken;
import org.springframework.security.web.PortResolverImpl;
import org.springframework.security.web.savedrequest.DefaultSavedRequest;
import org.springframework.security.web.savedrequest.SavedRequest;
import org.springframework.ui.ExtendedModelMap;

import javax.servlet.http.HttpSession;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
Expand Down Expand Up @@ -572,6 +576,26 @@ public void we_return_both_oauth_and_oidc_providers() throws Exception {
assertEquals(2, endpoint.getOauthIdentityProviderDefinitions().size());
}

@Test
public void xoauthCallback_redirectsToHomeIfNoSavedRequest() throws Exception {
HttpSession session = new MockHttpSession();
LoginInfoEndpoint endpoint = getEndpoint();
String redirectUrl = endpoint.handleXOAuthCallback(session);
assertEquals("redirect:/home", redirectUrl);
}

@Test
public void xoauthCallback_redirectsToSavedRequestIfPresent() throws Exception {
HttpSession session = new MockHttpSession();
DefaultSavedRequest savedRequest = Mockito.mock(DefaultSavedRequest.class);
when(savedRequest.getRedirectUrl()).thenReturn("/some.redirect.url");
session.setAttribute("SPRING_SECURITY_SAVED_REQUEST", savedRequest);
LoginInfoEndpoint endpoint = getEndpoint();
String redirectUrl = endpoint.handleXOAuthCallback(session);
assertEquals("redirect:/some.redirect.url", redirectUrl);

}

private MockHttpServletRequest getMockHttpServletRequest() {
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpSession session = new MockHttpSession();
Expand Down
Expand Up @@ -15,17 +15,22 @@


import org.cloudfoundry.identity.uaa.test.MockAuthentication;
import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.springframework.http.HttpStatus;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.client.HttpClientErrorException;

import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;

import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.contains;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
Expand Down Expand Up @@ -60,4 +65,22 @@ public void getXOAuthCodeTokenFromRequest() throws Exception {
assertEquals(authentication, SecurityContextHolder.getContext().getAuthentication());
}

}
@Test
public void redirectsToErrorPageInCaseOfException() throws Exception {

XOAuthAuthenticationManager xOAuthAuthenticationManager = Mockito.mock(XOAuthAuthenticationManager.class);
XOAuthAuthenticationFilter filter = new XOAuthAuthenticationFilter(xOAuthAuthenticationManager);

HttpServletRequest request = mock(HttpServletRequest.class);
FilterChain chain = mock(FilterChain.class);
MockHttpServletResponse response = new MockHttpServletResponse();

when(request.getRequestURL()).thenReturn(new StringBuffer("http://localhost/uaa/login/callback/the_origin"));
when(request.getServletPath()).thenReturn("/login/callback/the_origin");
when(request.getParameter("code")).thenReturn("the_code");

Mockito.doThrow(new HttpClientErrorException(HttpStatus.BAD_REQUEST, "error from oauth server")).when(xOAuthAuthenticationManager).authenticate(anyObject());
filter.doFilter(request, response, chain);
Assert.assertThat(response.getHeader("Location"), Matchers.containsString(request.getContextPath() + "/oauth_error?error="));
}
}
Expand Up @@ -39,25 +39,34 @@
import org.mockito.stubbing.Answer;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.HttpServerErrorException;

import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.AccessDeniedException;

import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.contains;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.springframework.http.HttpStatus.OK;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.content;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.header;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.jsonPath;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withBadRequest;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withServerError;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus;

public class XOAuthAuthenticationManagerTest {
Expand Down Expand Up @@ -167,7 +176,45 @@ public void failsIfProviderIsNotFound() throws Exception {
assertNull(authentication);
}

@Test(expected = HttpServerErrorException.class)
public void tokenCannotBeFetchedFromCodeBecauseOfServerError() throws Exception {
IdentityProvider<AbstractXOAuthIdentityProviderDefinition> identityProvider = getProvider();

Mockito.when(provisioning.retrieveByOrigin(eq(ORIGIN), anyString())).thenReturn(identityProvider);

mockUaaServer.expect(requestTo("http://oidc10.identity.cf-app.com/oauth/token")).andRespond(withServerError());
xoAuthAuthenticationManager.authenticate(xCodeToken);
}

@Test(expected = HttpClientErrorException.class)
public void tokenCannotBeFetchedFromInvalidCode() throws Exception {
IdentityProvider<AbstractXOAuthIdentityProviderDefinition> identityProvider = getProvider();

Mockito.when(provisioning.retrieveByOrigin(eq(ORIGIN), anyString())).thenReturn(identityProvider);

mockUaaServer.expect(requestTo("http://oidc10.identity.cf-app.com/oauth/token")).andRespond(withBadRequest());
xoAuthAuthenticationManager.authenticate(xCodeToken);
}

private void getToken(String idTokenJwt) throws MalformedURLException {
IdentityProvider<AbstractXOAuthIdentityProviderDefinition> identityProvider = getProvider();

Mockito.when(provisioning.retrieveByOrigin(eq(ORIGIN), anyString())).thenReturn(identityProvider);

CompositeAccessToken compositeAccessToken = new CompositeAccessToken("accessToken");
compositeAccessToken.setIdTokenValue(idTokenJwt);
String response = JsonUtils.writeValueAsString(compositeAccessToken);
mockUaaServer.expect(requestTo("http://oidc10.identity.cf-app.com/oauth/token"))
.andExpect(header("Authorization", "Basic " + new String(Base64.encodeBase64("identity:identitysecret".getBytes()))))
.andExpect(header("Accept", "application/json"))
.andExpect(content().string(containsString("grant_type=authorization_code")))
.andExpect(content().string(containsString("code=the_code")))
.andExpect(content().string(containsString("redirect_uri=http%3A%2F%2Flocalhost%2Fcallback%2Fthe_origin")))
.andExpect(content().string(containsString(("response_type=id_token"))))
.andRespond(withStatus(OK).contentType(APPLICATION_JSON).body(response));
}

private IdentityProvider<AbstractXOAuthIdentityProviderDefinition> getProvider() throws MalformedURLException {
IdentityProvider<AbstractXOAuthIdentityProviderDefinition> identityProvider = new IdentityProvider<>();
identityProvider.setName("my oidc provider");
identityProvider.setIdentityZoneId(OriginKeys.UAA);
Expand All @@ -183,19 +230,6 @@ private void getToken(String idTokenJwt) throws MalformedURLException {
config.setUserInfoUrl(new URL("http://oidc10.identity.cf-app.com/userinfo"));
identityProvider.setConfig(config);
identityProvider.setOriginKey("puppy");

Mockito.when(provisioning.retrieveByOrigin(eq(ORIGIN), anyString())).thenReturn(identityProvider);

CompositeAccessToken compositeAccessToken = new CompositeAccessToken("accessToken");
compositeAccessToken.setIdTokenValue(idTokenJwt);
String response = JsonUtils.writeValueAsString(compositeAccessToken);
mockUaaServer.expect(requestTo("http://oidc10.identity.cf-app.com/oauth/token"))
.andExpect(header("Authorization", "Basic " + new String(Base64.encodeBase64("identity:identitysecret".getBytes()))))
.andExpect(header("Accept", "application/json"))
.andExpect(jsonPath("grant_type").value("authorization_code"))
.andExpect(jsonPath("code").value(CODE))
.andExpect(jsonPath("redirect_uri").value("http://localhost/callback/the_origin"))
.andExpect(jsonPath("response_type").value("id_token"))
.andRespond(withStatus(OK).contentType(APPLICATION_JSON).body(response));
return identityProvider;
}
}
4 changes: 2 additions & 2 deletions uaa/src/main/resources/login.yml
Expand Up @@ -60,8 +60,8 @@ login:
# relyingPartyId: uaa
# relyingPartySecret: secret
# attributeMappings:
# given_name: Marissa
# family_name: Bloggs
# given_name: firstName
# family_name: lastname
url: http://localhost:8080/uaa

# SAML Key Configuration
Expand Down
Expand Up @@ -12,6 +12,7 @@
*******************************************************************************/
package org.cloudfoundry.identity.uaa.integration.feature;

import org.cloudfoundry.identity.uaa.ServerRunning;
import org.cloudfoundry.identity.uaa.constants.OriginKeys;
import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils;
import org.cloudfoundry.identity.uaa.integration.util.ScreenshotOnFail;
Expand All @@ -36,6 +37,7 @@

import java.net.URL;

import static org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils.getZoneAdminToken;
import static org.junit.Assert.assertThat;

@RunWith(LoginServerClassRunner.class)
Expand Down Expand Up @@ -66,14 +68,21 @@ public class OIDCLoginIT {
@Autowired
TestClient testClient;

ServerRunning serverRunning = ServerRunning.isRunning();

@Before
@After
public void logout() {
public void logout() throws Exception {
webDriver.get(baseUrl + "/logout.do");
webDriver.get("http://oidc10.identity.cf-app.com/logout.do");
screenShootRule.setWebDriver(webDriver);
}

@After
public void deleteProvider() throws Exception {
IntegrationTestUtils.deleteProvider(getZoneAdminToken(baseUrl, serverRunning), baseUrl, "uaa", "puppy");
}

@Test
public void successfulLoginWithOIDCProvider() throws Exception {
createOIDCProvider();
Expand Down

0 comments on commit 828e690

Please sign in to comment.