Skip to content

Commit

Permalink
Add ability to exclude claims from the JWT token
Browse files Browse the repository at this point in the history
https://www.pivotaltracker.com/story/show/106816904
[#106816904]

Signed-off-by: Jonathan Lo <jlo@us.ibm.com>
  • Loading branch information
fhanik authored and jlo committed Nov 4, 2015
1 parent 9c83d25 commit 721eb35
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 10 deletions.
Expand Up @@ -30,10 +30,8 @@
import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; import org.cloudfoundry.identity.uaa.user.UaaUserDatabase;
import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.cloudfoundry.identity.uaa.util.JsonUtils;
import org.cloudfoundry.identity.uaa.util.UaaTokenUtils; import org.cloudfoundry.identity.uaa.util.UaaTokenUtils;
import org.cloudfoundry.identity.uaa.zone.IdentityProviderProvisioning;
import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZone;
import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder;
import org.cloudfoundry.identity.uaa.zone.IdentityZoneProvisioning;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.context.ApplicationEventPublisherAware;
Expand Down Expand Up @@ -146,6 +144,16 @@ public class UaaTokenServices implements AuthorizationServerTokenServices, Resou
private List<String> validIdTokenScopes = Arrays.asList("openid"); private List<String> validIdTokenScopes = Arrays.asList("openid");
private TokenPolicy tokenPolicy; private TokenPolicy tokenPolicy;


private Set<String> excludedClaims = Collections.EMPTY_SET;

public Set<String> getExcludedClaims() {
return excludedClaims;
}

public void setExcludedClaims(Set<String> excludedClaims) {
this.excludedClaims = excludedClaims;
}

public void setValidIdTokenScopes(List<String> validIdTokenScopes) { public void setValidIdTokenScopes(List<String> validIdTokenScopes) {
this.validIdTokenScopes = validIdTokenScopes; this.validIdTokenScopes = validIdTokenScopes;
} }
Expand Down Expand Up @@ -507,6 +515,10 @@ private void populateIdToken(OpenIdToken token,
// them up // them up
response.put(AUD, resourceIds); response.put(AUD, resourceIds);


for (String excludedClaim : getExcludedClaims()) {
response.remove(excludedClaim);
}

return response; return response;
} }


Expand Down
3 changes: 3 additions & 0 deletions uaa/src/main/resources/uaa.yml
Expand Up @@ -216,6 +216,9 @@ oauth:
# QH+xY/4h8tgL+eASz5QWhj8DItm8wYGI5lKJr8f36jk0JLPUXODyDAeN6ekXY9LI # QH+xY/4h8tgL+eASz5QWhj8DItm8wYGI5lKJr8f36jk0JLPUXODyDAeN6ekXY9LI
# fudkijw0dnh28LJqbkFF5wLNtATzyCfzjp+czrPMn9uqLNKt/iVD # fudkijw0dnh28LJqbkFF5wLNtATzyCfzjp+czrPMn9uqLNKt/iVD
# -----END RSA PRIVATE KEY----- # -----END RSA PRIVATE KEY-----
# claims:
# exclude:
# - authorities


# Configure whitelist for allowing cross-origin XMLHttpRequest requests. # Configure whitelist for allowing cross-origin XMLHttpRequest requests.
#cors.xhr.allowed.headers: Accept,Authorization #cors.xhr.allowed.headers: Accept,Authorization
Expand Down
9 changes: 9 additions & 0 deletions uaa/src/main/webapp/WEB-INF/spring/oauth-endpoints.xml
Expand Up @@ -315,6 +315,15 @@
<property name="issuer" value="${issuer.uri:http://localhost:8080/uaa}" /> <property name="issuer" value="${issuer.uri:http://localhost:8080/uaa}" />
<property name="approvalStore" ref="approvalStore" /> <property name="approvalStore" ref="approvalStore" />
<property name="tokenPolicy" ref="globalTokenPolicy" /> <property name="tokenPolicy" ref="globalTokenPolicy" />
<property name="excludedClaims" ref="excludedClaims"/>
</bean>

<bean id="excludedClaims" class="java.util.LinkedHashSet">
<constructor-arg type="java.util.Collection"
value="#{@config['jwt']==null ? T(java.util.Collections).EMPTY_SET :
@config['jwt.token']==null ? T(java.util.Collections).EMPTY_SET :
@config['jwt.token.claims']==null ? T(java.util.Collections).EMPTY_SET :
@config['jwt.token.claims.exclude']==null ? T(java.util.Collections).EMPTY_SET : @config['jwt.token.claims.exclude']}"/>
</bean> </bean>


<oauth:resource-server id="oauthWithoutResourceAuthenticationFilter" token-services-ref="tokenServices" <oauth:resource-server id="oauthWithoutResourceAuthenticationFilter" token-services-ref="tokenServices"
Expand Down
Expand Up @@ -25,6 +25,7 @@
import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderConfigurator; import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderConfigurator;
import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderDefinition;
import org.cloudfoundry.identity.uaa.login.util.FakeJavaMailSender; import org.cloudfoundry.identity.uaa.login.util.FakeJavaMailSender;
import org.cloudfoundry.identity.uaa.oauth.Claims;
import org.cloudfoundry.identity.uaa.oauth.token.UaaTokenServices; import org.cloudfoundry.identity.uaa.oauth.token.UaaTokenServices;
import org.cloudfoundry.identity.uaa.oauth.token.UaaTokenStore; import org.cloudfoundry.identity.uaa.oauth.token.UaaTokenStore;
import org.cloudfoundry.identity.uaa.rest.jdbc.SimpleSearchQueryConverter; import org.cloudfoundry.identity.uaa.rest.jdbc.SimpleSearchQueryConverter;
Expand Down Expand Up @@ -69,11 +70,13 @@
import java.util.Scanner; import java.util.Scanner;
import java.util.Set; import java.util.Set;


import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame; import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;


public class BootstrapTests { public class BootstrapTests {
Expand Down Expand Up @@ -390,7 +393,7 @@ public void testDefaultInternalHostnamesAndNoDBSettings() throws Exception {
} }


@Test @Test
public void testBootstrappedIdps() throws Exception { public void testBootstrappedIdps_and_ExcludedClaims() throws Exception {


//generate login.yml with SAML and uaa.yml with LDAP //generate login.yml with SAML and uaa.yml with LDAP
System.setProperty("database.caseinsensitive", "false"); System.setProperty("database.caseinsensitive", "false");
Expand All @@ -405,6 +408,8 @@ public void testBootstrappedIdps() throws Exception {
//we have provided 4 here, but the original login.yml may add, but not remove some //we have provided 4 here, but the original login.yml may add, but not remove some
assertTrue(samlProviders.getIdentityProviderDefinitions().size() >= 4); assertTrue(samlProviders.getIdentityProviderDefinitions().size() >= 4);


assertThat(context.getBean(UaaTokenServices.class).getExcludedClaims(), containsInAnyOrder(Claims.AUTHORITIES));

//verify that they got loaded in the DB //verify that they got loaded in the DB
for (SamlIdentityProviderDefinition def : samlProviders.getIdentityProviderDefinitions()) { for (SamlIdentityProviderDefinition def : samlProviders.getIdentityProviderDefinitions()) {
assertNotNull(providerProvisioning.retrieveByOrigin(def.getIdpEntityAlias(), IdentityZone.getUaa().getId())); assertNotNull(providerProvisioning.retrieveByOrigin(def.getIdpEntityAlias(), IdentityZone.getUaa().getId()));
Expand Down
Expand Up @@ -1760,15 +1760,54 @@ public void testGetClientCredentialsTokenForDefaultIdentityZone() throws Excepti
String clientId = "testclient" + new RandomValueStringGenerator().generate(); String clientId = "testclient" + new RandomValueStringGenerator().generate();
String scopes = "space.*.developer,space.*.admin,org.*.reader,org.123*.admin,*.*,*"; String scopes = "space.*.developer,space.*.admin,org.*.reader,org.123*.admin,*.*,*";
setUpClients(clientId, scopes, scopes, GRANT_TYPES, true); setUpClients(clientId, scopes, scopes, GRANT_TYPES, true);
getMockMvc().perform(post("/oauth/token")
.accept(MediaType.APPLICATION_JSON_VALUE) String body = getMockMvc().perform(post("/oauth/token")
.header("Authorization", "Basic " + new String(Base64.encode((clientId + ":" + SECRET).getBytes()))) .accept(MediaType.APPLICATION_JSON_VALUE)
.param("grant_type", "client_credentials") .header("Authorization", "Basic " + new String(Base64.encode((clientId + ":" + SECRET).getBytes())))
.param("client_id", clientId) .param("grant_type", "client_credentials")
.param("client_secret", SECRET)) .param("client_id", clientId)
.andExpect(status().isOk()); .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(Claims.AUTHORITIES));
assertNotNull(claims.get(Claims.AZP));
} }


@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(Claims.AUTHORITIES, Claims.AZP)));
String clientId = "testclient" + new RandomValueStringGenerator().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(Claims.AUTHORITIES));
assertNull(claims.get(Claims.AZP));
}finally {
getWebApplicationContext().getBean(UaaTokenServices.class).setExcludedClaims(originalExclude);
}
}


@Test @Test
public void testGetClientCredentialsTokenForOtherIdentityZone() throws Exception { public void testGetClientCredentialsTokenForOtherIdentityZone() throws Exception {
String subdomain = "testzone"+new RandomValueStringGenerator().generate(); String subdomain = "testzone"+new RandomValueStringGenerator().generate();
Expand Down
5 changes: 5 additions & 0 deletions uaa/src/test/resources/test/bootstrap/uaa.yml
Expand Up @@ -7,3 +7,8 @@ ldap:
password: 'password' password: 'password'
searchBase: '' searchBase: ''
searchFilter: 'cn={0}' searchFilter: 'cn={0}'
jwt:
token:
claims:
exclude:
- authorities

0 comments on commit 721eb35

Please sign in to comment.