diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneSwitchingFilter.java b/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneSwitchingFilter.java index 3a0b1a8f814..fb13fd0e01c 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneSwitchingFilter.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneSwitchingFilter.java @@ -43,7 +43,7 @@ public IdentityZoneSwitchingFilter(IdentityZoneProvisioning dao) { private final IdentityZoneProvisioning dao; public static final String HEADER = "X-Identity-Zone-Id"; - + public static final String SUBDOMAIN_HEADER = "X-Identity-Zone-Subdomain"; public static final String ZONE_ID_MATCH = "{zone_id}"; public static final String ZONES_ZONE_ID_PREFIX = "zones." ; public static final String ZONES_ZONE_ID_ADMIN = ZONES_ZONE_ID_PREFIX + ZONE_ID_MATCH + "."+ "admin"; @@ -148,35 +148,51 @@ protected String[] getZoneSwitchingScopes(String identityZoneId) { protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - String identityZoneId = request.getHeader(HEADER); - if (StringUtils.hasText(identityZoneId)) { - if (!isAuthorizedToSwitchToIdentityZone(identityZoneId)) { - response.sendError(HttpServletResponse.SC_FORBIDDEN, "User is not authorized to switch to IdentityZone with id "+identityZoneId); - return; - } - IdentityZone originalIdentityZone = IdentityZoneHolder.get(); - try { - - IdentityZone identityZone = null; - try { - identityZone = dao.retrieve(identityZoneId); - } catch (ZoneDoesNotExistsException ex) { - } catch (EmptyResultDataAccessException ex) { - } catch (Exception ex) { - throw ex; - } - if (identityZone == null) { - response.sendError(HttpServletResponse.SC_NOT_FOUND, "Identity zone with id "+identityZoneId+" does not exist"); - return; - } - stripScopesFromAuthentication(identityZoneId, request); - IdentityZoneHolder.set(identityZone); - filterChain.doFilter(request, response); - } finally { - IdentityZoneHolder.set(originalIdentityZone); - } - } else { + + String identityZoneIdFromHeader = request.getHeader(HEADER); + String identityZoneSubDomain = request.getHeader(SUBDOMAIN_HEADER); + + if (StringUtils.isEmpty(identityZoneIdFromHeader) && StringUtils.isEmpty(identityZoneSubDomain)) { filterChain.doFilter(request, response); + return; } + + IdentityZone identityZone = validateIdentityZone(identityZoneIdFromHeader, identityZoneSubDomain); + if (identityZone == null) { + response.sendError(HttpServletResponse.SC_NOT_FOUND, "Identity zone with id/subdomain " + identityZoneIdFromHeader + "/" + identityZoneSubDomain + " does not exist"); + return; + } + + String identityZoneId = identityZone.getId(); + if (!isAuthorizedToSwitchToIdentityZone(identityZoneId)) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "User is not authorized to switch to IdentityZone with id "+identityZoneId); + return; + } + + IdentityZone originalIdentityZone = IdentityZoneHolder.get(); + try { + stripScopesFromAuthentication(identityZoneId, request); + IdentityZoneHolder.set(identityZone); + filterChain.doFilter(request, response); + } finally { + IdentityZoneHolder.set(originalIdentityZone); + } + } + + private IdentityZone validateIdentityZone(String identityZoneId, String identityZoneSubDomain) throws IOException { + IdentityZone identityZone = null; + + try { + if (StringUtils.isEmpty(identityZoneId)) { + identityZone = dao.retrieveBySubdomain(identityZoneSubDomain); + } else { + identityZone = dao.retrieve(identityZoneId); + } + } catch (ZoneDoesNotExistsException | EmptyResultDataAccessException ex) { + } catch (Exception ex) { + throw ex; + } + return identityZone; } + } diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneSwitchingFilterMockMvcTest.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneSwitchingFilterMockMvcTest.java index ffbe97eac35..dbf65db442a 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneSwitchingFilterMockMvcTest.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneSwitchingFilterMockMvcTest.java @@ -26,12 +26,15 @@ import org.cloudfoundry.identity.uaa.zone.IdentityZoneSwitchingFilter; import org.junit.Before; import org.junit.Test; +import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.client.BaseClientDetails; import org.springframework.test.web.servlet.ResultMatcher; import java.util.Arrays; import java.util.UUID; +import static org.cloudfoundry.identity.uaa.zone.IdentityZoneSwitchingFilter.HEADER; +import static org.cloudfoundry.identity.uaa.zone.IdentityZoneSwitchingFilter.SUBDOMAIN_HEADER; import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; @@ -60,44 +63,81 @@ public void setUp() throws Exception { @Test public void testSwitchingZones() throws Exception { - final String zoneId = createZone(identityToken); + IdentityZone identityZone = createZone(identityToken); + String zoneId = identityZone.getId(); String zoneAdminToken = MockMvcUtils.utils().getZoneAdminToken(getMockMvc(),adminToken, zoneId); // Using Identity Client, authenticate in originating Zone // - Create Client using X-Identity-Zone-Id header in new Zone + ClientDetails client = createClientInOtherZone(zoneAdminToken, status().isCreated(), HEADER, zoneId); + + // Authenticate with new Client in new Zone + getMockMvc().perform(get("/oauth/token?grant_type=client_credentials") + .header("Authorization", "Basic " + + new String(Base64.encodeBase64((client.getClientId() + ":" + client.getClientSecret()).getBytes()))) + .with(new SetServerNameRequestPostProcessor(identityZone.getSubdomain() + ".localhost"))) + .andExpect(status().isOk()); + } + + @Test + public void testSwitchingZoneWithSubdomain() throws Exception { + IdentityZone identityZone = createZone(identityToken); + String zoneAdminToken = MockMvcUtils.utils().getZoneAdminToken(getMockMvc(),adminToken, identityZone.getId()); + ClientDetails client = createClientInOtherZone(zoneAdminToken, status().isCreated(), SUBDOMAIN_HEADER, identityZone.getSubdomain()); + + getMockMvc().perform(get("/oauth/token?grant_type=client_credentials") + .header("Authorization", "Basic " + + new String(Base64.encodeBase64((client.getClientId() + ":" + client.getClientSecret()).getBytes()))) + .with(new SetServerNameRequestPostProcessor(identityZone.getSubdomain() + ".localhost"))) + .andExpect(status().isOk()); + + } + + @Test + public void testNoSwitching() throws Exception{ + final String clientId = UUID.randomUUID().toString(); BaseClientDetails client = new BaseClientDetails(clientId, null, null, "client_credentials", null); client.setClientSecret("secret"); + getMockMvc().perform(post("/oauth/clients") - .header(IdentityZoneSwitchingFilter.HEADER, zoneId) - .header("Authorization", "Bearer " + zoneAdminToken) - .accept(APPLICATION_JSON) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(client))) - .andExpect(status().isCreated()); + .header("Authorization", "Bearer " + adminToken) + .accept(APPLICATION_JSON) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(client))) + .andExpect(status().isCreated()); - // Authenticate with new Client in new Zone getMockMvc().perform(get("/oauth/token?grant_type=client_credentials") - .header("Authorization", "Basic " - + new String(Base64.encodeBase64((client.getClientId() + ":" + client.getClientSecret()).getBytes()))) - .with(new SetServerNameRequestPostProcessor(zoneId + ".localhost"))) + .header("Authorization", "Basic " + + new String(Base64.encodeBase64((client.getClientId() + ":" + client.getClientSecret()).getBytes())))) .andExpect(status().isOk()); } + @Test + public void testSwitchingToInvalidSubDomain() throws Exception{ + IdentityZone identityZone = createZone(identityToken); + String zoneAdminToken = MockMvcUtils.utils().getZoneAdminToken(getMockMvc(),adminToken, identityZone.getId()); + + createClientInOtherZone(zoneAdminToken, status().isNotFound(), SUBDOMAIN_HEADER, "InvalidSubDomain"); + } + @Test public void testSwitchingToNonExistentZone() throws Exception { - createClientInOtherZone(identityToken, "i-do-not-exist", status().isForbidden()); + IdentityZone identityZone = createZone(identityToken); + String zoneAdminToken = MockMvcUtils.utils().getZoneAdminToken(getMockMvc(),adminToken, identityZone.getId()); + + createClientInOtherZone(zoneAdminToken, status().isNotFound(), HEADER, "i-do-not-exist"); } @Test public void testSwitchingZonesWithoutAuthority() throws Exception { String identityTokenWithoutZonesAdmin = testClient.getClientCredentialsOAuthAccessToken("identity","identitysecret","zones.write,scim.zones"); - final String zoneId = createZone(identityTokenWithoutZonesAdmin); - createClientInOtherZone(identityTokenWithoutZonesAdmin, zoneId, status().isForbidden()); + final String zoneId = createZone(identityTokenWithoutZonesAdmin).getId(); + createClientInOtherZone(identityTokenWithoutZonesAdmin, status().isForbidden(), HEADER, zoneId); } @Test public void testSwitchingZonesWithAUser() throws Exception { - final String zoneId = createZone(identityToken); + final String zoneId = createZone(identityToken).getId(); String adminToken = testClient.getClientCredentialsOAuthAccessToken("admin","adminsecret","scim.write"); // Create a User String username = RandomStringUtils.randomAlphabetic(8) + "@example.com"; @@ -112,23 +152,24 @@ public void testSwitchingZonesWithAUser() throws Exception { group.setMembers(Arrays.asList(new ScimGroupMember(createdUser.getId()))); MockMvcUtils.utils().createGroup(getMockMvc(), adminToken, group); String userToken = MockMvcUtils.utils().getUserOAuthAccessTokenAuthCode(getMockMvc(),"identity", "identitysecret", createdUser.getId(),createdUser.getUserName(), "secret", null); - createClientInOtherZone(userToken, zoneId, status().isCreated()); + createClientInOtherZone(userToken, status().isCreated(), HEADER, zoneId); } - private String createZone(String accessToken) throws Exception { - return MockMvcUtils.utils().createZoneUsingWebRequest(getMockMvc(), accessToken).getId(); + private IdentityZone createZone(String accessToken) throws Exception { + return MockMvcUtils.utils().createZoneUsingWebRequest(getMockMvc(), accessToken); } - private void createClientInOtherZone(String accessToken, String zoneId, ResultMatcher statusMatcher) throws Exception { - final String clientId = UUID.randomUUID().toString(); + private ClientDetails createClientInOtherZone(String accessToken, ResultMatcher statusMatcher, String headerKey, String headerValue) throws Exception { + String clientId = UUID.randomUUID().toString(); BaseClientDetails client = new BaseClientDetails(clientId, null, null, "client_credentials", null); client.setClientSecret("secret"); getMockMvc().perform(post("/oauth/clients") - .header(IdentityZoneSwitchingFilter.HEADER, zoneId) + .header(headerKey, headerValue) .header("Authorization", "Bearer " + accessToken) .accept(APPLICATION_JSON) .contentType(APPLICATION_JSON) .content(JsonUtils.writeValueAsString(client))) .andExpect(statusMatcher); + return client; } }