Java tutorial
/******************************************************************************* * Cloud Foundry * Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved. * * This product is licensed to you under the Apache License, Version 2.0 (the "License"). * You may not use this product except in compliance with the License. * * This product includes a number of subcomponents with * separate copyright notices and license terms. Your use of these * subcomponents is subject to the terms and conditions of the * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ package org.cloudfoundry.identity.uaa.mock.token; import com.fasterxml.jackson.core.type.TypeReference; import org.apache.commons.collections.map.HashedMap; import org.apache.commons.httpclient.util.URIUtil; import org.cloudfoundry.identity.uaa.account.UserInfoResponse; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; import org.cloudfoundry.identity.uaa.oauth.DisableIdTokenResponseTypeFilter; import org.cloudfoundry.identity.uaa.oauth.KeyInfo; import org.cloudfoundry.identity.uaa.oauth.TokenRevokedException; import org.cloudfoundry.identity.uaa.oauth.UaaTokenServices; import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.cloudfoundry.identity.uaa.oauth.jwt.Jwt; import org.cloudfoundry.identity.uaa.oauth.jwt.JwtHelper; import org.cloudfoundry.identity.uaa.oauth.token.*; import org.cloudfoundry.identity.uaa.provider.*; import org.cloudfoundry.identity.uaa.provider.saml.idp.SamlTestUtils; import org.cloudfoundry.identity.uaa.provider.saml.idp.ZoneAwareIdpMetadataManager; import org.cloudfoundry.identity.uaa.scim.ScimGroup; import org.cloudfoundry.identity.uaa.scim.ScimGroupMember; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; import org.cloudfoundry.identity.uaa.scim.bootstrap.ScimUserBootstrap; import org.cloudfoundry.identity.uaa.user.UaaAuthority; import org.cloudfoundry.identity.uaa.user.UaaUser; import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.cloudfoundry.identity.uaa.util.SetServerNameRequestPostProcessor; import org.cloudfoundry.identity.uaa.util.UaaUrlUtils; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.cloudfoundry.identity.uaa.zone.IdentityZoneProvisioning; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; import org.opensaml.xml.ConfigurationException; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.http.MediaType; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.mock.env.MockEnvironment; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockHttpSession; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.crypto.codec.Base64; import org.springframework.security.oauth2.common.OAuth2RefreshToken; import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; import org.springframework.security.oauth2.common.util.OAuth2Utils; import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; import org.springframework.security.oauth2.provider.AuthorizationRequest; import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.security.oauth2.provider.client.BaseClientDetails; import org.springframework.security.web.context.HttpSessionSecurityContextRepository; import org.springframework.security.web.savedrequest.SavedRequest; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.ResultMatcher; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.test.web.servlet.request.RequestPostProcessor; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.UriUtils; import javax.servlet.http.HttpSession; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URL; import java.net.URLDecoder; import java.sql.Timestamp; import java.util.*; import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.CookieCsrfPostProcessor.cookieCsrf; import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.*; import static org.cloudfoundry.identity.uaa.oauth.UaaTokenServicesTests.PASSWORD; import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.JTI; import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.*; import static org.cloudfoundry.identity.uaa.provider.saml.idp.SamlTestUtils.createLocalSamlIdpDefinition; import static org.cloudfoundry.identity.uaa.web.UaaSavedRequestAwareAuthenticationSuccessHandler.FORM_REDIRECT_PARAMETER; import static org.cloudfoundry.identity.uaa.web.UaaSavedRequestAwareAuthenticationSuccessHandler.SAVED_REQUEST_SESSION_ATTRIBUTE; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.Matchers.*; import static org.hamcrest.core.Is.is; import static org.hamcrest.core.StringStartsWith.startsWith; import static org.junit.Assert.*; import static org.springframework.http.HttpHeaders.AUTHORIZATION; import static org.springframework.http.HttpHeaders.HOST; import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED; import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.security.oauth2.common.OAuth2AccessToken.ACCESS_TOKEN; import static org.springframework.security.oauth2.common.OAuth2AccessToken.REFRESH_TOKEN; import static org.springframework.security.oauth2.common.util.OAuth2Utils.GRANT_TYPE; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request; public class TokenMvcMockTests extends AbstractTokenMockMvcTests { private String BADSECRET = "badsecret"; private TestClient testClient; private RandomValueStringGenerator generator = new RandomValueStringGenerator(); private MockEnvironment mockEnvironment; public static final String IDP_ENTITY_ID = "cloudfoundry-saml-login"; private static SamlTestUtils samlTestUtils = new SamlTestUtils(); @BeforeClass public static void initializeSamlUtils() { try { samlTestUtils.initializeSimple(); } catch (ConfigurationException e) { e.printStackTrace(); } } @Override public void setUpContext() throws Exception { testClient = new TestClient(); super.setUpContext(); } @Test public void test_logon_timestamps_with_password_grant() throws Exception { String username = "testuser" + generator.generate(); String userScopes = "uaa.user"; ScimUser user = setUpUser(username, userScopes, OriginKeys.UAA, IdentityZone.getUaa().getId()); ScimUserProvisioning provisioning = getWebApplicationContext().getBean(ScimUserProvisioning.class); ScimUser scimUser = provisioning.retrieve(user.getId()); assertNull(scimUser.getLastLogonTime()); assertNull(scimUser.getPreviousLogonTime()); doPasswordGrant(username); scimUser = provisioning.retrieve(user.getId()); assertNotNull(scimUser.getLastLogonTime()); assertNull(scimUser.getPreviousLogonTime()); long lastLogonTime = scimUser.getLastLogonTime(); doPasswordGrant(username); scimUser = provisioning.retrieve(user.getId()); assertNotNull(scimUser.getLastLogonTime()); assertNotNull(scimUser.getPreviousLogonTime()); assertEquals(lastLogonTime, (long) scimUser.getPreviousLogonTime()); assertTrue(scimUser.getLastLogonTime() > scimUser.getPreviousLogonTime()); } public void doPasswordGrant(String username) throws Exception { getMockMvc().perform(post("/oauth/token").param("client_id", "cf").param("client_secret", "") .param(OAuth2Utils.GRANT_TYPE, PASSWORD).param("username", username).param("password", SECRET) .accept(APPLICATION_JSON).contentType(APPLICATION_FORM_URLENCODED)).andExpect(status().isOk()); } @Test public void passcode_with_client_parameters_when_password_change_required_for_user() throws Exception { String username = "testuser" + generator.generate(); String userScopes = "uaa.user"; ScimUser user = setUpUser(username, userScopes, OriginKeys.UAA, IdentityZone.getUaa().getId()); userProvisioning.updatePasswordChangeRequired(user.getId(), true); String response = getMockMvc() .perform(post("/oauth/token").param("client_id", "cf").param("client_secret", "") .param(OAuth2Utils.GRANT_TYPE, PASSWORD).param("username", username) .param("password", SECRET).accept(APPLICATION_JSON) .contentType(APPLICATION_FORM_URLENCODED)) .andExpect(status().isUnauthorized()).andReturn().getResponse().getContentAsString(); Map<String, String> error = (JsonUtils.readValue(response, new TypeReference<Map<String, String>>() { })); String error_description = error.get("error_description"); assertNotNull(error_description); assertEquals("User password needs to be changed", error_description); } @Test public void passcode_with_client_parameters() throws Exception { String username = "testuser" + generator.generate(); String userScopes = "uaa.user"; ScimUser user = setUpUser(username, userScopes, OriginKeys.UAA, IdentityZone.getUaa().getId()); String content = getMockMvc() .perform(get("/passcode").session(getAuthenticatedSession(user)).accept(APPLICATION_JSON)) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); String code = JsonUtils.readValue(content, String.class); String response = getMockMvc() .perform(post("/oauth/token").param("client_id", "cf").param("client_secret", "") .param(OAuth2Utils.GRANT_TYPE, PASSWORD).param("passcode", code).accept(APPLICATION_JSON) .contentType(APPLICATION_FORM_URLENCODED)) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); Map<String, Object> tokens = JsonUtils.readValue(response, new TypeReference<Map<String, Object>>() { }); Object accessToken = tokens.get(ACCESS_TOKEN); Object jti = tokens.get(JTI); assertNotNull(accessToken); assertNotNull(JTI); } @Test public void test_encoded_char_on_authorize_url() throws Exception { String username = "testuser" + generator.generate(); String userScopes = "uaa.user"; ScimUser user = setUpUser(username, userScopes, OriginKeys.UAA, IdentityZone.getUaa().getId()); getMockMvc() .perform(get("/oauth/authorize").param("client_id", new String(new char[] { '\u0000' })) .session(getAuthenticatedSession(user)).accept(MediaType.TEXT_HTML)) .andDo(print()).andExpect(status().isBadRequest()) .andExpect(request().attribute("error_message_code", "request.invalid_parameter")); } @Test public void test_token_ids() throws Exception { String clientId = "testclient" + generator.generate(); setUpClients(clientId, "uaa.user", "uaa.user", "password,refresh_token", true, TEST_REDIRECT_URI, Arrays.asList("uaa")); String username = "testuser" + generator.generate(); String userScopes = "uaa.user"; setUpUser(username, userScopes, OriginKeys.UAA, IdentityZone.getUaa().getId()); String response = getMockMvc() .perform(post("/oauth/token").contentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE) .param(OAuth2Utils.RESPONSE_TYPE, "token").param(OAuth2Utils.GRANT_TYPE, "password") .param(OAuth2Utils.CLIENT_ID, clientId).param(REQUEST_TOKEN_FORMAT, OPAQUE) .param("client_secret", SECRET).param("username", username).param("password", SECRET)) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); Map<String, Object> tokens = JsonUtils.readValue(response, new TypeReference<Map<String, Object>>() { }); Object accessToken = tokens.get(ACCESS_TOKEN); Object refreshToken = tokens.get(REFRESH_TOKEN); Object jti = tokens.get(JTI); assertNotNull(accessToken); assertNotNull(refreshToken); assertNotNull(jti); assertEquals(jti, accessToken); assertNotEquals(accessToken + REFRESH_TOKEN_SUFFIX, refreshToken); String accessTokenId = (String) accessToken; String refreshTokenId = (String) refreshToken; response = getMockMvc() .perform(post("/oauth/token") .header(AUTHORIZATION, "Basic " + new String(Base64.encode((clientId + ":" + SECRET).getBytes()))) .contentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE) .param(OAuth2Utils.RESPONSE_TYPE, "token").param(OAuth2Utils.GRANT_TYPE, REFRESH_TOKEN) .param(REFRESH_TOKEN, refreshTokenId).param(REQUEST_TOKEN_FORMAT, OPAQUE)) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); tokens = JsonUtils.readValue(response, new TypeReference<Map<String, Object>>() { }); accessToken = tokens.get(ACCESS_TOKEN); refreshToken = tokens.get(REFRESH_TOKEN); jti = tokens.get(JTI); assertNotNull(accessToken); assertNotNull(refreshToken); assertNotNull(jti); assertEquals(jti, accessToken); assertNotEquals(accessToken + REFRESH_TOKEN_SUFFIX, refreshToken); assertNotEquals(accessToken, accessTokenId); assertEquals(accessToken, jti); assertNotEquals(refreshToken, jti); } @Test public void test_saml_bearer_grant() throws Exception { String subdomain = generator.generate().toLowerCase(); //all our SAML defaults use :8080/uaa/ so we have to use that here too String host = subdomain + ".localhost"; String fullPath = "/uaa/oauth/token/alias/" + subdomain + ".cloudfoundry-saml-login"; String origin = subdomain + ".cloudfoundry-saml-login"; MockMvcUtils.IdentityZoneCreationResult zone = MockMvcUtils .createOtherIdentityZoneAndReturnResult(subdomain, getMockMvc(), getWebApplicationContext(), null); //create an actual IDP, so we can fetch metadata String idpMetadata = MockMvcUtils.getIDPMetaData(getMockMvc(), subdomain); //create an IDP in the default zone SamlIdentityProviderDefinition idpDef = createLocalSamlIdpDefinition(origin, zone.getIdentityZone().getId(), idpMetadata); IdentityProvider provider = new IdentityProvider(); provider.setConfig(idpDef); provider.setActive(true); provider.setIdentityZoneId(zone.getIdentityZone().getId()); provider.setName(origin); provider.setOriginKey(origin); IdentityZoneHolder.set(zone.getIdentityZone()); getWebApplicationContext().getBean(JdbcIdentityProviderProvisioning.class).create(provider); getWebApplicationContext().getBean(ZoneAwareIdpMetadataManager.class).refreshAllProviders(); IdentityZoneHolder.clear(); String assertion = samlTestUtils.mockAssertionEncoded(subdomain + ".cloudfoundry-saml-login", "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", "Saml2BearerIntegrationUser", "http://" + subdomain + ".localhost:8080/uaa/oauth/token/alias/" + subdomain + ".cloudfoundry-saml-login", subdomain + ".cloudfoundry-saml-login"); //create client in default zone String clientId = "testclient" + generator.generate(); setUpClients(clientId, "uaa.none", "uaa.user,openid", GRANT_TYPE_SAML2_BEARER + ",password", true, TEST_REDIRECT_URI, null, 600, zone.getIdentityZone()); //String fullPath = "/uaa/oauth/token"; MockHttpServletRequestBuilder post = post(fullPath).with(new RequestPostProcessor() { @Override public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) { request.setServerPort(8080); request.setRequestURI(fullPath); request.setServerName(host); return request; } }).contextPath("/uaa").accept(APPLICATION_JSON).header(HOST, host).contentType(APPLICATION_FORM_URLENCODED) .param("grant_type", "urn:ietf:params:oauth:grant-type:saml2-bearer").param("client_id", clientId) .param("client_secret", "secret").param("assertion", assertion); getMockMvc().perform(post).andDo(print()).andExpect(status().isOk()) .andExpect(jsonPath("$.access_token").exists()) .andExpect(jsonPath("$.scope").value("openid uaa.user")); getMockMvc().perform(post.param("scope", "uaa.admin")).andDo(print()).andExpect(status().isUnauthorized()); } @Test public void test_two_zone_saml_bearer_grant() throws Exception { String subdomain = generator.generate().toLowerCase(); //all our SAML defaults use :8080/uaa/ so we have to use that here too String spInvocationEndpoint = "/uaa/oauth/token/alias/cloudfoundry-saml-login"; String idpOrigin = subdomain + ".cloudfoundry-saml-login"; //create an zone - that zone will be our IDP MockMvcUtils.IdentityZoneCreationResult zone = MockMvcUtils .createOtherIdentityZoneAndReturnResult(subdomain, getMockMvc(), getWebApplicationContext(), null); //create an actual IDP, so we can fetch metadata String spMetadata = MockMvcUtils.getSPMetadata(getMockMvc(), null); String idpMetadata = MockMvcUtils.getIDPMetaData(getMockMvc(), subdomain); //create an IDP in the default zone SamlIdentityProviderDefinition idpDef = createLocalSamlIdpDefinition(idpOrigin, IdentityZone.getUaa().getId(), idpMetadata); IdentityProvider provider = new IdentityProvider(); provider.setConfig(idpDef); provider.setActive(true); provider.setIdentityZoneId(IdentityZone.getUaa().getId()); provider.setName(idpOrigin); provider.setOriginKey(idpOrigin); IdentityZoneHolder.clear(); getWebApplicationContext().getBean(JdbcIdentityProviderProvisioning.class).create(provider); getWebApplicationContext().getBean(ZoneAwareIdpMetadataManager.class).refreshAllProviders(); IdentityZoneHolder.clear(); String assertion = samlTestUtils.mockAssertionEncoded(subdomain + ".cloudfoundry-saml-login", "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", "Saml2BearerIntegrationUser", "http://localhost:8080/uaa/oauth/token/alias/cloudfoundry-saml-login", "cloudfoundry-saml-login"); //create client in default zone String clientId = "testclient" + generator.generate(); setUpClients(clientId, "uaa.none", "uaa.user,openid", GRANT_TYPE_SAML2_BEARER + ",password", true, TEST_REDIRECT_URI, null, 600, null); MockHttpServletRequestBuilder post = post(spInvocationEndpoint).with(new RequestPostProcessor() { @Override public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) { request.setServerPort(8080); request.setRequestURI(spInvocationEndpoint); request.setServerName("localhost"); return request; } }).contextPath("/uaa").accept(APPLICATION_JSON).header(HOST, "localhost") .contentType(APPLICATION_FORM_URLENCODED) .param("grant_type", "urn:ietf:params:oauth:grant-type:saml2-bearer").param("client_id", clientId) .param("client_secret", "secret").param("assertion", assertion); String json = getMockMvc().perform(post).andDo(print()).andExpect(status().isOk()) .andExpect(jsonPath("$.access_token").exists()) .andExpect(jsonPath("$.scope").value("openid uaa.user")).andReturn().getResponse() .getContentAsString(); System.out.println("json = " + json); getMockMvc().perform(post.param("scope", "uaa.admin")).andDo(print()).andExpect(status().isUnauthorized()); } @Test public void getOauthToken_Password_Grant_When_UAA_Provider_is_Disabled() throws Exception { String clientId = "testclient" + generator.generate(); setUpClients(clientId, "uaa.user", "uaa.user", "password", true, TEST_REDIRECT_URI, Arrays.asList("uaa")); String username = "testuser" + generator.generate(); String userScopes = "uaa.user"; setUpUser(username, userScopes, OriginKeys.UAA, IdentityZone.getUaa().getId()); setDisableInternalAuth(getWebApplicationContext(), IdentityZone.getUaa().getId(), true); try { getMockMvc() .perform(post("/oauth/token").contentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE) .param(OAuth2Utils.RESPONSE_TYPE, "token").param(OAuth2Utils.GRANT_TYPE, "password") .param(OAuth2Utils.CLIENT_ID, clientId).param("client_secret", SECRET) .param("username", username).param("password", SECRET)) .andExpect(status().isUnauthorized()); } finally { setDisableInternalAuth(getWebApplicationContext(), IdentityZone.getUaa().getId(), false); } } @Test public void token_endpoint_should_return_Basic_WWW_Authenticate_Header() throws Exception { String clientId = "testclient" + generator.generate(); setUpClients(clientId, "uaa.user", "uaa.user", "authorization_code", true, TEST_REDIRECT_URI, Arrays.asList("uaa")); String username = "testuser" + generator.generate(); String userScopes = "uaa.user"; ScimUser developer = setUpUser(username, userScopes, OriginKeys.UAA, IdentityZone.getUaa().getId()); MockHttpSession session = getAuthenticatedSession(developer); String state = generator.generate(); MvcResult result = getMockMvc() .perform(get("/oauth/authorize").session(session).param(OAuth2Utils.RESPONSE_TYPE, "code") .param(OAuth2Utils.STATE, state).param(OAuth2Utils.CLIENT_ID, clientId)) .andExpect(status().isFound()).andReturn(); URL url = new URL(result.getResponse().getHeader("Location").replace("redirect#", "redirect?")); Map query = splitQuery(url); String code = ((List<String>) query.get("code")).get(0); assertThat(code.length(), greaterThan(9)); state = ((List<String>) query.get("state")).get(0); getMockMvc() .perform(post("/oauth/token").contentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE) .accept(MediaType.APPLICATION_JSON_VALUE).param(OAuth2Utils.RESPONSE_TYPE, "token") .param(OAuth2Utils.GRANT_TYPE, "authorization_code").param(OAuth2Utils.CLIENT_ID, clientId) .param("code", code).param("state", state)) .andExpect(status().isUnauthorized()).andExpect(header().stringValues("WWW-Authenticate", "Basic realm=\"UAA/client\", error=\"unauthorized\", error_description=\"Bad credentials\"")); } @Test public void getOauthToken_usingAuthCode_withClientIdAndSecretInRequestBody_shouldBeOk() throws Exception { String clientId = "testclient" + generator.generate(); setUpClients(clientId, "uaa.user", "uaa.user", "authorization_code", true, TEST_REDIRECT_URI, Arrays.asList("uaa")); String username = "testuser" + generator.generate(); String userScopes = "uaa.user"; ScimUser developer = setUpUser(username, userScopes, OriginKeys.UAA, IdentityZone.getUaa().getId()); MockHttpSession session = getAuthenticatedSession(developer); String state = generator.generate(); MvcResult result = getMockMvc() .perform(get("/oauth/authorize").session(session).param(OAuth2Utils.RESPONSE_TYPE, "code") .param(OAuth2Utils.STATE, state).param(OAuth2Utils.CLIENT_ID, clientId)) .andExpect(status().isFound()).andReturn(); URL url = new URL(result.getResponse().getHeader("Location").replace("redirect#", "redirect?")); Map query = splitQuery(url); String code = ((List<String>) query.get("code")).get(0); assertThat(code.length(), greaterThan(9)); state = ((List<String>) query.get("state")).get(0); getMockMvc() .perform(post("/oauth/token").contentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE) .accept(MediaType.APPLICATION_JSON_VALUE).param(OAuth2Utils.RESPONSE_TYPE, "token") .param(OAuth2Utils.GRANT_TYPE, "authorization_code").param(OAuth2Utils.CLIENT_ID, clientId) .param("client_secret", "secret").param("code", code).param("state", state)) .andExpect(status().isOk()); } protected MockHttpSession getAuthenticatedSession(ScimUser user) { MockHttpSession session = new MockHttpSession(); setAuthentication(session, user); return session; } @Test public void refreshAccessToken_withClient_withAutoApproveField() throws Exception { String clientId = "testclient" + generator.generate(); BaseClientDetails clientDetails = new BaseClientDetails(clientId, null, "uaa.user,other.scope", "authorization_code,refresh_token", "uaa.resource", TEST_REDIRECT_URI); clientDetails.setAutoApproveScopes(Arrays.asList("uaa.user")); clientDetails.setClientSecret("secret"); clientDetails.addAdditionalInformation(ClientConstants.AUTO_APPROVE, Arrays.asList("other.scope")); clientDetails.addAdditionalInformation(ClientConstants.ALLOWED_PROVIDERS, Arrays.asList("uaa")); clientDetailsService.addClientDetails(clientDetails); String username = "testuser" + generator.generate(); String userScopes = "uaa.user,other.scope"; ScimUser developer = setUpUser(username, userScopes, OriginKeys.UAA, IdentityZone.getUaa().getId()); MockHttpSession session = getAuthenticatedSession(developer); String state = generator.generate(); MvcResult result = getMockMvc() .perform(get("/oauth/authorize").session(session).param(OAuth2Utils.RESPONSE_TYPE, "code") .param(OAuth2Utils.STATE, state).param(OAuth2Utils.CLIENT_ID, clientId)) .andExpect(status().isFound()).andReturn(); URL url = new URL(result.getResponse().getHeader("Location").replace("redirect#", "redirect?")); Map query = splitQuery(url); String code = ((List<String>) query.get("code")).get(0); state = ((List<String>) query.get("state")).get(0); MockHttpServletRequestBuilder oauthTokenPost = post("/oauth/token") .contentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE).accept(MediaType.APPLICATION_JSON_VALUE) .param(OAuth2Utils.RESPONSE_TYPE, "token").param(OAuth2Utils.GRANT_TYPE, "authorization_code") .param(OAuth2Utils.CLIENT_ID, clientId).param("client_secret", "secret").param("code", code) .param("state", state); MvcResult mvcResult = getMockMvc().perform(oauthTokenPost).andReturn(); OAuth2RefreshToken refreshToken = JsonUtils .readValue(mvcResult.getResponse().getContentAsString(), CompositeAccessToken.class) .getRefreshToken(); MockHttpServletRequestBuilder postForRefreshToken = post("/oauth/token") .header("Authorization", "Basic " + new String(Base64.encode((clientId + ":" + SECRET).getBytes()))) .param(GRANT_TYPE, REFRESH_TOKEN).param(REFRESH_TOKEN, refreshToken.getValue()); getMockMvc().perform(postForRefreshToken).andExpect(status().isOk()); } @Test public void authorizeEndpointWithPromptNone_WhenNotAuthenticated() throws Exception { String clientId = "testclient" + generator.generate(); BaseClientDetails clientDetails = new BaseClientDetails(clientId, null, "uaa.user,other.scope", "authorization_code,refresh_token", "uaa.resource", TEST_REDIRECT_URI); clientDetails.setAutoApproveScopes(Arrays.asList("uaa.user")); clientDetails.setClientSecret("secret"); clientDetails.addAdditionalInformation(ClientConstants.AUTO_APPROVE, Arrays.asList("other.scope")); clientDetails.addAdditionalInformation(ClientConstants.ALLOWED_PROVIDERS, Arrays.asList("uaa")); clientDetailsService.addClientDetails(clientDetails); MockHttpSession session = new MockHttpSession(); String state = generator.generate(); MvcResult result = getMockMvc() .perform(get("/oauth/authorize").session(session).param(OAuth2Utils.RESPONSE_TYPE, "code") .param(OAuth2Utils.STATE, state).param(OAuth2Utils.CLIENT_ID, clientId) .param(OAuth2Utils.REDIRECT_URI, TEST_REDIRECT_URI) .param(ID_TOKEN_HINT_PROMPT, ID_TOKEN_HINT_PROMPT_NONE)) .andExpect(status().isFound()).andExpect(cookie().maxAge("Current-User", 0)).andReturn(); String url = result.getResponse().getHeader("Location"); assertEquals(UaaUrlUtils.addQueryParameter(TEST_REDIRECT_URI, "error", "login_required"), url); } @Test public void testAuthorizeEndpointWithPromptNone_Authenticated() throws Exception { String clientId = "testclient" + generator.generate(); BaseClientDetails clientDetails = new BaseClientDetails(clientId, null, "uaa.user,other.scope", "authorization_code,refresh_token", "uaa.resource", TEST_REDIRECT_URI); clientDetails.setAutoApproveScopes(Arrays.asList("uaa.user")); clientDetails.setClientSecret("secret"); clientDetails.addAdditionalInformation(ClientConstants.AUTO_APPROVE, Arrays.asList("other.scope")); clientDetails.addAdditionalInformation(ClientConstants.ALLOWED_PROVIDERS, Arrays.asList("uaa")); clientDetailsService.addClientDetails(clientDetails); String username = "testuser" + generator.generate(); String userScopes = "uaa.user,other.scope"; ScimUser developer = setUpUser(username, userScopes, OriginKeys.UAA, IdentityZone.getUaa().getId()); MockHttpSession session = getAuthenticatedSession(developer); String state = generator.generate(); MvcResult result = getMockMvc() .perform(get("/oauth/authorize").session(session).param(OAuth2Utils.RESPONSE_TYPE, "code") .param(OAuth2Utils.STATE, state).param(OAuth2Utils.CLIENT_ID, clientId) .param(OAuth2Utils.REDIRECT_URI, TEST_REDIRECT_URI) .param(ID_TOKEN_HINT_PROMPT, ID_TOKEN_HINT_PROMPT_NONE)) .andExpect(status().isFound()).andReturn(); String url = result.getResponse().getHeader("Location"); assertThat(url, containsString(TEST_REDIRECT_URI)); } @Test public void getOauthToken_usingPassword_withClientIdAndSecretInRequestBody_shouldBeOk() throws Exception { String clientId = "testclient" + generator.generate(); setUpClients(clientId, "uaa.user", "uaa.user", "password", true, TEST_REDIRECT_URI, Arrays.asList("uaa")); String username = "testuser" + generator.generate(); String userScopes = "uaa.user"; setUpUser(username, userScopes, OriginKeys.UAA, IdentityZone.getUaa().getId()); getMockMvc().perform(post("/oauth/token").contentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE) .param(OAuth2Utils.RESPONSE_TYPE, "token").param(OAuth2Utils.GRANT_TYPE, "password") .param(OAuth2Utils.CLIENT_ID, clientId).param("client_secret", SECRET).param("username", username) .param("password", SECRET)).andExpect(status().isOk()); } @Test public void getOauthToken_usingPassword_withNoCommonScopes_shouldBeUnauthorized() throws Exception { String clientId = "testclient" + generator.generate(); setUpClients(clientId, "uaa.user", "something_else", "password", true, TEST_REDIRECT_URI, Arrays.asList("uaa")); String username = "testuser" + generator.generate(); String userScopes = "uaa.user"; setUpUser(username, userScopes, OriginKeys.UAA, IdentityZone.getUaa().getId()); MvcResult result = getMockMvc() .perform(post("/oauth/token").contentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE) .param(OAuth2Utils.RESPONSE_TYPE, "token").param(OAuth2Utils.GRANT_TYPE, "password") .param(OAuth2Utils.CLIENT_ID, clientId).param("client_secret", SECRET) .param("username", username).param("password", SECRET)) .andExpect(status().isUnauthorized()).andReturn(); assertThat(result.getResponse().getErrorMessage(), containsString( "[something_else] is invalid. This user is not allowed any of the requested scopes")); } @Test public void getOauthToken_usingClientCredentials_withClientIdAndSecretInRequestBody_shouldBeOk() throws Exception { String clientId = "testclient" + generator.generate(); setUpClients(clientId, "uaa.user", "uaa.user", "client_credentials", true, TEST_REDIRECT_URI, Arrays.asList("uaa")); getMockMvc().perform(post("/oauth/token").contentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE) .param(OAuth2Utils.RESPONSE_TYPE, "token").param(OAuth2Utils.GRANT_TYPE, "client_credentials") .param(OAuth2Utils.CLIENT_ID, clientId).param("client_secret", SECRET)).andExpect(status().isOk()); } @Test public void testClientIdentityProviderWithoutAllowedProvidersForPasswordGrantWorksInOtherZone() throws Exception { String scopes = "space.*.developer,space.*.admin,org.*.reader,org.123*.admin,*.*,*,openid"; //a client without allowed providers in non default zone should always be rejected String subdomain = "testzone" + generator.generate(); IdentityZone testZone = setupIdentityZone(subdomain); IdentityZoneHolder.set(testZone); IdentityProvider provider = setupIdentityProvider(OriginKeys.UAA); String clientId2 = "testclient" + generator.generate(); setUpClients(clientId2, scopes, scopes, "authorization_code,password", true, TEST_REDIRECT_URI, Arrays.asList(provider.getOriginKey())); String clientId = "testclient" + generator.generate(); setUpClients(clientId, scopes, scopes, "authorization_code,password", true, TEST_REDIRECT_URI, null); String username = "testuser" + generator.generate(); String userScopes = "space.1.developer,space.2.developer,org.1.reader,org.2.reader,org.12345.admin,scope.one,scope.two,scope.three,openid"; ScimUser developer = setUpUser(username, userScopes, OriginKeys.UAA, testZone.getId()); getMockMvc().perform(post("/oauth/token") .with(new SetServerNameRequestPostProcessor(subdomain + ".localhost")).param("username", username) .param("password", "secret") .header("Authorization", "Basic " + new String(Base64.encode((clientId + ":" + SECRET).getBytes()))) .param(OAuth2Utils.RESPONSE_TYPE, "token").param(OAuth2Utils.GRANT_TYPE, "password") .param(OAuth2Utils.CLIENT_ID, clientId)).andExpect(status().isOk()); getMockMvc() .perform(post("/oauth/token").with(new SetServerNameRequestPostProcessor(subdomain + ".localhost")) .param("username", username).param("password", "secret") .header("Authorization", "Basic " + new String(Base64.encode((clientId2 + ":" + SECRET).getBytes()))) .param(OAuth2Utils.RESPONSE_TYPE, "token").param(OAuth2Utils.GRANT_TYPE, "password") .param(OAuth2Utils.CLIENT_ID, clientId2)) .andExpect(status().isOk()); } @Test public void getToken_withPasswordGrantType_resultsInUserLastLogonTimestampUpdate() throws Exception { String username = "testuser" + generator.generate(); String userScopes = "uaa.user"; ScimUser user = setUpUser(username, userScopes, OriginKeys.UAA, IdentityZone.getUaa().getId()); getWebApplicationContext().getBean(UaaUserDatabase.class).updateLastLogonTime(user.getId()); getWebApplicationContext().getBean(UaaUserDatabase.class).updateLastLogonTime(user.getId()); String accessToken = getAccessTokenForPasswordGrant(username); Long firstTimestamp = getPreviousLogonTime(accessToken); String accessToken2 = getAccessTokenForPasswordGrant(username); Long secondTimestamp = getPreviousLogonTime(accessToken2); assertNotEquals(firstTimestamp, secondTimestamp); assertTrue(firstTimestamp < secondTimestamp); } private String getAccessTokenForPasswordGrant(String username) throws Exception { String response = getMockMvc() .perform(post("/oauth/token").param("client_id", "cf").param("client_secret", "") .param(OAuth2Utils.GRANT_TYPE, PASSWORD).param("username", username) .param("password", SECRET).accept(APPLICATION_JSON) .contentType(APPLICATION_FORM_URLENCODED)) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); return (String) JsonUtils.readValue(response, Map.class).get("access_token"); } private Long getPreviousLogonTime(String accessToken) throws Exception { UserInfoResponse userInfo; String userInfoResponse = getMockMvc() .perform(get("/userinfo").header("Authorization", "bearer " + accessToken).accept(APPLICATION_JSON)) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); assertNotNull(userInfoResponse); userInfo = JsonUtils.readValue(userInfoResponse, UserInfoResponse.class); return userInfo.getPreviousLogonSuccess(); } @Test public void testClientIdentityProviderClientWithoutAllowedProvidersForAuthCodeAlreadyLoggedInWorksInAnotherZone() throws Exception { //a client without allowed providers in non default zone should always be rejected String subdomain = "testzone" + generator.generate(); IdentityZone testZone = setupIdentityZone(subdomain); IdentityZoneHolder.set(testZone); IdentityProvider provider = setupIdentityProvider(OriginKeys.UAA); String scopes = "space.*.developer,space.*.admin,org.*.reader,org.123*.admin,*.*,*,openid"; String clientId = "testclient" + generator.generate(); setUpClients(clientId, scopes, scopes, "authorization_code,password", true, TEST_REDIRECT_URI, null); String clientId2 = "testclient" + generator.generate(); setUpClients(clientId2, scopes, scopes, "authorization_code,password", true, TEST_REDIRECT_URI, Arrays.asList(provider.getOriginKey())); String clientId3 = "testclient" + generator.generate(); setUpClients(clientId3, scopes, scopes, "authorization_code,password", true, TEST_REDIRECT_URI, Arrays.asList(OriginKeys.LOGIN_SERVER)); String username = "testuser" + generator.generate(); String userScopes = "space.1.developer,space.2.developer,org.1.reader,org.2.reader,org.12345.admin,scope.one,scope.two,scope.three,openid"; ScimUser developer = setUpUser(username, userScopes, OriginKeys.UAA, testZone.getId()); MockHttpSession session = getAuthenticatedSession(developer); String state = generator.generate(); IdentityZoneHolder.clear(); //no providers is ok getMockMvc() .perform(get("/oauth/authorize").session(session) .with(new SetServerNameRequestPostProcessor(subdomain + ".localhost")) .param(OAuth2Utils.RESPONSE_TYPE, "code").param(OAuth2Utils.STATE, state) .param(OAuth2Utils.CLIENT_ID, clientId).param(OAuth2Utils.REDIRECT_URI, TEST_REDIRECT_URI)) .andExpect(status().isFound()); //correct provider is ok MvcResult result = getMockMvc() .perform(get("/oauth/authorize").session(session) .with(new SetServerNameRequestPostProcessor(subdomain + ".localhost")) .param(OAuth2Utils.RESPONSE_TYPE, "code").param(OAuth2Utils.STATE, state) .param(OAuth2Utils.CLIENT_ID, clientId2).param(OAuth2Utils.REDIRECT_URI, TEST_REDIRECT_URI)) .andExpect(status().isFound()).andReturn(); //other provider, not ok getMockMvc() .perform(get("/oauth/authorize").session(session) .with(new SetServerNameRequestPostProcessor(subdomain + ".localhost")) .param(OAuth2Utils.RESPONSE_TYPE, "code").param(OAuth2Utils.STATE, state) .param(OAuth2Utils.CLIENT_ID, clientId3).param(OAuth2Utils.REDIRECT_URI, TEST_REDIRECT_URI)) .andExpect(status().isUnauthorized()).andExpect(model().attributeExists("error")) .andExpect(model().attribute("error_message_code", "login.invalid_idp")); URL url = new URL(result.getResponse().getHeader("Location").replace("redirect#", "redirect?")); Map query = splitQuery(url); assertNotNull(query.get("code")); String code = ((List<String>) query.get("code")).get(0); assertNotNull(code); } @Test public void testClientIdentityProviderRestrictionForPasswordGrant() throws Exception { //a client with allowed providers in the default zone should be rejected if the client is not allowed String clientId = "testclient" + generator.generate(); String clientId2 = "testclient" + generator.generate(); String scopes = "space.*.developer,space.*.admin,org.*.reader,org.123*.admin,*.*,*,openid"; String idpOrigin = "origin-" + generator.generate(); IdentityProvider provider = setupIdentityProvider(idpOrigin); setUpClients(clientId, scopes, scopes, "authorization_code,password", true, TEST_REDIRECT_URI, Arrays.asList(provider.getOriginKey())); setUpClients(clientId2, scopes, scopes, "authorization_code,password", true, TEST_REDIRECT_URI, null); //create a user in the UAA identity provider String username = "testuser" + generator.generate(); String userScopes = "space.1.developer,space.2.developer,org.1.reader,org.2.reader,org.12345.admin,scope.one,scope.two,scope.three,openid"; ScimUser developer = setUpUser(username, userScopes, OriginKeys.UAA, IdentityZoneHolder.get().getId()); getMockMvc().perform(post("/oauth/token").param("username", username).param("password", "secret") .header("Authorization", "Basic " + new String(Base64.encode((clientId + ":" + SECRET).getBytes()))) .param(OAuth2Utils.RESPONSE_TYPE, "token").param(OAuth2Utils.GRANT_TYPE, "password") .param(OAuth2Utils.CLIENT_ID, clientId)).andExpect(status().isUnauthorized()); getMockMvc().perform(post("/oauth/token").param("username", username).param("password", "secret") .header("Authorization", "Basic " + new String(Base64.encode((clientId2 + ":" + SECRET).getBytes()))) .param(OAuth2Utils.RESPONSE_TYPE, "token").param(OAuth2Utils.GRANT_TYPE, "password") .param(OAuth2Utils.CLIENT_ID, clientId2)).andExpect(status().isOk()); } @Test public void test_Oauth_Authorize_API_Endpoint() throws Exception { String clientId = "testclient" + generator.generate(); String scopes = "openid,uaa.user,scim.me"; setUpClients(clientId, "", scopes, "authorization_code", true); String username = "testuser" + generator.generate(); String userScopes = ""; setUpUser(username, userScopes, OriginKeys.UAA, IdentityZoneHolder.get().getId()); String cfAccessToken = getUserOAuthAccessToken(getMockMvc(), "cf", "", username, SECRET, ""); String state = generator.generate(); MockHttpServletRequestBuilder oauthAuthorizeGet = get("/oauth/authorize") .header("Authorization", "Bearer " + cfAccessToken).param(OAuth2Utils.RESPONSE_TYPE, "code") .param(OAuth2Utils.SCOPE, "").param(OAuth2Utils.STATE, state) .param(OAuth2Utils.CLIENT_ID, clientId); MvcResult result = getMockMvc().perform(oauthAuthorizeGet).andExpect(status().is3xxRedirection()) .andReturn(); String location = result.getResponse().getHeader("Location"); assertNotNull("Location must be present", location); assertThat("Location must have a code parameter.", location, containsString("code=")); URL url = new URL(location); Map query = splitQuery(url); assertNotNull(query.get("code")); String code = ((List<String>) query.get("code")).get(0); assertNotNull(code); String body = getMockMvc() .perform(post("/oauth/token") .header("Authorization", "Basic " + new String(Base64.encode((clientId + ":" + SECRET).getBytes()))) .accept(APPLICATION_JSON).param(OAuth2Utils.RESPONSE_TYPE, "token") .param(OAuth2Utils.GRANT_TYPE, "authorization_code").param(OAuth2Utils.CLIENT_ID, clientId) .param("code", code)) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); assertNotNull("Token body must not be null.", body); assertThat(body, stringContainsInOrder(Arrays.asList("access_token", REFRESH_TOKEN))); Map<String, Object> map = JsonUtils.readValue(body, new TypeReference<Map<String, Object>>() { }); String accessToken = (String) map.get("access_token"); OAuth2Authentication token = tokenServices.loadAuthentication(accessToken); assertTrue("Must have uaa.user scope", token.getOAuth2Request().getScope().contains("uaa.user")); } @Test public void refreshTokenIssued_whenScopeIsPresent_andRestrictedOnGrantType() throws Exception { UaaTokenServices bean = getWebApplicationContext().getBean(UaaTokenServices.class); bean.setRestrictRefreshGrant(true); String clientId = "testclient" + generator.generate(); String scopes = "openid,uaa.user,scim.me," + UaaTokenServices.UAA_REFRESH_TOKEN; setUpClients(clientId, "", scopes, "password", true); String username = "testuser" + generator.generate(); String userScopes = UaaTokenServices.UAA_REFRESH_TOKEN; setUpUser(username, userScopes, OriginKeys.UAA, IdentityZoneHolder.get().getId()); MockHttpServletRequestBuilder oauthTokenPost = post("/oauth/token").param("username", username) .param("password", SECRET) .header("Authorization", "Basic " + new String(Base64.encode((clientId + ":" + SECRET).getBytes()))) .param(OAuth2Utils.RESPONSE_TYPE, "token").param(OAuth2Utils.GRANT_TYPE, "password"); MvcResult result = getMockMvc().perform(oauthTokenPost).andExpect(status().isOk()).andReturn(); Map token = JsonUtils.readValue(result.getResponse().getContentAsString(), Map.class); assertNotNull(token.get("access_token")); assertNotNull(token.get(REFRESH_TOKEN)); bean.setRestrictRefreshGrant(false); } @Test public void refreshAccessToken_whenScopeIsPresent_andRestrictedOnGrantType() throws Exception { UaaTokenServices bean = getWebApplicationContext().getBean(UaaTokenServices.class); bean.setRestrictRefreshGrant(true); String clientId = "testclient" + generator.generate(); String scopes = "openid,uaa.user,scim.me," + UaaTokenServices.UAA_REFRESH_TOKEN; setUpClients(clientId, "", scopes, "password,refresh_token", true); String username = "testuser" + generator.generate(); String userScopes = UaaTokenServices.UAA_REFRESH_TOKEN; setUpUser(username, userScopes, OriginKeys.UAA, IdentityZoneHolder.get().getId()); MockHttpServletRequestBuilder oauthTokenPost = post("/oauth/token").param("username", username) .param("password", SECRET) .header("Authorization", "Basic " + new String(Base64.encode((clientId + ":" + SECRET).getBytes()))) .param(OAuth2Utils.RESPONSE_TYPE, "token").param(OAuth2Utils.GRANT_TYPE, "password"); MvcResult mvcResult = getMockMvc().perform(oauthTokenPost).andReturn(); OAuth2RefreshToken refreshToken = JsonUtils .readValue(mvcResult.getResponse().getContentAsString(), CompositeAccessToken.class) .getRefreshToken(); MockHttpServletRequestBuilder postForRefreshToken = post("/oauth/token") .header("Authorization", "Basic " + new String(Base64.encode((clientId + ":" + SECRET).getBytes()))) .param(GRANT_TYPE, REFRESH_TOKEN).param(REFRESH_TOKEN, refreshToken.getValue()); getMockMvc().perform(postForRefreshToken).andExpect(status().isOk()); getMockMvc().perform(postForRefreshToken.param(REQUEST_TOKEN_FORMAT, OPAQUE)).andExpect(status().isOk()); getMockMvc().perform(postForRefreshToken.param(REQUEST_TOKEN_FORMAT, OPAQUE)).andExpect(status().isOk()); bean.setRestrictRefreshGrant(false); } @Test public void testOpenIdTokenHybridFlowWithNoImplicitGrant_When_IdToken_Disabled() throws Exception { try { getWebApplicationContext().getBean(DisableIdTokenResponseTypeFilter.class).setIdTokenDisabled(true); String clientId = "testclient" + generator.generate(); String scopes = "space.*.developer,space.*.admin,org.*.reader,org.123*.admin,*.*,*,openid"; setUpClients(clientId, scopes, scopes, "authorization_code", true); String username = "testuser" + generator.generate(); String userScopes = "space.1.developer,space.2.developer,org.1.reader,org.2.reader,org.12345.admin,scope.one,scope.two,scope.three,openid"; ScimUser developer = setUpUser(username, userScopes, OriginKeys.UAA, IdentityZoneHolder.get().getId()); MockHttpSession session = getAuthenticatedSession(developer); String state = generator.generate(); MockHttpServletRequestBuilder oauthTokenPost = get("/oauth/authorize").session(session) .param(OAuth2Utils.RESPONSE_TYPE, "code id_token").param(OAuth2Utils.SCOPE, "openid") .param(OAuth2Utils.STATE, state).param(OAuth2Utils.CLIENT_ID, clientId) .param(OAuth2Utils.REDIRECT_URI, TEST_REDIRECT_URI); MvcResult result = getMockMvc().perform(oauthTokenPost).andExpect(status().is3xxRedirection()) .andReturn(); String location = result.getResponse().getHeader("Location"); assertFalse(location.contains("#")); URL url = new URL(location); Map query = splitQuery(url); assertNotNull(query.get("code")); assertNull(query.get("id_token")); String code = ((List<String>) query.get("code")).get(0); assertNotNull(code); } finally { getWebApplicationContext().getBean(DisableIdTokenResponseTypeFilter.class).setIdTokenDisabled(false); } } @Test public void testOpenIdTokenHybridFlowWithNoImplicitGrant() throws Exception { String clientId = "testclient" + generator.generate(); String scopes = "space.*.developer,space.*.admin,org.*.reader,org.123*.admin,*.*,*,openid"; setUpClients(clientId, scopes, scopes, "authorization_code", true); String username = "testuser" + generator.generate(); String userScopes = "space.1.developer,space.2.developer,org.1.reader,org.2.reader,org.12345.admin,scope.one,scope.two,scope.three,openid"; ScimUser developer = setUpUser(username, userScopes, OriginKeys.UAA, IdentityZoneHolder.get().getId()); MockHttpSession session = getAuthenticatedSession(developer); String state = generator.generate(); MockHttpServletRequestBuilder oauthTokenPost = get("/oauth/authorize").session(session) .param(OAuth2Utils.RESPONSE_TYPE, "code id_token").param(OAuth2Utils.SCOPE, "openid") .param(OAuth2Utils.STATE, state).param(OAuth2Utils.CLIENT_ID, clientId) .param(OAuth2Utils.REDIRECT_URI, TEST_REDIRECT_URI); MvcResult result = getMockMvc().perform(oauthTokenPost).andExpect(status().is3xxRedirection()).andReturn(); String location = result.getResponse().getHeader("Location"); assertTrue(location.contains("#")); URL url = new URL(location.replace("redirect#", "redirect?")); Map query = splitQuery(url); assertNotNull(((List) query.get("id_token")).get(0)); assertNotNull(((List) query.get("code")).get(0)); assertNull(query.get("token")); } @Test public void prompt_is_none_and_approvals_are_required() throws Exception { String redirectUrl = TEST_REDIRECT_URI + "#test=true"; String clientId = "testclient" + new RandomValueStringGenerator().generate(); String scopes = "space.*.developer,space.*.admin,org.*.reader,org.123*.admin,*.*,*,openid"; setUpClients(clientId, scopes, scopes, "implicit,authorization_code", false); String username = "testuser" + new RandomValueStringGenerator().generate(); String userScopes = "space.1.developer,space.2.developer,org.1.reader,org.2.reader,org.12345.admin,scope.one,scope.two,scope.three,openid"; ScimUser developer = setUpUser(username, userScopes, OriginKeys.UAA, IdentityZoneHolder.get().getId()); MockHttpSession session = getAuthenticatedSession(developer); String state = new RandomValueStringGenerator().generate(); getMockMvc() .perform(post("/oauth/authorize").session(session).param(OAuth2Utils.RESPONSE_TYPE, "token") .param("prompt", "none").param(OAuth2Utils.CLIENT_ID, clientId) .param(OAuth2Utils.STATE, state).param(OAuth2Utils.REDIRECT_URI, redirectUrl) .with(cookieCsrf())) .andExpect(status().is3xxRedirection()) .andExpect(header().string("Location", startsWith(redirectUrl))) .andExpect(header().string("Location", containsString("error=interaction_required"))); } @Test public void testOpenIdTokenHybridFlowWithNoImplicitGrantWhenLenientWhenAppNotApproved() throws Exception { String clientId = "testclient" + generator.generate(); String scopes = "space.*.developer,space.*.admin,org.*.reader,org.123*.admin,*.*,*,openid"; setUpClients(clientId, scopes, scopes, "authorization_code", false); String username = "testuser" + generator.generate(); String userScopes = "space.1.developer,space.2.developer,org.1.reader,org.2.reader,org.12345.admin,scope.one,scope.two,scope.three,openid"; ScimUser developer = setUpUser(username, userScopes, OriginKeys.UAA, IdentityZoneHolder.get().getId()); MockHttpSession session = getAuthenticatedSession(developer); String state = generator.generate(); AuthorizationRequest authorizationRequest = new AuthorizationRequest(); authorizationRequest.setClientId(clientId); authorizationRequest.setRedirectUri(TEST_REDIRECT_URI); authorizationRequest.setScope(new ArrayList<>(Arrays.asList("openid"))); authorizationRequest.setResponseTypes(new TreeSet<>(Arrays.asList("code", "id_token"))); authorizationRequest.setState(state); session.setAttribute("authorizationRequest", authorizationRequest); MvcResult result = getMockMvc() .perform(post("/oauth/authorize").session(session).with(cookieCsrf()) .param(OAuth2Utils.USER_OAUTH_APPROVAL, "true").param("scope.0", "openid")) .andExpect(status().is3xxRedirection()).andReturn(); URL url = new URL(result.getResponse().getHeader("Location").replace("redirect#", "redirect?")); Map query = splitQuery(url); assertNotNull(query.get("code")); String code = ((List<String>) query.get("code")).get(0); assertNotNull(code); } @Test public void testOpenIdTokenHybridFlowWithNoImplicitGrantWhenStrictWhenAppNotApproved() throws Exception { String clientId = "testclient" + generator.generate(); String scopes = "space.*.developer,space.*.admin,org.*.reader,org.123*.admin,*.*,*,openid"; setUpClients(clientId, scopes, scopes, "authorization_code", false); String username = "testuser" + generator.generate(); String userScopes = "space.1.developer,space.2.developer,org.1.reader,org.2.reader,org.12345.admin,scope.one,scope.two,scope.three,openid"; ScimUser developer = setUpUser(username, userScopes, OriginKeys.UAA, IdentityZoneHolder.get().getId()); MockHttpSession session = getAuthenticatedSession(developer); String state = generator.generate(); AuthorizationRequest authorizationRequest = new AuthorizationRequest(); authorizationRequest.setClientId(clientId); authorizationRequest.setRedirectUri(TEST_REDIRECT_URI); authorizationRequest.setScope(new ArrayList<>(Arrays.asList("openid"))); authorizationRequest.setResponseTypes(new TreeSet<>(Arrays.asList("code", "id_token"))); authorizationRequest.setState(state); session.setAttribute("authorizationRequest", authorizationRequest); MvcResult result = getMockMvc() .perform(post("/oauth/authorize").session(session).param(OAuth2Utils.USER_OAUTH_APPROVAL, "true") .with(cookieCsrf()).param("scope.0", "openid")) .andExpect(status().is3xxRedirection()).andReturn(); URL url = new URL(result.getResponse().getHeader("Location").replace("redirect#", "redirect?")); Map query = splitQuery(url); assertNotNull(query.get("id_token")); assertNotNull(((List) query.get("id_token")).get(0)); assertNotNull(((List) query.get("code")).get(0)); assertNull(query.get("token")); } @Test public void test_subdomain_redirect_url() throws Exception { String redirectUri = "https://example.com/dashboard/?appGuid=app-guid&ace_config=test"; String subDomainUri = redirectUri.replace("example.com", "test.example.com"); String clientId = "authclient-" + generator.generate(); String scopes = "openid"; setUpClients(clientId, scopes, scopes, GRANT_TYPES, true, redirectUri); String username = "authuser" + generator.generate(); String userScopes = "openid"; ScimUser developer = setUpUser(username, userScopes, OriginKeys.UAA, IdentityZoneHolder.get().getId()); String basicDigestHeaderValue = "Basic " + new String( org.apache.commons.codec.binary.Base64.encodeBase64((clientId + ":" + SECRET).getBytes())); MockHttpSession session = getAuthenticatedSession(developer); String state = generator.generate(); MockHttpServletRequestBuilder authRequest = get("/oauth/authorize") .header("Authorization", basicDigestHeaderValue).session(session) .param(OAuth2Utils.RESPONSE_TYPE, "code").param(OAuth2Utils.SCOPE, "openid") .param(OAuth2Utils.STATE, state).param(OAuth2Utils.CLIENT_ID, clientId) .param(OAuth2Utils.REDIRECT_URI, subDomainUri); MvcResult result = getMockMvc().perform(authRequest).andExpect(status().is3xxRedirection()).andReturn(); String location = result.getResponse().getHeader("Location"); location = location.substring(0, location.indexOf("&code=")); assertEquals(subDomainUri, location); } @Test public void invalidScopeErrorMessageIsNotShowingAllClientScopes() throws Exception { String clientId = "testclient" + generator.generate(); String scopes = "openid"; setUpClients(clientId, scopes, scopes, "authorization_code", true); String username = "testuser" + generator.generate(); ScimUser developer = setUpUser(username, "scim.write", OriginKeys.UAA, IdentityZoneHolder.getUaaZone().getId()); MockHttpSession session = getAuthenticatedSession(developer); String basicDigestHeaderValue = "Basic " + new String( org.apache.commons.codec.binary.Base64.encodeBase64((clientId + ":" + SECRET).getBytes())); String state = generator.generate(); MockHttpServletRequestBuilder authRequest = get("/oauth/authorize") .header("Authorization", basicDigestHeaderValue).session(session) .param(OAuth2Utils.RESPONSE_TYPE, "code").param(OAuth2Utils.SCOPE, "scim.write") .param(OAuth2Utils.STATE, state).param(OAuth2Utils.CLIENT_ID, clientId) .param(OAuth2Utils.REDIRECT_URI, TEST_REDIRECT_URI); MvcResult mvcResult = getMockMvc().perform(authRequest).andExpect(status().is3xxRedirection()).andReturn(); UriComponents locationComponents = UriComponentsBuilder .fromUri(URI.create(mvcResult.getResponse().getHeader("Location"))).build(); MultiValueMap<String, String> queryParams = locationComponents.getQueryParams(); String errorMessage = URIUtil .encodeQuery("scim.write is invalid. Please use a valid scope name in the request"); assertTrue(!queryParams.containsKey("scope")); assertEquals(errorMessage, queryParams.getFirst("error_description")); } @Test public void invalidScopeErrorMessageIsNotShowingAllUserScopes() throws Exception { String clientId = "testclient" + generator.generate(); String scopes = "openid,password.write,cloud_controller.read,scim.userids,password.write,something.else"; setUpClients(clientId, scopes, scopes, "authorization_code", true); String username = "testuser" + generator.generate(); ScimUser developer = setUpUser(username, "openid", OriginKeys.UAA, IdentityZoneHolder.getUaaZone().getId()); MockHttpSession session = getAuthenticatedSession(developer); String basicDigestHeaderValue = "Basic " + new String( org.apache.commons.codec.binary.Base64.encodeBase64((clientId + ":" + SECRET).getBytes())); String state = generator.generate(); MockHttpServletRequestBuilder authRequest = get("/oauth/authorize") .header("Authorization", basicDigestHeaderValue).session(session) .param(OAuth2Utils.RESPONSE_TYPE, "code").param(OAuth2Utils.SCOPE, "something.else") .param(OAuth2Utils.STATE, state).param(OAuth2Utils.CLIENT_ID, clientId) .param(OAuth2Utils.REDIRECT_URI, TEST_REDIRECT_URI); MvcResult mvcResult = getMockMvc().perform(authRequest).andExpect(status().is3xxRedirection()).andReturn(); UriComponents locationComponents = UriComponentsBuilder .fromUri(URI.create(mvcResult.getResponse().getHeader("Location"))).build(); MultiValueMap<String, String> queryParams = locationComponents.getQueryParams(); String errorMessage = URIUtil .encodeQuery("[something.else] is invalid. This user is not allowed any of the requested scopes"); assertTrue(!queryParams.containsKey("scope")); assertEquals(errorMessage, queryParams.getFirst("error_description")); } @Test public void ensure_that_form_redirect_is_not_a_parameter_unless_there_is_a_saved_request() throws Exception { //make sure we don't create a session on the homepage assertNull(getMockMvc().perform(get("/login")).andDo(print()) .andExpect(content().string(not(containsString(FORM_REDIRECT_PARAMETER)))).andReturn().getRequest() .getSession(false)); //if there is a session, but no saved request getMockMvc().perform(get("/login").session(new MockHttpSession())).andDo(print()) .andExpect(content().string(not(containsString(FORM_REDIRECT_PARAMETER)))); } public void setAuthentication(MockHttpSession session, ScimUser developer) { UaaPrincipal p = new UaaPrincipal(developer.getId(), developer.getUserName(), developer.getPrimaryEmail(), OriginKeys.UAA, "", IdentityZoneHolder.get().getId()); UaaAuthentication auth = new UaaAuthentication(p, UaaAuthority.USER_AUTHORITIES, new UaaAuthenticationDetails(false, "clientId", OriginKeys.ORIGIN, "sessionId")); Assert.assertTrue(auth.isAuthenticated()); SecurityContextHolder.getContext().setAuthentication(auth); session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, new MockSecurityContext(auth)); } @Test public void test_authorization_code_grant_session_expires_during_app_approval() throws Exception { String username = "authuser" + generator.generate(); ScimUser user = setUpUser(username, "", OriginKeys.UAA, IdentityZoneHolder.get().getId()); String redirectUri = "http://localhost:8080/app/"; String clientId = "authclient-" + generator.generate(); String scopes = "openid,password.write,cloud_controller.read,scim.userids,password.write"; setUpClients(clientId, scopes, scopes, GRANT_TYPES, false, redirectUri); String state = generator.generate(); String url = UriComponentsBuilder.fromUriString( "/oauth/authorize?response_type=code&scope=openid&state={state}&client_id={clientId}&redirect_uri={redirectUri}") .buildAndExpand(state, clientId, redirectUri).encode().toUri().toString(); String encodedRedirectUri = UriUtils.encodeQueryParam(redirectUri, "ISO-8859-1"); MockHttpSession session = getAuthenticatedSession(user); MvcResult result = getMockMvc().perform(get(new URI(url)).session(session)).andDo(print()) .andExpect(status().isOk()).andExpect(forwardedUrl("/oauth/confirm_access")) .andExpect(model().attribute("original_uri", "http://localhost" + url)).andReturn(); } @Test public void test_authorization_code_grant_redirect_when_session_expires() throws Exception { String redirectUri = "https://example.com/dashboard/?appGuid=app-guid&ace_config=test"; String clientId = "authclient-" + generator.generate(); String scopes = "openid"; setUpClients(clientId, scopes, scopes, GRANT_TYPES, true, redirectUri); String username = "authuser" + generator.generate(); String userScopes = "openid"; ScimUser user = setUpUser(username, userScopes, OriginKeys.UAA, IdentityZoneHolder.get().getId()); String state = generator.generate(); String url = UriComponentsBuilder.fromUriString( "/oauth/authorize?response_type=code&scope=openid&state={state}&client_id={clientId}&redirect_uri={redirectUri}") .buildAndExpand(state, clientId, redirectUri).encode().toUri().toString(); String encodedRedirectUri = UriUtils.encodeQueryParam(redirectUri, "ISO-8859-1"); MvcResult result = getMockMvc().perform(get(new URI(url))).andExpect(status().is3xxRedirection()) .andReturn(); String location = result.getResponse().getHeader("Location"); assertThat(location, endsWith("/login")); MockHttpSession session = (MockHttpSession) result.getRequest().getSession(false); assertNotNull(session); SavedRequest savedRequest = (SavedRequest) session.getAttribute(SAVED_REQUEST_SESSION_ATTRIBUTE); assertNotNull(savedRequest); assertEquals("http://localhost" + url, savedRequest.getRedirectUrl()); getMockMvc().perform(get("/login").session(session)).andDo(print()).andExpect(status().isOk()) .andExpect(content().string(containsString(FORM_REDIRECT_PARAMETER))) .andExpect(content().string(containsString(encodedRedirectUri))); //a failed login should survive the flow //attempt to login without a session result = getMockMvc() .perform(post("/login.do").with(cookieCsrf()).param("form_redirect_uri", url) .param("username", username).param("password", "invalid")) .andExpect(status().isFound()).andExpect(header().string("Location", containsString("/login"))) .andReturn(); session = (MockHttpSession) result.getRequest().getSession(false); assertNotNull(session); savedRequest = (SavedRequest) session.getAttribute(SAVED_REQUEST_SESSION_ATTRIBUTE); assertNotNull(savedRequest); getMockMvc().perform(get("/login").session(session)).andDo(print()).andExpect(status().isOk()) .andExpect(content().string(containsString(FORM_REDIRECT_PARAMETER))) .andExpect(content().string(containsString(encodedRedirectUri))); //attempt to login without a session getMockMvc() .perform(post("/login.do").with(cookieCsrf()).param("form_redirect_uri", url) .param("username", username).param("password", SECRET)) .andExpect(status().isFound()).andExpect(header().string("Location", url)); } @Test public void testAuthorizationCodeGrantWithEncodedRedirectURL() throws Exception { String redirectUri = "https://example.com/dashboard/?appGuid=app-guid&ace_config=%7B%22orgGuid%22%3A%22org-guid%22%2C%22spaceGuid%22%3A%22space-guid%22%2C%22appGuid%22%3A%22app-guid%22%2C%22redirect%22%3A%22https%3A%2F%2Fexample.com%2F%22%7D"; //String redirectUri = "https://example.com/dashboard/?appGuid=app-guid&ace_config=test"; String clientId = "authclient-" + generator.generate(); String scopes = "openid"; setUpClients(clientId, scopes, scopes, GRANT_TYPES, true, redirectUri); String username = "authuser" + generator.generate(); String userScopes = "openid"; ScimUser developer = setUpUser(username, userScopes, OriginKeys.UAA, IdentityZoneHolder.get().getId()); String basicDigestHeaderValue = "Basic " + new String( org.apache.commons.codec.binary.Base64.encodeBase64((clientId + ":" + SECRET).getBytes())); MockHttpSession session = getAuthenticatedSession(developer); String state = generator.generate(); MockHttpServletRequestBuilder authRequest = get("/oauth/authorize") .header("Authorization", basicDigestHeaderValue).session(session) .param(OAuth2Utils.RESPONSE_TYPE, "code").param(OAuth2Utils.SCOPE, "openid") .param(OAuth2Utils.STATE, state).param(OAuth2Utils.CLIENT_ID, clientId) .param(OAuth2Utils.REDIRECT_URI, redirectUri); MvcResult result = getMockMvc().perform(authRequest).andExpect(status().is3xxRedirection()).andReturn(); String location = result.getResponse().getHeader("Location"); location = location.substring(0, location.indexOf("&code=")); assertEquals(redirectUri, location); } @Test public void make_sure_Bootstrapped_users_Dont_Revoke_Tokens_If_No_Change() throws Exception { String tokenString = getMockMvc() .perform(post("/oauth/token").param("username", "testbootuser").param("password", "password") .header("Authorization", "Basic " + new String(Base64.encode(("cf:").getBytes()))) .param(OAuth2Utils.RESPONSE_TYPE, "token").param(OAuth2Utils.GRANT_TYPE, "password") .param(OAuth2Utils.CLIENT_ID, "cf")) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); Map<String, Object> tokenResponse = JsonUtils.readValue(tokenString, new TypeReference<Map<String, Object>>() { }); String accessToken = (String) tokenResponse.get("access_token"); //ensure we can do scim.read getMockMvc() .perform(get("/Users").header("Authorization", "Bearer " + accessToken).accept(APPLICATION_JSON)) .andExpect(status().isOk()); ScimUserBootstrap bootstrap = getWebApplicationContext().getBean(ScimUserBootstrap.class); boolean isOverride = bootstrap.isOverride(); bootstrap.setOverride(true); bootstrap.afterPropertiesSet(); bootstrap.setOverride(isOverride); //ensure we can do scim.read with the existing token getMockMvc() .perform(get("/Users").header("Authorization", "Bearer " + accessToken).accept(APPLICATION_JSON)) .andExpect(status().isOk()); } @Test public void testAuthorizationCode_ShouldNot_Throw_500_If_Client_Doesnt_Exist() throws Exception { String redirectUri = "https://example.com/"; String clientId = "nonexistent-" + generator.generate(); String userScopes = "openid"; String state = generator.generate(); MockHttpServletRequestBuilder authRequest = get("/oauth/authorize").accept(MediaType.TEXT_HTML) .param(OAuth2Utils.RESPONSE_TYPE, "code id_token").param(OAuth2Utils.SCOPE, userScopes) .param(OAuth2Utils.STATE, state).param(OAuth2Utils.CLIENT_ID, clientId) .param(OAuth2Utils.REDIRECT_URI, redirectUri); MvcResult result = getMockMvc().perform(authRequest).andExpect(status().is3xxRedirection()).andReturn(); String location = result.getResponse().getHeader("Location"); HttpSession session = result.getRequest().getSession(false); MockHttpServletRequestBuilder login = get("/login").accept(MediaType.TEXT_HTML) .session((MockHttpSession) session); getMockMvc().perform(login).andExpect(status().isOk()); } @Test public void testImplicitGrantWithFragmentInRedirectURL() throws Exception { String redirectUri = "https://example.com/dashboard/?appGuid=app-guid#test"; testImplicitGrantRedirectUri(redirectUri, false); } @Test public void testImplicitGrantWithNoFragmentInRedirectURL() throws Exception { String redirectUri = "https://example.com/dashboard/?appGuid=app-guid"; testImplicitGrantRedirectUri(redirectUri, false); } @Test public void testImplicitGrantWithFragmentInRedirectURLAndNoPrompt() throws Exception { String redirectUri = "https://example.com/dashboard/?appGuid=app-guid#test"; testImplicitGrantRedirectUri(redirectUri, true); } @Test public void testImplicitGrantWithNoFragmentInRedirectURLAndNoPrompt() throws Exception { String redirectUri = "https://example.com/dashboard/?appGuid=app-guid"; testImplicitGrantRedirectUri(redirectUri, true); } @Test public void testWildcardRedirectURL() throws Exception { String state = generator.generate(); String clientId = "authclient-" + generator.generate(); String scopes = "openid"; String redirectUri = "http*://subdomain.domain.com/**/path2?query1=value1"; setUpClients(clientId, scopes, scopes, GRANT_TYPES, true, redirectUri); String username = "authuser" + generator.generate(); String userScopes = "openid"; ScimUser developer = setUpUser(username, userScopes, OriginKeys.UAA, IdentityZoneHolder.get().getId()); String basicDigestHeaderValue = "Basic " + new String( org.apache.commons.codec.binary.Base64.encodeBase64((clientId + ":" + SECRET).getBytes())); MockHttpSession session = getAuthenticatedSession(developer); String requestedUri = "https://subdomain.domain.com/path1/path2?query1=value1"; ResultMatcher status = status().is3xxRedirection(); performAuthorize(state, clientId, basicDigestHeaderValue, session, requestedUri, status); requestedUri = "http://subdomain.domain.com/path1/path2?query1=value1"; performAuthorize(state, clientId, basicDigestHeaderValue, session, requestedUri, status); requestedUri = "http://subdomain.domain.com/path1/path1a/path1b/path2?query1=value1"; performAuthorize(state, clientId, basicDigestHeaderValue, session, requestedUri, status); requestedUri = "https://wrongsub.domain.com/path1/path2?query1=value1"; status = status().is4xxClientError(); performAuthorize(state, clientId, basicDigestHeaderValue, session, requestedUri, status); requestedUri = "https://subdomain.domain.com/path1/path2?query1=value1&query2=value2"; status = status().is4xxClientError(); performAuthorize(state, clientId, basicDigestHeaderValue, session, requestedUri, status); } protected void performAuthorize(String state, String clientId, String basicDigestHeaderValue, MockHttpSession session, String requestedUri, ResultMatcher status) throws Exception { getMockMvc().perform(get("/oauth/authorize").header("Authorization", basicDigestHeaderValue) .session(session).param(OAuth2Utils.RESPONSE_TYPE, "token").param(OAuth2Utils.SCOPE, "openid") .param(OAuth2Utils.STATE, state).param(OAuth2Utils.CLIENT_ID, clientId) .param(OAuth2Utils.REDIRECT_URI, requestedUri)).andExpect(status); } protected void testImplicitGrantRedirectUri(String redirectUri, boolean noPrompt) throws Exception { String clientId = "authclient-" + generator.generate(); String scopes = "openid"; setUpClients(clientId, scopes, scopes, GRANT_TYPES, true, redirectUri); String username = "authuser" + generator.generate(); String userScopes = "openid"; ScimUser developer = setUpUser(username, userScopes, OriginKeys.UAA, IdentityZoneHolder.get().getId()); String basicDigestHeaderValue = "Basic " + new String( org.apache.commons.codec.binary.Base64.encodeBase64((clientId + ":" + SECRET).getBytes())); MockHttpSession session = getAuthenticatedSession(developer); String state = generator.generate(); MockHttpServletRequestBuilder authRequest = get("/oauth/authorize").session(session) .param(OAuth2Utils.RESPONSE_TYPE, "token").param(OAuth2Utils.SCOPE, "openid") .param(OAuth2Utils.STATE, state).param(OAuth2Utils.CLIENT_ID, clientId) .param(OAuth2Utils.REDIRECT_URI, redirectUri); if (noPrompt) { authRequest = authRequest.param(ID_TOKEN_HINT_PROMPT, ID_TOKEN_HINT_PROMPT_NONE); } MvcResult result = getMockMvc().perform(authRequest).andExpect(status().is3xxRedirection()).andReturn(); String location = result.getResponse().getHeader("Location"); constainsExactlyOneInstance(location, "#"); String[] locationParts = location.split("#"); String locationUri = locationParts[0]; String locationToken = locationParts[1]; assertEquals(redirectUri.split("#")[0], locationUri); String[] locationParams = locationToken.split("&"); assertThat(Arrays.asList(locationParams), hasItem(is("token_type=bearer"))); assertThat(Arrays.asList(locationParams), hasItem(startsWith("access_token="))); } private static void constainsExactlyOneInstance(String string, String substring) { assertTrue(string.contains(substring)); assertEquals(string.indexOf(substring), string.lastIndexOf(substring)); } @Test public void testOpenIdToken() throws Exception { RandomValueStringGenerator generator = this.generator; String clientId = "testclient" + generator.generate(); String scopes = "space.*.developer,space.*.admin,org.*.reader,org.123*.admin,*.*,*,openid"; setUpClients(clientId, scopes, scopes, GRANT_TYPES, true); String username = "testuser" + generator.generate(); String userScopes = "space.1.developer,space.2.developer,org.1.reader,org.2.reader,org.12345.admin,scope.one,scope.two,scope.three,openid"; ScimUser developer = setUpUser(username, userScopes, OriginKeys.UAA, IdentityZoneHolder.get().getId()); getWebApplicationContext().getBean(UaaUserDatabase.class).updateLastLogonTime(developer.getId()); getWebApplicationContext().getBean(UaaUserDatabase.class).updateLastLogonTime(developer.getId()); String authCodeClientId = "testclient" + generator.generate(); setUpClients(authCodeClientId, scopes, scopes, "authorization_code", true); String implicitClientId = "testclient" + generator.generate(); setUpClients(implicitClientId, scopes, scopes, "implicit", true); String basicDigestHeaderValue = "Basic " + new String( org.apache.commons.codec.binary.Base64.encodeBase64((clientId + ":" + SECRET).getBytes())); String authCodeBasicDigestHeaderValue = "Basic " + new String( org.apache.commons.codec.binary.Base64.encodeBase64((authCodeClientId + ":" + SECRET).getBytes())); //password grant - request for id_token MockHttpServletRequestBuilder oauthTokenPost = post("/oauth/token") .header("Authorization", basicDigestHeaderValue).param(OAuth2Utils.RESPONSE_TYPE, "token id_token") .param(OAuth2Utils.GRANT_TYPE, "password").param(OAuth2Utils.CLIENT_ID, clientId) .param("username", username).param("password", SECRET).param(OAuth2Utils.SCOPE, "openid"); MvcResult result = getMockMvc().perform(oauthTokenPost).andExpect(status().isOk()).andReturn(); Map token = JsonUtils.readValue(result.getResponse().getContentAsString(), Map.class); assertNotNull(token.get("access_token")); assertNotNull(token.get(REFRESH_TOKEN)); assertNotNull(token.get("id_token")); assertNotEquals(token.get("access_token"), token.get("id_token")); validateOpenIdConnectToken((String) token.get("id_token"), developer.getId(), clientId); //request for id_token using our old-style direct authentication //this returns a redirect with a fragment in the URL/Location header String credentials = String.format("{ \"username\":\"%s\", \"password\":\"%s\" }", username, SECRET); oauthTokenPost = post("/oauth/authorize").header("Accept", "application/json") .param(OAuth2Utils.RESPONSE_TYPE, "token id_token").param(OAuth2Utils.CLIENT_ID, implicitClientId) .param(OAuth2Utils.REDIRECT_URI, TEST_REDIRECT_URI).param("credentials", credentials) .param(OAuth2Utils.STATE, generator.generate()).param(OAuth2Utils.SCOPE, "openid"); result = getMockMvc().perform(oauthTokenPost).andExpect(status().is3xxRedirection()).andReturn(); URL url = new URL(result.getResponse().getHeader("Location").replace("redirect#", "redirect?")); token = splitQuery(url); assertNotNull(((List<String>) token.get("access_token")).get(0)); assertNotNull(((List<String>) token.get("id_token")).get(0)); assertNotEquals(((List<String>) token.get("access_token")).get(0), ((List<String>) token.get("id_token")).get(0)); validateOpenIdConnectToken(((List<String>) token.get("id_token")).get(0), developer.getId(), implicitClientId); //authorization_code grant - requesting id_token MockHttpSession session = new MockHttpSession(); setAuthentication(session, developer); String state = generator.generate(); oauthTokenPost = get("/oauth/authorize").header("Authorization", basicDigestHeaderValue).session(session) .param(OAuth2Utils.RESPONSE_TYPE, "code").param(OAuth2Utils.SCOPE, "openid") .param(OAuth2Utils.STATE, state).param(OAuth2Utils.CLIENT_ID, authCodeClientId) .param(ClaimConstants.NONCE, "testnonce").param(OAuth2Utils.REDIRECT_URI, TEST_REDIRECT_URI); result = getMockMvc().perform(oauthTokenPost).andExpect(status().is3xxRedirection()).andReturn(); url = new URL(result.getResponse().getHeader("Location")); token = splitQuery(url); assertNotNull(token.get(OAuth2Utils.STATE)); assertEquals(state, ((List<String>) token.get(OAuth2Utils.STATE)).get(0)); assertNotNull(token.get("code")); assertNotNull(((List<String>) token.get(OAuth2Utils.STATE)).get(0)); String code = ((List<String>) token.get("code")).get(0); oauthTokenPost = post("/oauth/token").header("Authorization", authCodeBasicDigestHeaderValue) .session(session).param(OAuth2Utils.GRANT_TYPE, "authorization_code").param("code", code) .param(OAuth2Utils.RESPONSE_TYPE, "token id_token").param(OAuth2Utils.SCOPE, "openid") .param(OAuth2Utils.STATE, state).param(OAuth2Utils.CLIENT_ID, authCodeClientId) .param(OAuth2Utils.REDIRECT_URI, TEST_REDIRECT_URI); result = getMockMvc().perform(oauthTokenPost).andExpect(status().isOk()).andReturn(); token = JsonUtils.readValue(result.getResponse().getContentAsString(), Map.class); assertNotNull(token.get("access_token")); assertNotNull(token.get(REFRESH_TOKEN)); assertNotNull(token.get("id_token")); assertNotEquals(token.get("access_token"), token.get("id_token")); validateOpenIdConnectToken((String) token.get("id_token"), developer.getId(), authCodeClientId); //nonce must be in id_token if was in auth request, see http://openid.net/specs/openid-connect-core-1_0.html#IDToken Map<String, Object> claims = getClaimsForToken((String) token.get("id_token")); assertEquals("testnonce", claims.get(ClaimConstants.NONCE)); //hybrid flow defined in - response_types=code token id_token //http://openid.net/specs/openid-connect-core-1_0.html#HybridFlowAuth session = new MockHttpSession(); setAuthentication(session, developer); state = generator.generate(); oauthTokenPost = get("/oauth/authorize").header("Authorization", basicDigestHeaderValue).session(session) .param(OAuth2Utils.RESPONSE_TYPE, "code id_token token").param(OAuth2Utils.SCOPE, "openid") .param(OAuth2Utils.STATE, state).param(OAuth2Utils.CLIENT_ID, clientId) .param(OAuth2Utils.REDIRECT_URI, TEST_REDIRECT_URI); result = getMockMvc().perform(oauthTokenPost).andExpect(status().is3xxRedirection()).andReturn(); url = new URL(result.getResponse().getHeader("Location").replace("redirect#", "redirect?")); token = splitQuery(url); assertNotNull(token.get(OAuth2Utils.STATE)); assertEquals(state, ((List<String>) token.get(OAuth2Utils.STATE)).get(0)); assertNotNull(token.get("code")); assertNotNull(((List<String>) token.get(OAuth2Utils.STATE)).get(0)); assertNotNull(((List<String>) token.get("access_token")).get(0)); assertNotNull(((List<String>) token.get("id_token")).get(0)); assertNotEquals(((List<String>) token.get("access_token")).get(0), ((List<String>) token.get("id_token")).get(0)); validateOpenIdConnectToken(((List<String>) token.get("id_token")).get(0), developer.getId(), clientId); //hybrid flow defined in - response_types=code token //http://openid.net/specs/openid-connect-core-1_0.html#HybridFlowAuth session = new MockHttpSession(); setAuthentication(session, developer); state = generator.generate(); oauthTokenPost = get("/oauth/authorize").header("Authorization", basicDigestHeaderValue).session(session) .param(OAuth2Utils.RESPONSE_TYPE, "code token").param(OAuth2Utils.SCOPE, "openid") .param(OAuth2Utils.STATE, state).param(OAuth2Utils.CLIENT_ID, clientId) .param(OAuth2Utils.REDIRECT_URI, TEST_REDIRECT_URI); result = getMockMvc().perform(oauthTokenPost).andExpect(status().is3xxRedirection()).andReturn(); url = new URL(result.getResponse().getHeader("Location").replace("redirect#", "redirect?")); token = splitQuery(url); assertNotNull(token.get(OAuth2Utils.STATE)); assertEquals(state, ((List<String>) token.get(OAuth2Utils.STATE)).get(0)); assertNotNull(token.get("code")); assertNotNull(((List<String>) token.get(OAuth2Utils.STATE)).get(0)); assertNotNull(((List<String>) token.get("access_token")).get(0)); //hybrid flow defined in - response_types=code id_token //http://openid.net/specs/openid-connect-core-1_0.html#HybridFlowAuth session = new MockHttpSession(); setAuthentication(session, developer); state = generator.generate(); oauthTokenPost = get("/oauth/authorize").session(session).param(OAuth2Utils.RESPONSE_TYPE, "code id_token") .param(OAuth2Utils.SCOPE, "openid").param(OAuth2Utils.STATE, state) .param(OAuth2Utils.CLIENT_ID, authCodeClientId).param(OAuth2Utils.REDIRECT_URI, TEST_REDIRECT_URI); result = getMockMvc().perform(oauthTokenPost).andExpect(status().is3xxRedirection()).andReturn(); url = new URL(result.getResponse().getHeader("Location").replace("redirect#", "redirect?")); token = splitQuery(url); assertNotNull(token.get(OAuth2Utils.STATE)); assertEquals(state, ((List<String>) token.get(OAuth2Utils.STATE)).get(0)); assertNotNull(token.get("code")); assertNotNull(((List<String>) token.get(OAuth2Utils.STATE)).get(0)); assertNotNull(((List<String>) token.get("id_token")).get(0)); assertNull(((List<String>) token.get("token"))); validateOpenIdConnectToken(((List<String>) token.get("id_token")).get(0), developer.getId(), authCodeClientId); //authorization code flow with parameter scope=openid //http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest session = new MockHttpSession(); setAuthentication(session, developer); state = generator.generate(); oauthTokenPost = get("/oauth/authorize").header("Authorization", basicDigestHeaderValue).session(session) .param(OAuth2Utils.RESPONSE_TYPE, "code").param(OAuth2Utils.SCOPE, "openid") .param(OAuth2Utils.STATE, state).param(OAuth2Utils.CLIENT_ID, clientId) .param(OAuth2Utils.REDIRECT_URI, TEST_REDIRECT_URI); result = getMockMvc().perform(oauthTokenPost).andExpect(status().is3xxRedirection()).andReturn(); assertFalse("Redirect URL should not be a fragment.", result.getResponse().getHeader("Location").contains("#")); url = new URL(result.getResponse().getHeader("Location")); token = splitQuery(url); assertNotNull(token.get(OAuth2Utils.STATE)); assertEquals(state, ((List<String>) token.get(OAuth2Utils.STATE)).get(0)); code = ((List<String>) token.get("code")).get(0); assertNotNull(code); oauthTokenPost = post("/oauth/token").accept(APPLICATION_JSON) .header("Authorization", basicDigestHeaderValue).param(OAuth2Utils.GRANT_TYPE, "authorization_code") .param(OAuth2Utils.REDIRECT_URI, TEST_REDIRECT_URI).param("code", code); result = getMockMvc().perform(oauthTokenPost).andExpect(status().isOk()).andReturn(); token = JsonUtils.readValue(result.getResponse().getContentAsString(), Map.class); assertNotNull("ID Token should be present when scope=openid", token.get("id_token")); assertNotNull(token.get("id_token")); validateOpenIdConnectToken((String) token.get("id_token"), developer.getId(), clientId); //authorization code flow without parameter scope=openid //http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest //this behavior should NOT return an id_token session = new MockHttpSession(); setAuthentication(session, developer); state = generator.generate(); oauthTokenPost = get("/oauth/authorize").header("Authorization", basicDigestHeaderValue).session(session) .param(OAuth2Utils.RESPONSE_TYPE, "code").param(OAuth2Utils.STATE, state) .param(OAuth2Utils.CLIENT_ID, clientId).param(OAuth2Utils.REDIRECT_URI, TEST_REDIRECT_URI); result = getMockMvc().perform(oauthTokenPost).andExpect(status().is3xxRedirection()).andReturn(); assertFalse("Redirect URL should not be a fragment.", result.getResponse().getHeader("Location").contains("#")); url = new URL(result.getResponse().getHeader("Location")); token = splitQuery(url); assertNotNull(token.get(OAuth2Utils.STATE)); assertEquals(state, ((List<String>) token.get(OAuth2Utils.STATE)).get(0)); code = ((List<String>) token.get("code")).get(0); assertNotNull(code); oauthTokenPost = post("/oauth/token").accept(APPLICATION_JSON) .header("Authorization", basicDigestHeaderValue).param(OAuth2Utils.GRANT_TYPE, "authorization_code") .param(OAuth2Utils.REDIRECT_URI, TEST_REDIRECT_URI).param("code", code); result = getMockMvc().perform(oauthTokenPost).andExpect(status().isOk()).andReturn(); token = JsonUtils.readValue(result.getResponse().getContentAsString(), Map.class); assertNull("ID Token should not be present when scope=openid is not present", token.get("id_token")); //test if we can retrieve an ID token using //response type token+id_token after a regular auth_code flow session = new MockHttpSession(); setAuthentication(session, developer); state = generator.generate(); oauthTokenPost = get("/oauth/authorize").header("Authorization", basicDigestHeaderValue).session(session) .param(OAuth2Utils.RESPONSE_TYPE, "code").param(OAuth2Utils.STATE, state) .param(OAuth2Utils.CLIENT_ID, clientId).param(OAuth2Utils.REDIRECT_URI, TEST_REDIRECT_URI); result = getMockMvc().perform(oauthTokenPost).andExpect(status().is3xxRedirection()).andReturn(); url = new URL(result.getResponse().getHeader("Location").replace("redirect#", "redirect?")); token = splitQuery(url); assertNotNull(token.get(OAuth2Utils.STATE)); assertEquals(state, ((List<String>) token.get(OAuth2Utils.STATE)).get(0)); code = ((List<String>) token.get("code")).get(0); assertNotNull(code); oauthTokenPost = post("/oauth/token").accept(APPLICATION_JSON) .header("Authorization", basicDigestHeaderValue).param(OAuth2Utils.GRANT_TYPE, "authorization_code") .param(OAuth2Utils.RESPONSE_TYPE, "token id_token") .param(OAuth2Utils.REDIRECT_URI, TEST_REDIRECT_URI).param("code", code); result = getMockMvc().perform(oauthTokenPost).andExpect(status().isOk()).andReturn(); token = JsonUtils.readValue(result.getResponse().getContentAsString(), Map.class); assertNotNull("ID Token should be present when response_type includes id_token", token.get("id_token")); assertNotNull(token.get("id_token")); assertNotNull(token.get("access_token")); validateOpenIdConnectToken((String) token.get("id_token"), developer.getId(), clientId); session = new MockHttpSession(); setAuthentication(session, developer); state = generator.generate(); oauthTokenPost = get("/oauth/authorize").session(session).param(OAuth2Utils.RESPONSE_TYPE, "id_token") .param(OAuth2Utils.STATE, state).param(OAuth2Utils.CLIENT_ID, implicitClientId) .param(OAuth2Utils.REDIRECT_URI, TEST_REDIRECT_URI); result = getMockMvc().perform(oauthTokenPost).andExpect(status().is3xxRedirection()).andReturn(); url = new URL(result.getResponse().getHeader("Location").replace("redirect#", "redirect?")); token = splitQuery(url); assertNotNull(token.get(OAuth2Utils.STATE)); assertNotNull(token.get("id_token")); assertEquals(state, ((List<String>) token.get(OAuth2Utils.STATE)).get(0)); } private void validateOpenIdConnectToken(String token, String userId, String clientId) throws Exception { Map<String, Object> result = getClaimsForToken(token); String iss = (String) result.get(ClaimConstants.ISS); assertEquals(tokenServices.getTokenEndpoint(), iss); String sub = (String) result.get(ClaimConstants.SUB); assertEquals(userId, sub); List<String> aud = (List<String>) result.get(ClaimConstants.AUD); assertTrue(aud.contains(clientId)); Integer exp = (Integer) result.get(ClaimConstants.EXP); assertNotNull(exp); Integer iat = (Integer) result.get(ClaimConstants.IAT); assertNotNull(iat); assertTrue(exp > iat); List<String> openid = (List<String>) result.get(ClaimConstants.SCOPE); Assert.assertThat(openid, containsInAnyOrder("openid")); //TODO OpenID Integer auth_time = (Integer) result.get(ClaimConstants.AUTH_TIME); assertNotNull(auth_time); Long previous_logon_time = (Long) result.get(ClaimConstants.PREVIOUS_LOGON_TIME); assertNotNull(previous_logon_time); Long dbPreviousLogonTime = getWebApplicationContext().getBean(UaaUserDatabase.class) .retrieveUserById(userId).getPreviousLogonTime(); assertEquals(dbPreviousLogonTime, previous_logon_time); } private Map<String, Object> getClaimsForToken(String token) { Jwt tokenJwt; try { tokenJwt = JwtHelper.decode(token); } catch (Throwable t) { throw new InvalidTokenException("Invalid token (could not decode): " + token); } Map<String, Object> claims; try { claims = JsonUtils.readValue(tokenJwt.getClaims(), new TypeReference<Map<String, Object>>() { }); } catch (Exception e) { throw new IllegalStateException("Cannot read token claims", e); } String kid = tokenJwt.getHeader().getKid(); assertNotNull("Token should have a key ID.", kid); tokenJwt.verifySignature(KeyInfo.getKey(kid).getVerifier()); return claims; } public static Map<String, List<String>> splitQuery(URL url) throws UnsupportedEncodingException { Map<String, List<String>> params = new LinkedHashMap<>(); String[] kv = url.getQuery().split("&"); for (String pair : kv) { int i = pair.indexOf("="); String key = i > 0 ? URLDecoder.decode(pair.substring(0, i), "UTF-8") : pair; if (!params.containsKey(key)) { params.put(key, new LinkedList<String>()); } String value = i > 0 && pair.length() > i + 1 ? URLDecoder.decode(pair.substring(i + 1), "UTF-8") : null; params.get(key).add(value); } return params; } @Test public void test_Token_Expiry_Time() throws Exception { String clientId = "testclient" + generator.generate(); String scopes = "space.*.developer,space.*.admin,org.*.reader,org.123*.admin,*.*,*"; setUpClients(clientId, scopes, scopes, GRANT_TYPES, true, null, null, 60 * 60 * 24 * 3650); String userId = "testuser" + generator.generate(); String userScopes = "space.1.developer,space.2.developer,org.1.reader,org.2.reader,org.12345.admin,scope.one,scope.two,scope.three"; ScimUser developer = setUpUser(userId, userScopes, OriginKeys.UAA, IdentityZoneHolder.get().getId()); Set<String> allUserScopes = new HashSet<>(); allUserScopes.addAll(defaultAuthorities); allUserScopes.addAll(StringUtils.commaDelimitedListToSet(userScopes)); String token = validatePasswordGrantToken(clientId, userId, "", allUserScopes.toArray(new String[0])); if (token.length() <= 36) { token = getWebApplicationContext().getBean(JdbcRevocableTokenProvisioning.class).retrieve(token) .getValue(); } Jwt tokenJwt = JwtHelper.decode(token); Map<String, Object> claims = JsonUtils.readValue(tokenJwt.getClaims(), new TypeReference<Map<String, Object>>() { }); Integer expirationTime = (Integer) claims.get(ClaimConstants.EXP); Calendar nineYearsAhead = new GregorianCalendar(); nineYearsAhead.setTimeInMillis(System.currentTimeMillis()); nineYearsAhead.add(Calendar.YEAR, 9); assertTrue("Expiration Date should be more than 9 years ahead.", new Date(expirationTime * 1000l).after(new Date(nineYearsAhead.getTimeInMillis()))); } @Test public void testWildcardPasswordGrant() throws Exception { String clientId = "testclient" + generator.generate(); String scopes = "space.*.developer,space.*.admin,org.*.reader,org.123*.admin,*.*,*"; setUpClients(clientId, scopes, scopes, GRANT_TYPES, true); String userId = "testuser" + generator.generate(); String userScopes = "space.1.developer,space.2.developer,org.1.reader,org.2.reader,org.12345.admin,scope.one,scope.two,scope.three"; ScimUser developer = setUpUser(userId, userScopes, OriginKeys.UAA, IdentityZoneHolder.get().getId()); Set<String> allUserScopes = new HashSet<>(); allUserScopes.addAll(defaultAuthorities); allUserScopes.addAll(StringUtils.commaDelimitedListToSet(userScopes)); validatePasswordGrantToken(clientId, userId, "", allUserScopes.toArray(new String[0])); validatePasswordGrantToken(clientId, userId, "space.*.developer", "space.1.developer", "space.2.developer"); validatePasswordGrantToken(clientId, userId, "space.2.developer", "space.2.developer"); validatePasswordGrantToken(clientId, userId, "org.123*.admin", "org.12345.admin"); validatePasswordGrantToken(clientId, userId, "org.123*.admin,space.1.developer", "org.12345.admin", "space.1.developer"); validatePasswordGrantToken(clientId, userId, "org.123*.admin,space.*.developer", "org.12345.admin", "space.1.developer", "space.2.developer"); Set<String> set1 = new HashSet<>(defaultAuthorities); set1.addAll(Arrays.asList("org.12345.admin", "space.1.developer", "space.2.developer", "scope.one", "scope.two", "scope.three")); set1.remove("openid"); set1.remove("profile"); set1.remove("roles"); set1.remove(ClaimConstants.USER_ATTRIBUTES); validatePasswordGrantToken(clientId, userId, "org.123*.admin,space.*.developer,*.*", set1.toArray(new String[0])); validatePasswordGrantToken(clientId, userId, "org.123*.admin,space.*.developer,scope.*", "org.12345.admin", "space.1.developer", "space.2.developer", "scope.one", "scope.two", "scope.three"); } public String validatePasswordGrantToken(String clientId, String username, String requestedScopes, String... expectedScopes) throws Exception { String t1 = testClient.getUserOAuthAccessToken(clientId, SECRET, username, SECRET, requestedScopes); OAuth2Authentication a1 = tokenServices.loadAuthentication(t1); assertEquals(expectedScopes.length, a1.getOAuth2Request().getScope().size()); assertThat(a1.getOAuth2Request().getScope(), containsInAnyOrder(expectedScopes)); return t1; } @Test public void testLoginAddNewUserForOauthTokenPasswordGrant() throws Exception { String loginToken = testClient.getClientCredentialsOAuthAccessToken("login", "loginsecret", ""); //the login server is matched by providing //1. Bearer token (will be authenticated for oauth.login scope) //2. source=login //3. grant_type=password //4. add_new=<any value> //without the above four parameters, it is not considered a external login-server request String username = generator.generate(); String email = username + "@addnew.test.org"; String first = "firstName"; String last = "lastName"; //success - contains everything we need getMockMvc().perform(post("/oauth/token").accept(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Bearer " + loginToken).param("source", "login").param("add_new", "true") .param("grant_type", "password").param("client_id", "cf").param("client_secret", "") .param("username", username).param("family_name", last).param("given_name", first) .param("email", email)).andExpect(status().isOk()); UaaUserDatabase db = getWebApplicationContext().getBean(UaaUserDatabase.class); UaaUser user = db.retrieveUserByName(username, OriginKeys.LOGIN_SERVER); assertNotNull(user); assertEquals(username, user.getUsername()); assertEquals(email, user.getEmail()); assertEquals(first, user.getGivenName()); assertEquals(last, user.getFamilyName()); } @Test public void testLoginAuthenticationFilter() throws Exception { String clientId = "testclient" + generator.generate(); String scopes = "space.*.developer,space.*.admin,org.*.reader,org.123*.admin,*.*,*"; setUpClients(clientId, scopes, scopes, GRANT_TYPES, true); String userId = "testuser" + generator.generate(); String userScopes = "space.1.developer,space.2.developer,org.1.reader,org.2.reader,org.12345.admin,scope.one,scope.two,scope.three"; ScimUser developer = setUpUser(userId, userScopes, OriginKeys.LOGIN_SERVER, IdentityZoneHolder.get().getId()); String loginToken = testClient.getClientCredentialsOAuthAccessToken("login", "loginsecret", ""); //the login server is matched by providing //1. Bearer token (will be authenticated for oauth.login scope) //2. source=login //3. grant_type=password //4. add_new=<any value> //without the above four parameters, it is not considered a external login-server request //success - contains everything we need getMockMvc() .perform(post("/oauth/token").accept(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Bearer " + loginToken).param("source", "login") .param("add_new", "false").param("grant_type", "password").param("client_id", clientId) .param("client_secret", SECRET).param("username", developer.getUserName()) .param("user_id", developer.getId()).param(OriginKeys.ORIGIN, developer.getOrigin())) .andExpect(status().isOk()); //success - user_id only, contains everything we need getMockMvc() .perform(post("/oauth/token").accept(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Bearer " + loginToken).param("source", "login") .param("add_new", "false").param("grant_type", "password").param("client_id", clientId) .param("client_secret", SECRET).param("user_id", developer.getId())) .andExpect(status().isOk()); //success - username/origin only, contains everything we need getMockMvc().perform(post("/oauth/token").accept(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Bearer " + loginToken).param("source", "login").param("add_new", "false") .param("grant_type", "password").param("client_id", clientId).param("client_secret", SECRET) .param("username", developer.getUserName()).param(OriginKeys.ORIGIN, developer.getOrigin())) .andExpect(status().isOk()); //failure - missing client ID getMockMvc().perform(post("/oauth/token").accept(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Bearer " + loginToken).param("source", "login").param("add_new", "false") .param("grant_type", "password").param("client_secret", SECRET).param("user_id", developer.getId())) .andExpect(status().isUnauthorized()); //failure - invalid client ID getMockMvc() .perform(post("/oauth/token").accept(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Bearer " + loginToken).param("source", "login") .param("add_new", "false").param("grant_type", "password").param("client_id", "dasdasdadas") .param("client_secret", SECRET).param("username", developer.getUserName()) .param("user_id", developer.getId()).param(OriginKeys.ORIGIN, developer.getOrigin())) .andExpect(status().isUnauthorized()); //failure - invalid client secret getMockMvc() .perform(post("/oauth/token").accept(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Bearer " + loginToken).param("source", "login") .param("add_new", "false").param("grant_type", "password").param("client_id", clientId) .param("client_secret", SECRET + "dasdasasas").param("user_id", developer.getId())) .andExpect(status().isUnauthorized()); //failure - missing client_id and secret getMockMvc().perform(post("/oauth/token").accept(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Bearer " + loginToken).param("source", "login").param("add_new", "false") .param("grant_type", "password").param("username", developer.getUserName()) .param("user_id", developer.getId()).param(OriginKeys.ORIGIN, developer.getOrigin())) .andExpect(status().isUnauthorized()); //failure - invalid user ID - user_id takes priority over username/origin so it must fail getMockMvc().perform(post("/oauth/token").accept(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Bearer " + loginToken).param("source", "login").param("add_new", "false") .param("grant_type", "password").param("client_id", clientId).param("client_secret", SECRET) .param("username", developer.getUserName()).param("user_id", developer.getId() + "1dsda") .param(OriginKeys.ORIGIN, developer.getOrigin())).andExpect(status().isUnauthorized()); //failure - no user ID and an invalid origin must fail getMockMvc() .perform(post("/oauth/token").accept(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Bearer " + loginToken).param("source", "login") .param("add_new", "false").param("grant_type", "password").param("client_id", clientId) .param("client_secret", SECRET).param("username", developer.getUserName()) .param(OriginKeys.ORIGIN, developer.getOrigin() + "dasda")) .andExpect(status().isUnauthorized()); //failure - no user ID, invalid username must fail getMockMvc().perform(post("/oauth/token").accept(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Bearer " + loginToken).param("source", "login").param("add_new", "false") .param("grant_type", "password").param("client_id", clientId).param("client_secret", SECRET) .param("username", developer.getUserName() + "asdasdas") .param(OriginKeys.ORIGIN, developer.getOrigin())).andExpect(status().isUnauthorized()); //success - pretend to be login server - add new user is true - any username will be added getMockMvc().perform(post("/oauth/token").accept(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Bearer " + loginToken).param("source", "login").param("add_new", "true") .param("grant_type", "password").param("client_id", clientId).param("client_secret", SECRET) .param("username", developer.getUserName() + "AddNew" + (generator.generate())) .param(OriginKeys.ORIGIN, developer.getOrigin())).andExpect(status().isOk()); //failure - pretend to be login server - add new user is false getMockMvc().perform(post("/oauth/token").accept(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Bearer " + loginToken).param("source", "login").param("add_new", "false") .param("grant_type", "password").param("client_id", clientId).param("client_secret", SECRET) .param("username", developer.getUserName() + "AddNew" + (generator.generate())) .param(OriginKeys.ORIGIN, developer.getOrigin())).andExpect(status().isUnauthorized()); //failure - source=login missing, so missing user password should trigger a failure getMockMvc().perform(post("/oauth/token").accept(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Bearer " + loginToken).param("add_new", "false") .param("grant_type", "password").param("client_id", clientId).param("client_secret", SECRET) .param("username", developer.getUserName()).param("user_id", developer.getId()) .param(OriginKeys.ORIGIN, developer.getOrigin())).andExpect(status().isUnauthorized()); //failure - add_new is missing, so missing user password should trigger a failure getMockMvc().perform(post("/oauth/token").accept(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Bearer " + loginToken).param("source", "login") .param("grant_type", "password").param("client_id", clientId).param("client_secret", SECRET) .param("username", developer.getUserName()).param("user_id", developer.getId()) .param(OriginKeys.ORIGIN, developer.getOrigin())).andExpect(status().isUnauthorized()); } @Test public void testOtherOauthResourceLoginAuthenticationFilter() throws Exception { String clientId = "testclient" + generator.generate(); String scopes = "space.*.developer,space.*.admin,org.*.reader,org.123*.admin,*.*,*"; setUpClients(clientId, scopes, scopes, GRANT_TYPES, true); String oauthClientId = "testclient" + generator.generate(); String oauthScopes = "space.*.developer,space.*.admin,org.*.reader,org.123*.admin,*.*,*,oauth.something"; setUpClients(oauthClientId, oauthScopes, oauthScopes, GRANT_TYPES, true); String userId = "testuser" + generator.generate(); String userScopes = "space.1.developer,space.2.developer,org.1.reader,org.2.reader,org.12345.admin,scope.one,scope.two,scope.three"; ScimUser developer = setUpUser(userId, userScopes, OriginKeys.UAA, IdentityZoneHolder.get().getId()); String loginToken = testClient.getClientCredentialsOAuthAccessToken(oauthClientId, SECRET, ""); //failure - success only if token has oauth.login getMockMvc() .perform(post("/oauth/token").accept(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Bearer " + loginToken).param("source", "login") .param("add_new", "false").param("grant_type", "password").param("client_id", clientId) .param("client_secret", SECRET).param("username", developer.getUserName()) .param("user_id", developer.getId()).param(OriginKeys.ORIGIN, developer.getOrigin())) .andExpect(status().isForbidden()); //failure - success only if token has oauth.login getMockMvc() .perform(post("/oauth/token").accept(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Bearer " + loginToken).param("source", "login") .param("add_new", "false").param("grant_type", "password").param("client_id", clientId) .param("client_secret", SECRET).param("user_id", developer.getId())) .andExpect(status().isForbidden()); //failure - success only if token has oauth.login getMockMvc().perform(post("/oauth/token").accept(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Bearer " + loginToken).param("source", "login").param("add_new", "false") .param("grant_type", "password").param("client_id", clientId).param("client_secret", SECRET) .param("username", developer.getUserName()).param(OriginKeys.ORIGIN, developer.getOrigin())) .andExpect(status().isForbidden()); //failure - missing client ID getMockMvc().perform(post("/oauth/token").accept(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Bearer " + loginToken).param("source", "login").param("add_new", "false") .param("grant_type", "password").param("client_secret", SECRET).param("user_id", developer.getId())) .andExpect(status().isForbidden()); //failure - invalid client ID getMockMvc() .perform(post("/oauth/token").accept(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Bearer " + loginToken).param("source", "login") .param("add_new", "false").param("grant_type", "password").param("client_id", "dasdasdadas") .param("client_secret", SECRET).param("username", developer.getUserName()) .param("user_id", developer.getId()).param(OriginKeys.ORIGIN, developer.getOrigin())) .andExpect(status().isForbidden()); //failure - invalid client secret getMockMvc() .perform(post("/oauth/token").accept(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Bearer " + loginToken).param("source", "login") .param("add_new", "false").param("grant_type", "password").param("client_id", clientId) .param("client_secret", SECRET + "dasdasasas").param("user_id", developer.getId())) .andExpect(status().isForbidden()); //failure - missing client_id and secret getMockMvc().perform(post("/oauth/token").accept(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Bearer " + loginToken).param("source", "login").param("add_new", "false") .param("grant_type", "password").param("username", developer.getUserName()) .param("user_id", developer.getId()).param(OriginKeys.ORIGIN, developer.getOrigin())) .andExpect(status().isForbidden()); //failure - invalid user ID - user_id takes priority over username/origin so it must fail getMockMvc().perform(post("/oauth/token").accept(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Bearer " + loginToken).param("source", "login").param("add_new", "false") .param("grant_type", "password").param("client_id", clientId).param("client_secret", SECRET) .param("username", developer.getUserName()).param("user_id", developer.getId() + "1dsda") .param(OriginKeys.ORIGIN, developer.getOrigin())).andExpect(status().isForbidden()); //failure - no user ID and an invalid origin must fail getMockMvc() .perform(post("/oauth/token").accept(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Bearer " + loginToken).param("source", "login") .param("add_new", "false").param("grant_type", "password").param("client_id", clientId) .param("client_secret", SECRET).param("username", developer.getUserName()) .param(OriginKeys.ORIGIN, developer.getOrigin() + "dasda")) .andExpect(status().isForbidden()); //failure - no user ID, invalid username must fail getMockMvc().perform(post("/oauth/token").accept(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Bearer " + loginToken).param("source", "login").param("add_new", "false") .param("grant_type", "password").param("client_id", clientId).param("client_secret", SECRET) .param("username", developer.getUserName() + "asdasdas") .param(OriginKeys.ORIGIN, developer.getOrigin())).andExpect(status().isForbidden()); //failure - success only if token has oauth.login getMockMvc().perform(post("/oauth/token").accept(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Bearer " + loginToken).param("source", "login").param("add_new", "true") .param("grant_type", "password").param("client_id", clientId).param("client_secret", SECRET) .param("username", developer.getUserName() + "AddNew" + (generator.generate())) .param(OriginKeys.ORIGIN, developer.getOrigin())).andExpect(status().isForbidden()); //failure - pretend to be login server - add new user is false getMockMvc().perform(post("/oauth/token").accept(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Bearer " + loginToken).param("source", "login").param("add_new", "false") .param("grant_type", "password").param("client_id", clientId).param("client_secret", SECRET) .param("username", developer.getUserName() + "AddNew" + (generator.generate())) .param(OriginKeys.ORIGIN, developer.getOrigin())).andExpect(status().isForbidden()); } @Test public void testOtherClientAuthenticationMethods() throws Exception { String clientId = "testclient" + generator.generate(); String scopes = "space.*.developer,space.*.admin,org.*.reader,org.123*.admin,*.*,*"; setUpClients(clientId, scopes, scopes, GRANT_TYPES, true); String oauthClientId = "testclient" + generator.generate(); String oauthScopes = "space.*.developer,space.*.admin,org.*.reader,org.123*.admin,*.*,*,oauth.something"; setUpClients(oauthClientId, oauthScopes, oauthScopes, GRANT_TYPES, true); String userId = "testuser" + generator.generate(); String userScopes = "space.1.developer,space.2.developer,org.1.reader,org.2.reader,org.12345.admin,scope.one,scope.two,scope.three"; ScimUser developer = setUpUser(userId, userScopes, OriginKeys.UAA, IdentityZoneHolder.get().getId()); String loginToken = testClient.getClientCredentialsOAuthAccessToken(oauthClientId, SECRET, ""); //success - regular password grant but client is authenticated using POST parameters getMockMvc() .perform(post("/oauth/token").accept(MediaType.APPLICATION_JSON_VALUE) .param("grant_type", "password").param("client_id", clientId).param("client_secret", SECRET) .param("username", developer.getUserName()).param("password", SECRET)) .andExpect(status().isUnauthorized()); //success - regular password grant but client is authenticated using token getMockMvc() .perform(post("/oauth/token").accept(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Bearer " + loginToken).param("grant_type", "password") .param("client_id", oauthClientId).param("client_secret", SECRET) .param("username", developer.getUserName()).param("password", SECRET)) .andExpect(status().isUnauthorized()); //failure - client ID mismatch getMockMvc() .perform(post("/oauth/token").accept(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Basic " + new String(Base64.encode((oauthClientId + ":" + SECRET).getBytes()))) .param("grant_type", "password").param("client_id", clientId).param("client_secret", SECRET) .param("username", developer.getUserName()).param("password", SECRET)) .andExpect(status().isUnauthorized()); //failure - client ID mismatch getMockMvc() .perform(post("/oauth/token").accept(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Bearer " + loginToken).param("grant_type", "password") .param("client_id", clientId).param("client_secret", SECRET) .param("username", developer.getUserName()).param("password", SECRET)) .andExpect(status().isUnauthorized()); } @Test public void testGetClientCredentialsTokenForDefaultIdentityZone() throws Exception { String clientId = "testclient" + generator.generate(); String scopes = "space.*.developer,space.*.admin,org.*.reader,org.123*.admin,*.*,*"; setUpClients(clientId, scopes, scopes, GRANT_TYPES, true); String body = getMockMvc() .perform(post("/oauth/token").accept(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Basic " + new String(Base64.encode((clientId + ":" + SECRET).getBytes()))) .param("grant_type", "client_credentials").param("client_id", clientId) .param("client_secret", SECRET)) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); Map<String, Object> bodyMap = JsonUtils.readValue(body, new TypeReference<Map<String, Object>>() { }); assertNotNull(bodyMap.get("access_token")); Jwt jwt = JwtHelper.decode((String) bodyMap.get("access_token")); Map<String, Object> claims = JsonUtils.readValue(jwt.getClaims(), new TypeReference<Map<String, Object>>() { }); assertNotNull(claims.get(ClaimConstants.AUTHORITIES)); assertNotNull(claims.get(ClaimConstants.AZP)); assertNull(claims.get(ClaimConstants.USER_ID)); } @Test public void clientCredentials_byDefault_willNotLockoutClientsUsingFormData() throws Exception { String clientId = "testclient" + generator.generate(); String scopes = "space.*.developer,space.*.admin,org.*.reader,org.123*.admin,*.*,*"; setUpClients(clientId, scopes, scopes, GRANT_TYPES, true); for (int i = 0; i < 6; i++) { tryLoginWithWrongSecretInBody(clientId); } getMockMvc() .perform(post("/oauth/token").accept(MediaType.APPLICATION_JSON_VALUE) .contentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE) .param("grant_type", "client_credentials").param("client_id", clientId) .param("client_secret", SECRET)) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); } @Test public void clientCredentials_byDefault_WillNotLockoutDuringFailedBasicAuth() throws Exception { String clientId = "testclient" + generator.generate(); String scopes = "space.*.developer,space.*.admin,org.*.reader,org.123*.admin,*.*,*"; setUpClients(clientId, scopes, scopes, GRANT_TYPES, true); for (int i = 0; i < 6; i++) { tryLoginWithWrongSecretInHeader(clientId); } login(clientId); } @Test public void clientCredentials_byDefault_WillNotLockoutDuringFailedBasicAuthAndFormData() throws Exception { String clientId = "testclient" + generator.generate(); String scopes = "space.*.developer,space.*.admin,org.*.reader,org.123*.admin,*.*,*"; setUpClients(clientId, scopes, scopes, GRANT_TYPES, true); for (int i = 0; i < 3; i++) { tryLoginWithWrongSecretInHeader(clientId); tryLoginWithWrongSecretInBody(clientId); } login(clientId); } private void tryLoginWithWrongSecretInHeader(String clientId) throws Exception { getMockMvc() .perform(post("/oauth/token").accept(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Basic " + new String(Base64.encode((clientId + ":" + BADSECRET).getBytes()))) .param("grant_type", "client_credentials")) .andExpect(status().isUnauthorized()).andReturn().getResponse().getContentAsString(); } private void tryLoginWithWrongSecretInBody(String clientId) throws Exception { getMockMvc() .perform(post("/oauth/token").accept(MediaType.APPLICATION_JSON_VALUE) .contentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE) .param("grant_type", "client_credentials").param("client_id", clientId) .param("client_secret", BADSECRET)) .andExpect(status().isUnauthorized()).andReturn().getResponse().getContentAsString(); } private void login(String clientId) throws Exception { getMockMvc() .perform(post("/oauth/token").accept(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Basic " + new String(Base64.encode((clientId + ":" + SECRET).getBytes()))) .param("grant_type", "client_credentials")) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); } @Test public void validateOldTokenAfterAddClientSecret() throws Exception { String clientId = "testclient" + generator.generate(); String scopes = "space.*.developer,space.*.admin,org.*.reader,org.123*.admin,*.*,*"; setUpClients(clientId, scopes, scopes, GRANT_TYPES, true); String body = getMockMvc() .perform(post("/oauth/token").accept(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Basic " + new String(Base64.encode((clientId + ":" + SECRET).getBytes()))) .param("grant_type", "client_credentials").param("client_id", clientId) .param("client_secret", SECRET)) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); Map<String, Object> bodyMap = JsonUtils.readValue(body, new TypeReference<Map<String, Object>>() { }); String access_token = (String) bodyMap.get("access_token"); assertNotNull(access_token); clientDetailsService.addClientSecret(clientId, "newSecret"); getMockMvc().perform(post("/check_token") .header("Authorization", "Basic " + new String(Base64.encode("app:appclientsecret".getBytes()))) .param("token", access_token)).andDo(print()).andExpect(status().isOk()); } @Test public void validateNewTokenAfterAddClientSecret() throws Exception { String clientId = "testclient" + generator.generate(); String scopes = "space.*.developer,space.*.admin,org.*.reader,org.123*.admin,*.*,*"; setUpClients(clientId, scopes, scopes, GRANT_TYPES, true); clientDetailsService.addClientSecret(clientId, "newSecret"); for (String secret : Arrays.asList(SECRET, "newSecret")) { String body = getMockMvc() .perform(post("/oauth/token").accept(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Basic " + new String(Base64.encode((clientId + ":" + SECRET).getBytes()))) .param("grant_type", "client_credentials").param("client_id", clientId) .param("client_secret", secret)) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); Map<String, Object> bodyMap = JsonUtils.readValue(body, new TypeReference<Map<String, Object>>() { }); String access_token = (String) bodyMap.get("access_token"); assertNotNull(access_token); getMockMvc().perform(post("/check_token") .header("Authorization", "Basic " + new String(Base64.encode("app:appclientsecret".getBytes()))) .param("token", access_token)).andExpect(status().isOk()); } } @Test public void validateOldTokenAfterDeleteClientSecret() throws Exception { String clientId = "testclient" + generator.generate(); String scopes = "space.*.developer,space.*.admin,org.*.reader,org.123*.admin,*.*,*"; setUpClients(clientId, scopes, scopes, GRANT_TYPES, true); String body = getMockMvc() .perform(post("/oauth/token").accept(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Basic " + new String(Base64.encode((clientId + ":" + SECRET).getBytes()))) .param("grant_type", "client_credentials").param("client_id", clientId) .param("client_secret", SECRET)) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); Map<String, Object> bodyMap = JsonUtils.readValue(body, new TypeReference<Map<String, Object>>() { }); String access_token = (String) bodyMap.get("access_token"); assertNotNull(access_token); clientDetailsService.addClientSecret(clientId, "newSecret"); clientDetailsService.deleteClientSecret(clientId); MockHttpServletResponse response = getMockMvc().perform(post("/check_token") .header("Authorization", "Basic " + new String(Base64.encode("app:appclientsecret".getBytes()))) .param("token", access_token)).andExpect(status().isBadRequest()).andReturn().getResponse(); InvalidTokenException tokenRevokedException = JsonUtils.readValue(response.getContentAsString(), TokenRevokedException.class); assertEquals("invalid_token", tokenRevokedException.getOAuth2ErrorCode()); assertEquals("revocable signature mismatch", tokenRevokedException.getMessage()); } @Test public void validateNewTokenBeforeDeleteClientSecret() throws Exception { String clientId = "testclient" + generator.generate(); String scopes = "space.*.developer,space.*.admin,org.*.reader,org.123*.admin,*.*,*"; setUpClients(clientId, scopes, scopes, GRANT_TYPES, true); clientDetailsService.addClientSecret(clientId, "newSecret"); String body = getMockMvc() .perform(post("/oauth/token").accept(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Basic " + new String(Base64.encode((clientId + ":" + SECRET).getBytes()))) .param("grant_type", "client_credentials").param("client_id", clientId) .param("client_secret", SECRET)) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); Map<String, Object> bodyMap = JsonUtils.readValue(body, new TypeReference<Map<String, Object>>() { }); String access_token = (String) bodyMap.get("access_token"); assertNotNull(access_token); clientDetailsService.deleteClientSecret(clientId); getMockMvc().perform(post("/check_token") .header("Authorization", "Basic " + new String(Base64.encode("app:appclientsecret".getBytes()))) .param("token", access_token)).andExpect(status().isOk()); } @Test public void validateNewTokenAfterDeleteClientSecret() throws Exception { String clientId = "testclient" + generator.generate(); String scopes = "space.*.developer,space.*.admin,org.*.reader,org.123*.admin,*.*,*"; setUpClients(clientId, scopes, scopes, GRANT_TYPES, true); clientDetailsService.addClientSecret(clientId, "newSecret"); clientDetailsService.deleteClientSecret(clientId); String body = getMockMvc() .perform(post("/oauth/token").accept(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Basic " + new String(Base64.encode((clientId + ":newSecret").getBytes()))) .param("grant_type", "client_credentials").param("client_id", clientId) .param("client_secret", SECRET)) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); Map<String, Object> bodyMap = JsonUtils.readValue(body, new TypeReference<Map<String, Object>>() { }); String access_token = (String) bodyMap.get("access_token"); assertNotNull(access_token); getMockMvc().perform(post("/check_token") .header("Authorization", "Basic " + new String(Base64.encode("app:appclientsecret".getBytes()))) .param("token", access_token)).andExpect(status().isOk()); } @Test public void revokeOwnJWToken() throws Exception { IdentityZone defaultZone = identityZoneProvisioning.retrieve(IdentityZone.getUaa().getId()); defaultZone.getConfig().getTokenPolicy().setJwtRevocable(true); identityZoneProvisioning.update(defaultZone); try { BaseClientDetails client = new BaseClientDetails(generator.generate(), "", "openid", "client_credentials,password", "clients.write"); client.setClientSecret("secret"); createClient(getMockMvc(), adminToken, client); //this is the token we will revoke String clientToken = getClientCredentialsOAuthAccessToken(getMockMvc(), client.getClientId(), client.getClientSecret(), null, null); Jwt jwt = JwtHelper.decode(clientToken); Map<String, Object> claims = JsonUtils.readValue(jwt.getClaims(), new TypeReference<Map<String, Object>>() { }); String jti = (String) claims.get("jti"); getMockMvc() .perform(delete("/oauth/token/revoke/" + jti).header("Authorization", "Bearer " + clientToken)) .andExpect(status().isOk()); tokenProvisioning.retrieve(jti); } catch (EmptyResultDataAccessException e) { } finally { defaultZone.getConfig().getTokenPolicy().setJwtRevocable(false); identityZoneProvisioning.update(defaultZone); } } @Test public void revokeOtherClientToken() throws Exception { String resourceClientId = generator.generate(); BaseClientDetails resourceClient = new BaseClientDetails(resourceClientId, "", "uaa.resource", "client_credentials,password", "uaa.resource"); resourceClient.setClientSecret("secret"); createClient(getMockMvc(), adminToken, resourceClient); BaseClientDetails client = new BaseClientDetails(generator.generate(), "", "openid", "client_credentials,password", "tokens.revoke"); client.setClientSecret("secret"); createClient(getMockMvc(), adminToken, client); //this is the token we will revoke String revokeAccessToken = getClientCredentialsOAuthAccessToken(getMockMvc(), client.getClientId(), client.getClientSecret(), "tokens.revoke", null, false); String tokenToBeRevoked = getClientCredentialsOAuthAccessToken(getMockMvc(), resourceClientId, resourceClient.getClientSecret(), null, null, true); getMockMvc().perform(delete("/oauth/token/revoke/" + tokenToBeRevoked).header("Authorization", "Bearer " + revokeAccessToken)).andExpect(status().isOk()); try { tokenProvisioning.retrieve(tokenToBeRevoked); fail("Token should have been deleted"); } catch (EmptyResultDataAccessException e) { //expected } } @Test public void revokeOtherClientTokenForbidden() throws Exception { String resourceClientId = generator.generate(); BaseClientDetails resourceClient = new BaseClientDetails(resourceClientId, "", "uaa.resource", "client_credentials,password", "uaa.resource"); resourceClient.setClientSecret("secret"); createClient(getMockMvc(), adminToken, resourceClient); BaseClientDetails client = new BaseClientDetails(generator.generate(), "", "openid", "client_credentials,password", null); client.setClientSecret("secret"); createClient(getMockMvc(), adminToken, client); //this is the token we will revoke String revokeAccessToken = getClientCredentialsOAuthAccessToken(getMockMvc(), client.getClientId(), client.getClientSecret(), null, null, false); String tokenToBeRevoked = getClientCredentialsOAuthAccessToken(getMockMvc(), resourceClientId, resourceClient.getClientSecret(), null, null, true); getMockMvc().perform(delete("/oauth/token/revoke/" + tokenToBeRevoked).header("Authorization", "Bearer " + revokeAccessToken)).andExpect(status().isForbidden()); } @Test public void revokeOpaqueTokenWithOpaqueToken() throws Exception { ScimUser scimUser = setUpUser("testUser" + generator.generate()); String opaqueUserToken = testClient.getUserOAuthAccessToken("app", "appclientsecret", scimUser.getUserName(), "secret", null); getMockMvc().perform(delete("/oauth/token/revoke/" + opaqueUserToken).header("Authorization", "Bearer " + opaqueUserToken)).andExpect(status().isOk()); try { tokenProvisioning.retrieve(opaqueUserToken); } catch (EmptyResultDataAccessException e) { } } @Test public void test_Revoke_Client_And_User_Tokens() throws Exception { BaseClientDetails client = getAClientWithClientsRead(); BaseClientDetails otherClient = getAClientWithClientsRead(); //this is the token we will revoke String readClientsToken = getClientCredentialsOAuthAccessToken(getMockMvc(), client.getClientId(), client.getClientSecret(), null, null); //this is the token from another client String otherReadClientsToken = getClientCredentialsOAuthAccessToken(getMockMvc(), otherClient.getClientId(), otherClient.getClientSecret(), null, null); //ensure our token works getMockMvc().perform(get("/oauth/clients").header("Authorization", "Bearer " + readClientsToken)) .andExpect(status().isOk()); //ensure we can't get to the endpoint without authentication getMockMvc().perform(get("/oauth/token/revoke/client/" + client.getClientId())) .andExpect(status().isUnauthorized()); //ensure we can't get to the endpoint without correct scope getMockMvc().perform(get("/oauth/token/revoke/client/" + client.getClientId()).header("Authorization", "Bearer " + otherReadClientsToken)).andExpect(status().isForbidden()); //ensure that we have the correct error for invalid client id getMockMvc().perform(get("/oauth/token/revoke/client/notfound" + generator.generate()) .header("Authorization", "Bearer " + adminToken)).andExpect(status().isNotFound()); //we revoke the tokens for that client getMockMvc().perform(get("/oauth/token/revoke/client/" + client.getClientId()).header("Authorization", "Bearer " + adminToken)).andExpect(status().isOk()); //we should fail attempting to use the token getMockMvc().perform(get("/oauth/clients").header("Authorization", "Bearer " + readClientsToken)) .andExpect(status().isUnauthorized()) .andExpect(content().string(containsString("\"error\":\"invalid_token\""))); ScimUser user = new ScimUser(null, generator.generate(), "Given Name", "Family Name"); user.setPrimaryEmail(user.getUserName() + "@test.org"); user.setPassword("password"); user = createUser(getMockMvc(), adminToken, user); user.setPassword("password"); String userInfoToken = getUserOAuthAccessToken(getMockMvc(), client.getClientId(), client.getClientSecret(), user.getUserName(), user.getPassword(), "openid"); //ensure our token works getMockMvc().perform(get("/userinfo").header("Authorization", "Bearer " + userInfoToken)) .andExpect(status().isOk()); //we revoke the tokens for that user getMockMvc().perform(get("/oauth/token/revoke/user/" + user.getId() + "notfound").header("Authorization", "Bearer " + adminToken)).andExpect(status().isNotFound()); //we revoke the tokens for that user getMockMvc().perform( get("/oauth/token/revoke/user/" + user.getId()).header("Authorization", "Bearer " + adminToken)) .andExpect(status().isOk()); getMockMvc().perform(get("/userinfo").header("Authorization", "Bearer " + userInfoToken)) .andExpect(status().isUnauthorized()) .andExpect(content().string(containsString("\"error\":\"invalid_token\""))); } protected BaseClientDetails getAClientWithClientsRead() throws Exception { BaseClientDetails client = new BaseClientDetails(generator.generate(), "", "openid", "client_credentials,password", "clients.read"); client.setClientSecret("secret"); createClient(getMockMvc(), adminToken, client); return client; } @Test public void testGetClientCredentials_WithAuthoritiesExcluded_ForDefaultIdentityZone() throws Exception { Set<String> originalExclude = getWebApplicationContext().getBean(UaaTokenServices.class) .getExcludedClaims(); try { getWebApplicationContext().getBean(UaaTokenServices.class).setExcludedClaims( new HashSet<>(Arrays.asList(ClaimConstants.AUTHORITIES, ClaimConstants.AZP))); String clientId = "testclient" + generator.generate(); String scopes = "space.*.developer,space.*.admin,org.*.reader,org.123*.admin,*.*,*"; setUpClients(clientId, scopes, scopes, GRANT_TYPES, true); String body = getMockMvc() .perform(post("/oauth/token").accept(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Basic " + new String(Base64.encode((clientId + ":" + SECRET).getBytes()))) .param("grant_type", "client_credentials").param("client_id", clientId) .param("client_secret", SECRET)) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); Map<String, Object> bodyMap = JsonUtils.readValue(body, new TypeReference<Map<String, Object>>() { }); assertNotNull(bodyMap.get("access_token")); Jwt jwt = JwtHelper.decode((String) bodyMap.get("access_token")); Map<String, Object> claims = JsonUtils.readValue(jwt.getClaims(), new TypeReference<Map<String, Object>>() { }); assertNull(claims.get(ClaimConstants.AUTHORITIES)); assertNull(claims.get(ClaimConstants.AZP)); } finally { getWebApplicationContext().getBean(UaaTokenServices.class).setExcludedClaims(originalExclude); } } @Test public void testGetClientCredentialsTokenForOtherIdentityZone() throws Exception { String subdomain = "testzone" + generator.generate(); IdentityZone testZone = setupIdentityZone(subdomain); IdentityZoneHolder.set(testZone); String clientId = "testclient" + generator.generate(); String scopes = "space.*.developer,space.*.admin,org.*.reader,org.123*.admin,*.*,*"; setUpClients(clientId, scopes, scopes, GRANT_TYPES, true); IdentityZoneHolder.clear(); getMockMvc().perform(post("http://" + subdomain + ".localhost/oauth/token") .accept(MediaType.APPLICATION_JSON_VALUE) .with(new SetServerNameRequestPostProcessor(subdomain + ".localhost")) .header("Authorization", "Basic " + new String(Base64.encode((clientId + ":" + SECRET).getBytes()))) .param("grant_type", "client_credentials").param("client_id", clientId) .param("client_secret", SECRET)).andExpect(status().isOk()); } @Test public void testGetClientCredentialsTokenForOtherIdentityZoneFromDefaultZoneFails() throws Exception { String subdomain = "testzone" + generator.generate(); IdentityZone testZone = setupIdentityZone(subdomain); IdentityZoneHolder.set(testZone); String clientId = "testclient" + generator.generate(); String scopes = "space.*.developer,space.*.admin,org.*.reader,org.123*.admin,*.*,*"; setUpClients(clientId, scopes, scopes, GRANT_TYPES, true); IdentityZoneHolder.clear(); getMockMvc().perform(post("http://localhost/oauth/token").accept(MediaType.APPLICATION_JSON_VALUE) //.header("Host", subdomain + ".localhost") - with updated Spring, this now works for request.getServerName .header("Authorization", "Basic " + new String(Base64.encode((clientId + ":" + SECRET).getBytes()))) .param("grant_type", "client_credentials").param("client_id", clientId) .param("client_secret", SECRET)).andExpect(status().isUnauthorized()); } @Test public void testGetClientCredentialsTokenForDefaultIdentityZoneFromOtherZoneFails() throws Exception { String clientId = "testclient" + generator.generate(); String scopes = "space.*.developer,space.*.admin,org.*.reader,org.123*.admin,*.*,*"; setUpClients(clientId, scopes, scopes, GRANT_TYPES, true); String subdomain = "testzone" + generator.generate(); setupIdentityZone(subdomain); getMockMvc().perform(post("http://" + subdomain + ".localhost/oauth/token") .accept(MediaType.APPLICATION_JSON_VALUE) .with(new SetServerNameRequestPostProcessor(subdomain + ".localhost")) .header("Authorization", "Basic " + new String(Base64.encode((clientId + ":" + SECRET).getBytes()))) .param("grant_type", "client_credentials").param("client_id", clientId) .param("client_secret", SECRET)).andExpect(status().isUnauthorized()); } @Test public void testGetPasswordGrantInvalidPassword() throws Exception { String username = generator.generate() + "@test.org"; IdentityZoneHolder.clear(); String clientId = "testclient" + generator.generate(); String scopes = "cloud_controller.read"; setUpClients(clientId, scopes, scopes, "password,client_credentials", true, TEST_REDIRECT_URI, Arrays.asList(OriginKeys.UAA)); setUpUser(username); IdentityZoneHolder.clear(); getMockMvc() .perform(post("/oauth/token").param("username", username).param("password", "badsecret") .header("Authorization", "Basic " + new String(Base64.encode((clientId + ":" + SECRET).getBytes()))) .param(OAuth2Utils.RESPONSE_TYPE, "token").param(OAuth2Utils.GRANT_TYPE, "password") .param(OAuth2Utils.CLIENT_ID, clientId)) .andExpect(status().isUnauthorized()).andExpect( content().string("{\"error\":\"unauthorized\",\"error_description\":\"Bad credentials\"}")); } @Test public void testGetPasswordGrantTokenExpiredPasswordForOtherZone() throws Exception { String username = generator.generate() + "@test.org"; String subdomain = "testzone" + generator.generate(); IdentityZone testZone = setupIdentityZone(subdomain); IdentityZoneHolder.set(testZone); IdentityProvider<UaaIdentityProviderDefinition> provider = setupIdentityProvider(); UaaIdentityProviderDefinition config = provider.getConfig(); if (config == null) { config = new UaaIdentityProviderDefinition(null, null); } PasswordPolicy passwordPolicy = new PasswordPolicy(6, 128, 1, 1, 1, 0, 6); config.setPasswordPolicy(passwordPolicy); provider.setConfig(config); identityProviderProvisioning.update(provider); String clientId = "testclient" + generator.generate(); String scopes = "cloud_controller.read"; setUpClients(clientId, scopes, scopes, "password,client_credentials", true, TEST_REDIRECT_URI, Arrays.asList(provider.getOriginKey())); setUpUser(username); IdentityZoneHolder.clear(); getMockMvc().perform(post("/oauth/token") .with(new SetServerNameRequestPostProcessor(subdomain + ".localhost")).param("username", username) .param("password", "secret") .header("Authorization", "Basic " + new String(Base64.encode((clientId + ":" + SECRET).getBytes()))) .param(OAuth2Utils.RESPONSE_TYPE, "token").param(OAuth2Utils.GRANT_TYPE, "password") .param(OAuth2Utils.CLIENT_ID, clientId)).andExpect(status().isOk()); Calendar cal = Calendar.getInstance(); cal.setTimeInMillis(System.currentTimeMillis()); cal.add(Calendar.YEAR, -1); Timestamp t = new Timestamp(cal.getTimeInMillis()); assertEquals(1, getWebApplicationContext().getBean(JdbcTemplate.class) .update("UPDATE users SET passwd_lastmodified = ? WHERE username = ?", t, username)); getMockMvc() .perform(post("/oauth/token").with(new SetServerNameRequestPostProcessor(subdomain + ".localhost")) .param("username", username).param("password", "secret") .header("Authorization", "Basic " + new String(Base64.encode((clientId + ":" + SECRET).getBytes()))) .param(OAuth2Utils.RESPONSE_TYPE, "token").param(OAuth2Utils.GRANT_TYPE, "password") .param(OAuth2Utils.CLIENT_ID, clientId)) .andExpect(status().isForbidden()).andExpect(content().string( "{\"error\":\"access_denied\",\"error_description\":\"Your current password has expired. Please reset your password.\"}")); } @Test public void testGetPasswordGrantTokenForOtherZone() throws Exception { String username = generator.generate() + "@test.org"; String subdomain = "testzone" + generator.generate(); String clientId = "testclient" + generator.generate(); createNonDefaultZone(username, subdomain, clientId); MvcResult result = getMockMvc().perform(post("/oauth/token") .with(new SetServerNameRequestPostProcessor(subdomain + ".localhost")).param("username", username) .param("password", "secret") .header("Authorization", "Basic " + new String(Base64.encode((clientId + ":" + SECRET).getBytes()))) .param(OAuth2Utils.RESPONSE_TYPE, "token").param(OAuth2Utils.GRANT_TYPE, "password") .param(OAuth2Utils.CLIENT_ID, clientId)).andExpect(status().isOk()).andReturn(); String claimsJSON = JwtHelper.decode( JsonUtils.readValue(result.getResponse().getContentAsString(), OAuthToken.class).accessToken) .getClaims(); Claims claims = JsonUtils.readValue(claimsJSON, Claims.class); assertEquals(claims.getIss(), "http://" + subdomain.toLowerCase() + ".localhost:8080/uaa/oauth/token"); } @Test public void testGetPasswordGrantForDefaultIdentityZoneFromOtherZoneFails() throws Exception { String username = generator.generate() + "@test.org"; String clientId = "testclient" + generator.generate(); String scopes = "cloud_controller.read"; setUpClients(clientId, scopes, scopes, "password,client_credentials", true); setUpUser(username); String subdomain = "testzone" + generator.generate(); IdentityZone testZone = setupIdentityZone(subdomain); IdentityZoneHolder.set(testZone); setupIdentityProvider(); IdentityZoneHolder.clear(); getMockMvc().perform(post("/oauth/token") .with(new SetServerNameRequestPostProcessor(subdomain + ".localhost")).param("username", username) .param("password", "secret") .header("Authorization", "Basic " + new String(Base64.encode((clientId + ":" + SECRET).getBytes()))) .param(OAuth2Utils.RESPONSE_TYPE, "token").param(OAuth2Utils.GRANT_TYPE, "password") .param(OAuth2Utils.CLIENT_ID, clientId)).andExpect(status().isUnauthorized()); } @Test public void testGetPasswordGrantForOtherIdentityZoneFromDefaultZoneFails() throws Exception { String username = generator.generate() + "@test.org"; String subdomain = "testzone" + generator.generate(); IdentityZone testZone = setupIdentityZone(subdomain); IdentityZoneHolder.set(testZone); setupIdentityProvider(); String clientId = "testclient" + generator.generate(); String scopes = "cloud_controller.read"; setUpClients(clientId, scopes, scopes, "password,client_credentials", true); setUpUser(username); IdentityZoneHolder.clear(); getMockMvc().perform(post("/oauth/token").param("username", username).param("password", "secret") .header("Authorization", "Basic " + new String(Base64.encode((clientId + ":" + SECRET).getBytes()))) .param(OAuth2Utils.RESPONSE_TYPE, "token").param(OAuth2Utils.GRANT_TYPE, "password") .param(OAuth2Utils.CLIENT_ID, clientId)).andExpect(status().isUnauthorized()); } @Test public void testGetTokenScopesNotInAuthentication() throws Exception { String basicDigestHeaderValue = "Basic " + new String( org.apache.commons.codec.binary.Base64.encodeBase64(("identity:identitysecret").getBytes())); ScimUser user = setUpUser(generator.generate() + "@test.org"); String zoneadmingroup = "zones." + generator.generate() + ".admin"; ScimGroup group = new ScimGroup(null, zoneadmingroup, IdentityZone.getUaa().getId()); group = groupProvisioning.create(group); ScimGroupMember member = new ScimGroupMember(user.getId()); groupMembershipManager.addMember(group.getId(), member); MockHttpSession session = getAuthenticatedSession(user); String state = generator.generate(); MockHttpServletRequestBuilder authRequest = get("/oauth/authorize") .header("Authorization", basicDigestHeaderValue).header("Accept", MediaType.APPLICATION_JSON_VALUE) .session(session).param(OAuth2Utils.GRANT_TYPE, "authorization_code") .param(OAuth2Utils.RESPONSE_TYPE, "code").param(OAuth2Utils.STATE, state) .param(OAuth2Utils.CLIENT_ID, "identity").param(OAuth2Utils.REDIRECT_URI, "http://localhost/test"); MvcResult result = getMockMvc().perform(authRequest).andExpect(status().is3xxRedirection()).andReturn(); String location = result.getResponse().getHeader("Location"); UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(location); String code = builder.build().getQueryParams().get("code").get(0); authRequest = post("/oauth/token").header("Authorization", basicDigestHeaderValue) .header("Accept", MediaType.APPLICATION_JSON_VALUE) .param(OAuth2Utils.GRANT_TYPE, "authorization_code").param(OAuth2Utils.RESPONSE_TYPE, "token") .param("code", code).param(OAuth2Utils.CLIENT_ID, "identity") .param(OAuth2Utils.REDIRECT_URI, "http://localhost/test"); result = getMockMvc().perform(authRequest).andExpect(status().is2xxSuccessful()).andReturn(); OAuthToken oauthToken = JsonUtils.readValue(result.getResponse().getContentAsString(), OAuthToken.class); OAuth2Authentication a1 = tokenServices.loadAuthentication(oauthToken.accessToken); assertEquals(4, a1.getOAuth2Request().getScope().size()); assertThat(a1.getOAuth2Request().getScope(), containsInAnyOrder( new String[] { zoneadmingroup, "openid", "cloud_controller.read", "cloud_controller.write" })); } @Test public void testRevocablePasswordGrantTokenForDefaultZone() throws Exception { String tokenKey = "access_token"; Map<String, Object> tokenResponse = testRevocablePasswordGrantTokenForDefaultZone(new HashedMap()); assertNotNull("Token must be present", tokenResponse.get(tokenKey)); assertTrue("Token must be a string", tokenResponse.get(tokenKey) instanceof String); String token = (String) tokenResponse.get(tokenKey); Jwt jwt = JwtHelper.decode(token); Map<String, Object> claims = JsonUtils.readValue(jwt.getClaims(), new TypeReference<Map<String, Object>>() { }); assertNotNull("Token revocation signature must exist", claims.get(ClaimConstants.REVOCATION_SIGNATURE)); assertTrue("Token revocation signature must be a string", claims.get(ClaimConstants.REVOCATION_SIGNATURE) instanceof String); assertTrue("Token revocation signature must have data", StringUtils.hasText((String) claims.get(ClaimConstants.REVOCATION_SIGNATURE))); } @Test public void testPasswordGrantTokenForDefaultZone_Opaque() throws Exception { Map<String, String> parameters = new HashedMap(); parameters.put(REQUEST_TOKEN_FORMAT, OPAQUE); String tokenKey = "access_token"; Map<String, Object> tokenResponse = testRevocablePasswordGrantTokenForDefaultZone(parameters); assertNotNull("Token must be present", tokenResponse.get(tokenKey)); assertTrue("Token must be a string", tokenResponse.get(tokenKey) instanceof String); String token = (String) tokenResponse.get(tokenKey); assertThat("Token must be shorter than 37 characters", token.length(), lessThanOrEqualTo(36)); RevocableToken revocableToken = getWebApplicationContext().getBean(RevocableTokenProvisioning.class) .retrieve(token); assertNotNull("Token should have been stored in the DB", revocableToken); Jwt jwt = JwtHelper.decode(revocableToken.getValue()); Map<String, Object> claims = JsonUtils.readValue(jwt.getClaims(), new TypeReference<Map<String, Object>>() { }); assertNotNull("Revocable claim must exist", claims.get(ClaimConstants.REVOCABLE)); assertTrue("Token revocable claim must be set to true", (Boolean) claims.get(ClaimConstants.REVOCABLE)); } @Test public void testNonDefaultZone_Jwt_Revocable() throws Exception { String username = generator.generate() + "@test.org"; String subdomain = "testzone" + generator.generate(); String clientId = "testclient" + generator.generate(); createNonDefaultZone(username, subdomain, clientId); IdentityZoneProvisioning zoneProvisioning = getWebApplicationContext() .getBean(IdentityZoneProvisioning.class); IdentityZone defaultZone = zoneProvisioning.retrieveBySubdomain(subdomain); try { defaultZone.getConfig().getTokenPolicy().setJwtRevocable(true); zoneProvisioning.update(defaultZone); MockHttpServletRequestBuilder post = post("/oauth/token") .header("Authorization", "Basic " + new String(Base64.encode((clientId + ":" + SECRET).getBytes()))) .header("Host", subdomain + ".localhost").param("username", username) .param("password", "secret").param(OAuth2Utils.RESPONSE_TYPE, "token") .param(OAuth2Utils.GRANT_TYPE, "password").param(OAuth2Utils.CLIENT_ID, clientId); Map<String, Object> tokenResponse = JsonUtils.readValue(getMockMvc().perform(post).andDo(print()) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(), new TypeReference<Map<String, Object>>() { }); validateRevocableJwtToken(tokenResponse, defaultZone); } finally { defaultZone.getConfig().getTokenPolicy().setJwtRevocable(false); zoneProvisioning.update(defaultZone); } } protected void createNonDefaultZone(String username, String subdomain, String clientId) { IdentityZone testZone = setupIdentityZone(subdomain); IdentityZoneHolder.set(testZone); IdentityProvider provider = setupIdentityProvider(); String scopes = "cloud_controller.read"; setUpClients(clientId, scopes, scopes, "password,client_credentials", true, TEST_REDIRECT_URI, Arrays.asList(provider.getOriginKey())); setUpUser(username); IdentityZoneHolder.clear(); } @Test public void testDefaultZone_Jwt_Revocable() throws Exception { IdentityZoneProvisioning zoneProvisioning = getWebApplicationContext() .getBean(IdentityZoneProvisioning.class); IdentityZone defaultZone = zoneProvisioning.retrieve(IdentityZone.getUaa().getId()); try { defaultZone.getConfig().getTokenPolicy().setJwtRevocable(true); zoneProvisioning.update(defaultZone); Map<String, String> parameters = new HashedMap(); Map<String, Object> tokenResponse = testRevocablePasswordGrantTokenForDefaultZone(parameters); validateRevocableJwtToken(tokenResponse, defaultZone); } finally { defaultZone.getConfig().getTokenPolicy().setJwtRevocable(false); zoneProvisioning.update(defaultZone); } } protected void validateRevocableJwtToken(Map<String, Object> tokenResponse, IdentityZone zone) throws Exception { String tokenKey = "access_token"; assertNotNull("Token must be present", tokenResponse.get(tokenKey)); assertTrue("Token must be a string", tokenResponse.get(tokenKey) instanceof String); String token = (String) tokenResponse.get(tokenKey); assertThat("Token must be longer than 36 characters", token.length(), greaterThan(36)); Jwt jwt = JwtHelper.decode(token); Map<String, Object> claims = JsonUtils.readValue(jwt.getClaims(), new TypeReference<Map<String, Object>>() { }); assertNotNull("JTI Claim should be present", claims.get(JTI)); String tokenId = (String) claims.get(JTI); IdentityZoneHolder.set(zone); RevocableToken revocableToken = getWebApplicationContext().getBean(RevocableTokenProvisioning.class) .retrieve(tokenId); IdentityZoneHolder.clear(); assertNotNull("Token should have been stored in the DB", revocableToken); jwt = JwtHelper.decode(revocableToken.getValue()); claims = JsonUtils.readValue(jwt.getClaims(), new TypeReference<Map<String, Object>>() { }); assertNotNull("Revocable claim must exist", claims.get(ClaimConstants.REVOCABLE)); assertTrue("Token revocable claim must be set to true", (Boolean) claims.get(ClaimConstants.REVOCABLE)); assertEquals(token, revocableToken.getValue()); } @Test @Ignore(value = "We no longer support revocable=true parameter on the /oauth/token endpoint") public void testPasswordGrantTokenForDefaultZone_Revocable() throws Exception { Map<String, String> parameters = new HashedMap(); parameters.put("revocable", "true"); String tokenKey = "access_token"; Map<String, Object> tokenResponse = testRevocablePasswordGrantTokenForDefaultZone(parameters); assertNotNull("Token must be present", tokenResponse.get(tokenKey)); assertTrue("Token must be a string", tokenResponse.get(tokenKey) instanceof String); String token = (String) tokenResponse.get(tokenKey); assertThat("Token must be longer than 36 characters", token.length(), greaterThan(36)); Jwt jwt = JwtHelper.decode(token); Map<String, Object> claims = JsonUtils.readValue(jwt.getClaims(), new TypeReference<Map<String, Object>>() { }); assertNotNull("Revocable claim must exist", claims.get(ClaimConstants.REVOCABLE)); assertTrue("Token revocable claim must be set to true", (Boolean) claims.get(ClaimConstants.REVOCABLE)); RevocableToken revocableToken = getWebApplicationContext().getBean(RevocableTokenProvisioning.class) .retrieve((String) claims.get(JTI)); assertNotNull("Token should have been stored in the DB", revocableToken); } public Map<String, Object> testRevocablePasswordGrantTokenForDefaultZone(Map<String, String> parameters) throws Exception { String username = generator.generate() + "@test.org"; String clientId = "testclient" + generator.generate(); String scopes = "cloud_controller.read"; setUpClients(clientId, scopes, scopes, "password,client_credentials", true, TEST_REDIRECT_URI, Arrays.asList(OriginKeys.UAA)); setUpUser(username); MockHttpServletRequestBuilder post = post("/oauth/token") .header("Authorization", "Basic " + new String(Base64.encode((clientId + ":" + SECRET).getBytes()))) .param("username", username).param("password", "secret").param(OAuth2Utils.RESPONSE_TYPE, "token") .param(OAuth2Utils.GRANT_TYPE, "password").param(OAuth2Utils.CLIENT_ID, clientId); for (Map.Entry<String, String> entry : parameters.entrySet()) { post.param(entry.getKey(), entry.getValue()); } return JsonUtils.readValue(getMockMvc().perform(post).andDo(print()).andExpect(status().isOk()).andReturn() .getResponse().getContentAsString(), new TypeReference<Map<String, Object>>() { }); } private ScimUser setUpUser(String username) { ScimUser scimUser = new ScimUser(); scimUser.setUserName(username); ScimUser.Email email = new ScimUser.Email(); email.setValue(username); scimUser.setEmails(Arrays.asList(email)); return jdbcScimUserProvisioning.createUser(scimUser, "secret"); } public static class MockSecurityContext implements SecurityContext { private static final long serialVersionUID = -1386535243513362694L; private Authentication authentication; public MockSecurityContext(Authentication authentication) { this.authentication = authentication; } @Override public Authentication getAuthentication() { return this.authentication; } @Override public void setAuthentication(Authentication authentication) { this.authentication = authentication; } } }