Skip to content

Commit

Permalink
Merge branch 'releases/2.2.6'
Browse files Browse the repository at this point in the history
  • Loading branch information
Chris Dutra committed May 1, 2015
2 parents 37c5b13 + 868833d commit f7ce199
Show file tree
Hide file tree
Showing 22 changed files with 246 additions and 105 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@ Here are some ways for you to get involved in the community:
vote on the ones that you are interested in.
* Github is for social coding: if you want to write code, we encourage
contributions through pull requests from
[forks of this repository](http://help.github.com/forking/). If you
[forks of this repository](https://github.com/cloudfoundry/uaa). If you
want to contribute code this way, please reference an existing issue
if there is one as well covering the specific issue you are
addressing. Always submit pull requests to the "develop" branch.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,10 @@ public class UaaConfiguration {
@Pattern(regexp = "(default|postgresql|hsqldb|mysql|oracle)")
public String platform;
public String spring_profiles;
public String internalHostnames;

@Valid
public Zones zones;

@URL(message = "issuer.uri must be a valid URL")
public String issuerUri;
public boolean dump_requests;
Expand Down Expand Up @@ -92,6 +95,15 @@ public class UaaConfiguration {
@Valid
public OAuth multitenant;

public static class Zones {
@Valid
public InternalZone internal;

public static class InternalZone {
public Set<String> hostnames;
}
}

public static class CloudController {
@Valid
public Database database;
Expand All @@ -105,6 +117,14 @@ public static class Database {
public String username;
@NotNull(message = "Database password is required")
public String password;

public int maxactive;
public int maxidle;
public boolean removeabandoned;
public boolean logabandoned;
public int abandonedtimeout;
public long evictionintervalms;

}

public static class Logging {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,12 @@ public IdentityProviderDefinition(String metaDataLocation, String idpEntityAlias

@JsonIgnore
public MetadataLocation getType() {
if (metaDataLocation.trim().startsWith("<?xml")) {
String trimmedLocation = metaDataLocation.trim();
if (trimmedLocation.startsWith("<?xml") ||
trimmedLocation.startsWith("<md:EntityDescriptor") ||
trimmedLocation.startsWith("<EntityDescriptor")) {
return MetadataLocation.DATA;
} else if (metaDataLocation.startsWith("http")) {
} else if (trimmedLocation.startsWith("http")) {
return MetadataLocation.URL;
} else {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
*******************************************************************************/
package org.cloudfoundry.identity.uaa.zone;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.web.filter.OncePerRequestFilter;

Expand All @@ -22,23 +21,19 @@
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

/**
* This filter ensures that all requests are targeting a specific identity zone
* by hostname. If the hostname doesn't match an identity zone, a 404 error is
* sent.
*
* @author wtran@pivotal.io
* @author rszumlakowski@pivotal.io
*
*/
public class IdentityZoneResolvingFilter extends OncePerRequestFilter {

private IdentityZoneProvisioning dao;
private Set<String> internalHostnames = new HashSet<>();
private Set<String> defaultZoneHostnames = new HashSet<>();

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
Expand Down Expand Up @@ -67,10 +62,10 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
}

private String getSubdomain(String hostname) {
if (internalHostnames.contains(hostname)) {
if (defaultZoneHostnames.contains(hostname)) {
return "";
}
for (String internalHostname : internalHostnames) {
for (String internalHostname : defaultZoneHostnames) {
if (hostname.endsWith("." + internalHostname)) {
return hostname.substring(0, hostname.length() - internalHostname.length() - 1);
}
Expand All @@ -82,12 +77,17 @@ public void setIdentityZoneProvisioning(IdentityZoneProvisioning dao) {
this.dao = dao;
}

@Value("${internalHostnames:localhost}")
public void setInternalHostnames(String hostnames) {
this.internalHostnames.addAll(Arrays.asList(hostnames.split("[ ,]+")));
public void setAdditionalInternalHostnames(Set<String> hostnames) {
if (hostnames!=null) {
this.defaultZoneHostnames.addAll(hostnames);
}
}

public void setDefaultInternalHostnames(Set<String> hostnames) {
this.defaultZoneHostnames.addAll(hostnames);
}

public void setInternalHostnames(Set<String> hostnames) {
this.internalHostnames.addAll(Collections.unmodifiableSet(hostnames));
public Set<String> getDefaultZoneHostnames() {
return defaultZoneHostnames;
}
}
1 change: 1 addition & 0 deletions common/src/main/resources/spring/data-source.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
<property name="validationInterval" value="5000" />
<property name="validationQuery" value="#{@validationQuery}" />
<property name="testOnBorrow" value="true" />
<property name="minIdle" value="0"/>
<property name="maxActive" value="${database.maxactive:100}"/>
<property name="maxIdle" value="${database.maxidle:10}"/>
<property name="removeAbandoned" value="${database.removeabandoned:false}"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,24 @@ public class IdentityProviderConfiguratorTests {
"vvphhSERhqk/Nv76Vkl8uvJwwHbQrR9KJx4L3PRkGCG24rix71jEuXVGZUsDNM3CUKnARx4MEab6\n" +
"GFHNkZ6DmoT/PFagngecHu+EwmuDtaG0rEkFrARwe+d8Ru0BN558abFb</ds:X509Certificate></ds:X509Data></ds:KeyInfo></md:KeyDescriptor><md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat><md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat><md:SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" Location=\"https://pivotal.oktapreview.com/app/pivotal_pivotalcfstaging_1/k2lw4l5bPODCMIIDBRYZ/sso/saml\"/><md:SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\" Location=\"https://pivotal.oktapreview.com/app/pivotal_pivotalcfstaging_1/k2lw4l5bPODCMIIDBRYZ/sso/saml\"/></md:IDPSSODescriptor></md:EntityDescriptor>\n";

private static String xmlWithoutHeader =
" <md:EntityDescriptor xmlns:md=\"urn:oasis:names:tc:SAML:2.0:metadata\" entityID=\"http://www.okta.com/k2lw4l5bPODCMIIDBRYZ\"><md:IDPSSODescriptor WantAuthnRequestsSigned=\"true\" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\"><md:KeyDescriptor use=\"signing\"><ds:KeyInfo xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\"><ds:X509Data><ds:X509Certificate>MIICmTCCAgKgAwIBAgIGAUPATqmEMA0GCSqGSIb3DQEBBQUAMIGPMQswCQYDVQQGEwJVUzETMBEG\n" +
"A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU\n" +
"MBIGA1UECwwLU1NPUHJvdmlkZXIxEDAOBgNVBAMMB1Bpdm90YWwxHDAaBgkqhkiG9w0BCQEWDWlu\n" +
"Zm9Ab2t0YS5jb20wHhcNMTQwMTIzMTgxMjM3WhcNNDQwMTIzMTgxMzM3WjCBjzELMAkGA1UEBhMC\n" +
"VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNVBAoM\n" +
"BE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRAwDgYDVQQDDAdQaXZvdGFsMRwwGgYJKoZIhvcN\n" +
"AQkBFg1pbmZvQG9rdGEuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCeil67/TLOiTZU\n" +
"WWgW2XEGgFZ94bVO90v5J1XmcHMwL8v5Z/8qjdZLpGdwI7Ph0CyXMMNklpaR/Ljb8fsls3amdT5O\n" +
"Bw92Zo8ulcpjw2wuezTwL0eC0wY/GQDAZiXL59npE6U+fH1lbJIq92hx0HJSru/0O1q3+A/+jjZL\n" +
"3tL/SwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAI5BoWZoH6Mz9vhypZPOJCEKa/K+biZQsA4Zqsuk\n" +
"vvphhSERhqk/Nv76Vkl8uvJwwHbQrR9KJx4L3PRkGCG24rix71jEuXVGZUsDNM3CUKnARx4MEab6\n" +
"GFHNkZ6DmoT/PFagngecHu+EwmuDtaG0rEkFrARwe+d8Ru0BN558abFb</ds:X509Certificate></ds:X509Data></ds:KeyInfo></md:KeyDescriptor><md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat><md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat><md:SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" Location=\"https://pivotal.oktapreview.com/app/pivotal_pivotalcfstaging_1/k2lw4l5bPODCMIIDBRYZ/sso/saml\"/><md:SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\" Location=\"https://pivotal.oktapreview.com/app/pivotal_pivotalcfstaging_1/k2lw4l5bPODCMIIDBRYZ/sso/saml\"/></md:IDPSSODescriptor></md:EntityDescriptor>\n";

IdentityProviderConfigurator conf = null;
Map<String, Map<String, Object>> data = null;
IdentityProviderDefinition singleAdd = null;
IdentityProviderDefinition singleAddWithoutHeader = null;
private static final String singleAddAlias = "sample-alias";

String sampleYaml = " providers:\n" +
Expand Down Expand Up @@ -115,6 +129,7 @@ public void setUp() throws Exception {
conf = new IdentityProviderConfigurator();
parseYaml(sampleYaml);
singleAdd = new IdentityProviderDefinition(xml, singleAddAlias,"sample-nameID",1,true,true,"sample-link-test","sample-icon-url","uaa");
singleAddWithoutHeader = new IdentityProviderDefinition(xmlWithoutHeader, singleAddAlias,"sample-nameID",1,true,true,"sample-link-test","sample-icon-url","uaa");
}

private void parseYaml(String sampleYaml) {
Expand Down Expand Up @@ -146,6 +161,14 @@ public void testSingleAddProviderDefinition() throws Exception {
testGetIdentityProviderDefinitions(6, false);
}

@Test
public void testSingleAddProviderWithoutXMLHeader() throws Exception {
conf.setIdentityProviders(data);
conf.afterPropertiesSet();
conf.addIdentityProviderDefinition(singleAddWithoutHeader);
testGetIdentityProviderDefinitions(6, false);
}

@Test(expected = NullPointerException.class)
public void testAddNullProvider() {
conf.addIdentityProviderDefinition(null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
Expand All @@ -19,45 +18,44 @@
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.util.StringUtils;

public class IdentityZoneResolvingFilterTest {

private boolean wasFilterExecuted = false;

@Test
public void holderIsSetWithDefaultIdentityZone() {
IdentityZoneHolder.clear();
assertEquals(IdentityZone.getUaa(), IdentityZoneHolder.get());
}

@Test
public void holderIsSetWithMatchingIdentityZone() throws Exception {
assertFindsCorrectSubdomain("myzone", "myzone.uaa.mycf.com", "uaa.mycf.com,login.mycf.com");
assertFindsCorrectSubdomain("myzone", "myzone.uaa.mycf.com", "uaa.mycf.com","login.mycf.com");
}

@Test
public void holderIsSetWithMatchingIdentityZoneWhenSubdomainContainsUaaHostname() throws Exception {
assertFindsCorrectSubdomain("foo.uaa.mycf.com","foo.uaa.mycf.com.uaa.mycf.com", "uaa.mycf.com,login.mycf.com");
assertFindsCorrectSubdomain("foo.uaa.mycf.com", "foo.uaa.mycf.com.uaa.mycf.com", "uaa.mycf.com", "login.mycf.com");
}

@Test
public void holderIsSetWithUAAIdentityZone() throws Exception {
assertFindsCorrectSubdomain("", "uaa.mycf.com", "uaa.mycf.com,login.mycf.com");
assertFindsCorrectSubdomain("", "login.mycf.com", "uaa.mycf.com,login.mycf.com");
assertFindsCorrectSubdomain("", "uaa.mycf.com", "uaa.mycf.com","login.mycf.com");
assertFindsCorrectSubdomain("", "login.mycf.com", "uaa.mycf.com","login.mycf.com");
}
private void assertFindsCorrectSubdomain(final String expectedSubdomain, final String incomingHostname, String internalHostnames) throws ServletException, IOException {

private void assertFindsCorrectSubdomain(final String expectedSubdomain, final String incomingHostname, String... additionalInternalHostnames) throws ServletException, IOException {

IdentityZoneResolvingFilter filter = new IdentityZoneResolvingFilter();
IdentityZoneProvisioning dao = Mockito.mock(IdentityZoneProvisioning.class);
filter.setIdentityZoneProvisioning(dao);
filter.setInternalHostnames(internalHostnames);
filter.setAdditionalInternalHostnames(new HashSet<>(Arrays.asList(additionalInternalHostnames)));

IdentityZone identityZone = new IdentityZone();
identityZone.setSubdomain(expectedSubdomain);
Mockito.when(dao.retrieveBySubdomain(Mockito.eq(expectedSubdomain))).thenReturn(identityZone);

MockHttpServletRequest request = new MockHttpServletRequest();
request.setServerName(incomingHostname);
MockHttpServletResponse response = new MockHttpServletResponse();
Expand All @@ -69,13 +67,13 @@ public void doFilter(ServletRequest request, ServletResponse response) throws IO
wasFilterExecuted = true;
}
};

filter.doFilter(request, response, filterChain);
assertTrue(wasFilterExecuted);
Mockito.verify(dao).retrieveBySubdomain(Mockito.eq(expectedSubdomain));
assertEquals(IdentityZone.getUaa(), IdentityZoneHolder.get());
}

@Test
public void holderIsNotSetWithNonMatchingIdentityZone() throws Exception {
String incomingSubdomain = "not_a_zone";
Expand All @@ -86,16 +84,16 @@ public void holderIsNotSetWithNonMatchingIdentityZone() throws Exception {
IdentityZoneProvisioning dao = Mockito.mock(IdentityZoneProvisioning.class);
FilterChain chain = Mockito.mock(FilterChain.class);
filter.setIdentityZoneProvisioning(dao);
filter.setInternalHostnames(uaaHostname);
filter.setAdditionalInternalHostnames(new HashSet<>(Arrays.asList(uaaHostname)));

IdentityZone identityZone = new IdentityZone();
identityZone.setSubdomain(incomingSubdomain);
Mockito.when(dao.retrieveBySubdomain(Mockito.eq(incomingSubdomain))).thenThrow(new EmptyResultDataAccessException(1));

MockHttpServletRequest request = new MockHttpServletRequest();
request.setServerName(incomingHostname);
MockHttpServletResponse response = new MockHttpServletResponse();

filter.doFilter(request, response, chain);
assertEquals(HttpServletResponse.SC_NOT_FOUND, response.getStatus());
assertEquals(IdentityZone.getUaa(), IdentityZoneHolder.get());
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
version=2.2.5
version=2.2.6
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Cloud Foundry
* Cloud Foundry
* Copyright (c) [2009-2014] Pivotal Software, Inc. All Rights Reserved.
*
* This product is licensed to you under the Apache License, Version 2.0 (the "License").
Expand All @@ -14,9 +14,12 @@

import org.cloudfoundry.identity.uaa.authentication.Origin;
import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal;
import org.cloudfoundry.identity.uaa.client.ClientConstants;
import org.cloudfoundry.identity.uaa.oauth.approval.Approval;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
Expand All @@ -25,20 +28,21 @@

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
* @author Vidya Valmikinathan
*/
@Controller
public class ProfileController {

private final ApprovalsService approvalsService;
private final ClientDetailsService clientDetailsService;

@Autowired
public ProfileController(ApprovalsService approvalsService) {
public ProfileController(ApprovalsService approvalsService,
ClientDetailsService clientDetailsService) {
this.approvalsService = approvalsService;
this.clientDetailsService = clientDetailsService;
}

/**
Expand All @@ -47,11 +51,26 @@ public ProfileController(ApprovalsService approvalsService) {
@RequestMapping(value = "/profile", method = RequestMethod.GET)
public String get(Authentication authentication, Model model) {
Map<String, List<DescribedApproval>> approvals = approvalsService.getCurrentApprovalsByClientId();
Map<String, String> clientNames = getClientNames(approvals);
model.addAttribute("clientnames", clientNames);
model.addAttribute("approvals", approvals);
model.addAttribute("isUaaManagedUser", isUaaManagedUser(authentication));
return "approvals";
}

protected Map<String, String> getClientNames(Map<String, List<DescribedApproval>> approvals) {
Map<String, String> clientNames = new LinkedHashMap<>();
for (String clientId : approvals.keySet()) {
ClientDetails details = clientDetailsService.loadClientByClientId(clientId);
String name = details.getClientId();
if (details.getAdditionalInformation()!=null && details.getAdditionalInformation().get(ClientConstants.CLIENT_NAME)!=null) {
name = (String)details.getAdditionalInformation().get(ClientConstants.CLIENT_NAME);
}
clientNames.put(clientId, name);
}
return clientNames;
}

/**
* Handle form post for revoking chosen approvals
*/
Expand Down
4 changes: 2 additions & 2 deletions login/src/main/resources/templates/web/approvals.html
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ <h2>Third Party Access</h2>
<input type="hidden" name="clientId" th:value="${client.key}"/>
<div class="application-scopes">
<div class="application-label">
<h3 th:text="${client.key}">
<h3 th:text="${clientnames[client.key]}">
Cloudbees
</h3><a href="#" th:href="|revoke?client=${client.key}|" th:attr="data-client-id=${client.key}" class="revoke-link">
Revoke Access
Expand Down Expand Up @@ -78,7 +78,7 @@ <h3 th:text="${client.key}">
<h3>Revoke Access</h3><div class="close-icon revocation-cancel">&#x2716;</div>
</div>
<p>
Are you sure you want to revoke access to <th:block th:text="${client.key}">Cloudbees</th:block>?
Are you sure you want to revoke access to <th:block th:text="${clientnames[client.key]}">Cloudbees</th:block>?
</p>
<div class="revocation-actions">
<button type="button" class="revocation-cancel btn btn-lowlight">Go Back</button>
Expand Down
Loading

0 comments on commit f7ce199

Please sign in to comment.