Skip to content

Commit

Permalink
Merge branch 'feature/user_token_fixes' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
fhanik committed Sep 1, 2016
2 parents b756422 + 1d097dd commit 322a05d
Show file tree
Hide file tree
Showing 12 changed files with 268 additions and 80 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,16 @@
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.GRANT_TYPE_USER_TOKEN;

public class ClientAdminEndpointsValidator implements InitializingBean, ClientDetailsValidator {


private final Log logger = LogFactory.getLog(getClass());

private static final Set<String> VALID_GRANTS = new HashSet<>(Arrays.asList("implicit", "password",
"client_credentials", "authorization_code", "refresh_token"));
"client_credentials", "authorization_code", "refresh_token", GRANT_TYPE_USER_TOKEN));

private static final Collection<String> NON_ADMIN_INVALID_GRANTS = new HashSet<>(Arrays.asList("password"));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal;
import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants;
import org.cloudfoundry.identity.uaa.oauth.token.RevocableToken;
import org.cloudfoundry.identity.uaa.oauth.token.RevocableTokenProvisioning;
Expand All @@ -29,9 +30,11 @@
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.oauth2.common.util.RandomValueStringGenerator;
import org.springframework.security.oauth2.provider.NoSuchClientException;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
import org.springframework.security.oauth2.provider.error.DefaultWebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.expression.OAuth2ExpressionUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PathVariable;
Expand Down Expand Up @@ -87,18 +90,36 @@ public ResponseEntity<Void> revokeTokenById(@PathVariable String tokenId) {
return new ResponseEntity<>(OK);
}

@RequestMapping(value = "/oauth/token/list/user/{userId}", method = GET)
public ResponseEntity<List<RevocableToken>> listUserTokens(@PathVariable String userId) {
logger.debug("Listing revocable tokens for user:"+userId);
List<RevocableToken> result = tokenProvisioning.getUserTokens(userId);
@RequestMapping(value = "/oauth/token/list", method = GET)
public ResponseEntity<List<RevocableToken>> listUserTokens(OAuth2Authentication authentication) {
UaaPrincipal principal = (UaaPrincipal) authentication.getUserAuthentication().getPrincipal();
String userId = principal.getId();
String clientId = authentication.getOAuth2Request().getClientId();
logger.debug("Listing revocable tokens access token userId:"+ userId +" clientId:"+ clientId);
List<RevocableToken> result = tokenProvisioning.getUserTokens(userId, clientId);
return new ResponseEntity<>(result, OK);
}

@RequestMapping(value = "/oauth/token/list/user/{userId}", method = GET)
public ResponseEntity<List<RevocableToken>> listUserTokens(@PathVariable String userId, OAuth2Authentication authentication) {
if (OAuth2ExpressionUtils.hasAnyScope(authentication, new String[] {"tokens.list", "uaa.admin"})) {
logger.debug("Listing revocable tokens for user:" + userId);
List<RevocableToken> result = tokenProvisioning.getUserTokens(userId);
return new ResponseEntity<>(result, OK);
} else {
return listUserTokens(authentication);
}
}

@RequestMapping(value = "/oauth/token/list/client/{clientId}", method = GET)
public ResponseEntity<List<RevocableToken>> listClientTokens(@PathVariable String clientId) {
logger.debug("Listing revocable tokens for client:"+clientId);
List<RevocableToken> result = tokenProvisioning.getClientTokens(clientId);
return new ResponseEntity<>(result, OK);
public ResponseEntity<List<RevocableToken>> listClientTokens(@PathVariable String clientId, OAuth2Authentication authentication) {
if (OAuth2ExpressionUtils.hasAnyScope(authentication, new String[] {"tokens.list", "uaa.admin"})) {
logger.debug("Listing revocable tokens for client:" + clientId);
List<RevocableToken> result = tokenProvisioning.getClientTokens(clientId);
return new ResponseEntity<>(result, OK);
} else {
return listUserTokens(authentication);
}
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
import java.sql.SQLException;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;

import static org.springframework.util.StringUtils.isEmpty;

public class JdbcRevocableTokenProvisioning implements RevocableTokenProvisioning, SystemDeletable {

Expand Down Expand Up @@ -139,6 +142,14 @@ public List<RevocableToken> getUserTokens(String userId) {
return template.query(GET_BY_USER_QUERY, rowMapper, userId, IdentityZoneHolder.get().getId());
}

@Override
public List<RevocableToken> getUserTokens(String userId, String clientId) {
if (isEmpty(clientId)) {
throw new NullPointerException("Client ID can not be null when retrieving tokens.");
}
return getUserTokens(userId).stream().filter(r -> clientId.equals(r.getClientId())).collect(Collectors.toList());
}

@Override
public List<RevocableToken> getClientTokens(String clientId) {
return template.query(GET_BY_CLIENT_QUERY, rowMapper, clientId, IdentityZoneHolder.get().getId());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ public interface RevocableTokenProvisioning extends ResourceManager<RevocableTok

List<RevocableToken> getUserTokens(String userId);

List<RevocableToken> getUserTokens(String userId, String clientId);

List<RevocableToken> getClientTokens(String clientId);


Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,30 @@
package org.cloudfoundry.identity.uaa.zone;

import java.util.Collections;

import org.apache.commons.lang.StringUtils;
import org.cloudfoundry.identity.uaa.client.ClientDetailsValidator;
import org.cloudfoundry.identity.uaa.client.InvalidClientDetailsException;
import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants;
import org.cloudfoundry.identity.uaa.constants.OriginKeys;
import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.client.BaseClientDetails;

import java.util.Collections;

import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.GRANT_TYPE_REFRESH_TOKEN;
import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.GRANT_TYPE_USER_TOKEN;

public class ZoneEndpointsClientDetailsValidator implements ClientDetailsValidator {

private final String requiredScope;

public ZoneEndpointsClientDetailsValidator(String requiredScope) {
this.requiredScope = requiredScope;
}

@Override
public ClientDetails validate(ClientDetails clientDetails, Mode mode) throws InvalidClientDetailsException {

if (mode == Mode.CREATE) {
if (!Collections.singleton("openid").equals(clientDetails.getScope())) {
throw new InvalidClientDetailsException("only openid scope is allowed");
Expand All @@ -34,6 +37,8 @@ public ClientDetails validate(ClientDetails clientDetails, Mode mode) throws Inv
}
if (clientDetails.getAuthorizedGrantTypes().contains("client_credentials") ||
clientDetails.getAuthorizedGrantTypes().contains("authorization_code") ||
clientDetails.getAuthorizedGrantTypes().contains(GRANT_TYPE_USER_TOKEN) ||
clientDetails.getAuthorizedGrantTypes().contains(GRANT_TYPE_REFRESH_TOKEN) ||
clientDetails.getAuthorizedGrantTypes().contains("password")) {
if (StringUtils.isBlank(clientDetails.getClientSecret())) {
throw new InvalidClientDetailsException("client_secret cannot be blank");
Expand All @@ -42,12 +47,12 @@ public ClientDetails validate(ClientDetails clientDetails, Mode mode) throws Inv
if (!Collections.singletonList(OriginKeys.UAA).equals(clientDetails.getAdditionalInformation().get(ClientConstants.ALLOWED_PROVIDERS))) {
throw new InvalidClientDetailsException("only the internal IdP ('uaa') is allowed");
}

BaseClientDetails validatedClientDetails = new BaseClientDetails(clientDetails);
validatedClientDetails.setAdditionalInformation(clientDetails.getAdditionalInformation());
validatedClientDetails.setResourceIds(Collections.singleton("none"));
validatedClientDetails.addAdditionalInformation(ClientConstants.CREATED_WITH, requiredScope);
return validatedClientDetails;
return validatedClientDetails;
} else if (mode == Mode.MODIFY) {
throw new IllegalStateException("This validator cannot be used for modification requests");
} else if (mode == Mode.DELETE) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@

import java.util.Arrays;

import static org.junit.Assert.assertTrue;
import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.GRANT_TYPE_USER_TOKEN;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
Expand All @@ -42,10 +44,7 @@ public void createClient() throws Exception {
client.setClientSecret("secret");
caller = new BaseClientDetails("caller","","","client_credentials","clients.write");
validator = new ClientAdminEndpointsValidator();
}

@Test
public void testValidate_Should_Allow_Prefix_Names() throws Exception {
QueryableResourceManager<ClientDetails> clientDetailsService = mock(QueryableResourceManager.class);
SecurityContextAccessor accessor = mock(SecurityContextAccessor.class);
when(accessor.isAdmin()).thenReturn(false);
Expand All @@ -55,6 +54,18 @@ public void testValidate_Should_Allow_Prefix_Names() throws Exception {
validator.setClientDetailsService(clientDetailsService);
validator.setSecurityContextAccessor(accessor);

}

@Test
public void test_validate_user_token_grant_type() throws Exception {
client.setAuthorizedGrantTypes(Arrays.asList(GRANT_TYPE_USER_TOKEN));
validator.validate(client, true, true);
}


@Test
public void testValidate_Should_Allow_Prefix_Names() throws Exception {

client.setAuthorities(Arrays.asList(new SimpleGrantedAuthority("uaa.resource")));
validator.validate(client, true, true);
client.setAuthorities(Arrays.asList(new SimpleGrantedAuthority(caller.getClientId()+".some.other.authority")));
Expand All @@ -63,7 +74,7 @@ public void testValidate_Should_Allow_Prefix_Names() throws Exception {
validator.validate(client, true, true);
fail();
} catch (InvalidClientDetailsException x) {
assertTrue(x.getMessage().contains("not an allowed authority"));
assertThat(x.getMessage(), containsString("not an allowed authority"));
}


Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
package org.cloudfoundry.identity.uaa.oauth;

import static org.junit.Assert.*;

import java.util.Collections;

import org.cloudfoundry.identity.uaa.client.ClientDetailsValidator.Mode;
import org.cloudfoundry.identity.uaa.client.InvalidClientDetailsException;
import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants;
import org.cloudfoundry.identity.uaa.constants.OriginKeys;
import org.cloudfoundry.identity.uaa.client.ClientDetailsValidator.Mode;
import org.cloudfoundry.identity.uaa.zone.ZoneEndpointsClientDetailsValidator;
import org.junit.Before;
import org.junit.Test;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.client.BaseClientDetails;

import java.util.Arrays;
import java.util.Collections;

import static org.cloudfoundry.identity.uaa.oauth.client.ClientConstants.ALLOWED_PROVIDERS;
import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.GRANT_TYPE_REFRESH_TOKEN;
import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.GRANT_TYPE_USER_TOKEN;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;

public class ZoneEndpointsClientDetailsValidatorTests {

private ZoneEndpointsClientDetailsValidator zoneEndpointsClientDetailsValidator;
Expand All @@ -27,43 +33,51 @@ public void setUp() throws Exception {
public void testCreateLimitedClient() {
BaseClientDetails clientDetails = new BaseClientDetails("valid-client", null, "openid", "authorization_code,password", "uaa.resource");
clientDetails.setClientSecret("secret");
clientDetails.addAdditionalInformation(ClientConstants.ALLOWED_PROVIDERS, Collections.singletonList(OriginKeys.UAA));
clientDetails.addAdditionalInformation(ALLOWED_PROVIDERS, Collections.singletonList(OriginKeys.UAA));
ClientDetails validatedClientDetails = zoneEndpointsClientDetailsValidator.validate(clientDetails, Mode.CREATE);
assertEquals(clientDetails.getClientId(), validatedClientDetails.getClientId());
assertEquals(clientDetails.getScope(), validatedClientDetails.getScope());
assertEquals(clientDetails.getAuthorizedGrantTypes(), validatedClientDetails.getAuthorizedGrantTypes());
assertEquals(clientDetails.getAuthorities(), validatedClientDetails.getAuthorities());
assertEquals(Collections.singleton("none"), validatedClientDetails.getResourceIds());
assertEquals(Collections.singletonList(OriginKeys.UAA), validatedClientDetails.getAdditionalInformation().get(ClientConstants.ALLOWED_PROVIDERS));
assertEquals(Collections.singletonList(OriginKeys.UAA), validatedClientDetails.getAdditionalInformation().get(ALLOWED_PROVIDERS));
}

@Test(expected = InvalidClientDetailsException.class)
public void testCreateClientNoNameIsInvalid() {
BaseClientDetails clientDetails = new BaseClientDetails("", null, "openid", "authorization_code", "uaa.resource");
clientDetails.setClientSecret("secret");
zoneEndpointsClientDetailsValidator.validate(clientDetails, Mode.CREATE);
}
@Test(expected = InvalidClientDetailsException.class)

@Test
public void testCreateClientNoSecretIsInvalid() {
ClientDetails clientDetails = new BaseClientDetails("client", null, "openid", "authorization_code", "uaa.resource");
zoneEndpointsClientDetailsValidator.validate(clientDetails, Mode.CREATE);
for (String grantType : Arrays.asList("password", "client_credentials", "authorization_code", GRANT_TYPE_USER_TOKEN, GRANT_TYPE_REFRESH_TOKEN)) {
try {
BaseClientDetails clientDetails = new BaseClientDetails("client", null, "openid", grantType, "uaa.resource");
clientDetails.addAdditionalInformation(ALLOWED_PROVIDERS, Collections.singletonList(OriginKeys.UAA));
zoneEndpointsClientDetailsValidator.validate(clientDetails, Mode.CREATE);
fail("Grant type:"+grantType + " must require a secret");
} catch (InvalidClientDetailsException e) {
assertThat(e.getMessage(), containsString("client_secret cannot be blank"));
}
}
}

@Test
public void testCreateClientNoSecretForImplicitIsValid() {
BaseClientDetails clientDetails = new BaseClientDetails("client", null, "openid", "implicit", "uaa.resource");
clientDetails.addAdditionalInformation(ClientConstants.ALLOWED_PROVIDERS, Collections.singletonList(OriginKeys.UAA));
clientDetails.addAdditionalInformation(ALLOWED_PROVIDERS, Collections.singletonList(OriginKeys.UAA));
ClientDetails validatedClientDetails = zoneEndpointsClientDetailsValidator.validate(clientDetails, Mode.CREATE);
assertEquals(clientDetails.getAuthorizedGrantTypes(), validatedClientDetails.getAuthorizedGrantTypes());
}

@Test(expected = InvalidClientDetailsException.class)
public void testCreateAdminScopeClientIsInvalid() {
ClientDetails clientDetails = new BaseClientDetails("admin-client", null, "uaa.admin", "authorization_code", "uaa.resource");
zoneEndpointsClientDetailsValidator.validate(clientDetails, Mode.CREATE);
}

@Test(expected = InvalidClientDetailsException.class)
public void testCreateAdminAuthorityClientIsInvalid() {
ClientDetails clientDetails = new BaseClientDetails("admin-client", null, "openid", "authorization_code", "uaa.admin");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public class JdbcRevocableTokenProvisioningTest extends JdbcTestBase {
public void createData() {
createData("test-token-id", "test-user-id", "test-client-id");
}

public void createData(String tokenId, String userId, String clientId) {
value = new char[100*1024];
Arrays.fill(value, 'X');
Expand Down Expand Up @@ -148,6 +149,39 @@ public void listUserTokens() throws Exception {
listTokens(false);
}

@Test(expected = NullPointerException.class)
public void listUserTokens_Null_ClientId() {
dao.getUserTokens("userid", null);
}

@Test(expected = NullPointerException.class)
public void listUserTokens_Empty_ClientId() {
dao.getUserTokens("userid", "");
}

@Test
public void listUserTokenForClient() throws Exception {
String clientId = "test-client-id";
String userId = "test-user-id";
List<RevocableToken> expectedTokens = new ArrayList<>();
int count = 37;
RandomValueStringGenerator generator = new RandomValueStringGenerator(36);
for (int i=0; i<count; i++) {
createData(generator.generate(), userId, clientId);
insertToken();
expectedTokens.add(this.expected);
}

for (int i=0; i<count; i++) {
//create a random record that should not show up
createData(generator.generate(), generator.generate(), generator.generate());
insertToken();
}

List<RevocableToken> actualTokens = dao.getUserTokens(userId, clientId);
assertThat(actualTokens, containsInAnyOrder(expectedTokens.toArray()));
}

@Test
public void listClientTokens() throws Exception {
listTokens(true);
Expand All @@ -170,6 +204,10 @@ public void listTokens(boolean client) throws Exception {
expectedTokens.add(this.expected);
}

//create a random record that should not show up
createData(generator.generate(), generator.generate(), generator.generate());
insertToken();

List<RevocableToken> actualTokens = client ? dao.getClientTokens(clientId) : dao.getUserTokens(userId);
assertThat(actualTokens, containsInAnyOrder(expectedTokens.toArray()));
}
Expand Down
Loading

0 comments on commit 322a05d

Please sign in to comment.