Skip to content

Commit

Permalink
Add an endpoint to list user and client tokens
Browse files Browse the repository at this point in the history
  • Loading branch information
fhanik committed Aug 25, 2016
1 parent d6b65eb commit 7bceab7
Show file tree
Hide file tree
Showing 6 changed files with 236 additions and 45 deletions.
Expand Up @@ -90,12 +90,14 @@ public ResponseEntity<Void> revokeTokenById(@PathVariable String tokenId) {
@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 = null;
if (result!=null) {
for (RevocableToken rt : result) {
rt.setValue(null);
}
}
List<RevocableToken> result = tokenProvisioning.getUserTokens(userId);
return new ResponseEntity<>(result, OK);
}

@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);
}

Expand Down
Expand Up @@ -20,7 +20,7 @@ public interface RevocableTokenProvisioning extends ResourceManager<RevocableTok

List<RevocableToken> getUserTokens(String userId);

List<RevocableToken> getClientTokens(String userId);
List<RevocableToken> getClientTokens(String clientId);



Expand Down
Expand Up @@ -22,10 +22,11 @@
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;

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

public class IsSelfCheck {

private final RevocableTokenProvisioning tokenProvisioning;
Expand All @@ -34,26 +35,29 @@ public IsSelfCheck(RevocableTokenProvisioning tokenProvisioning) {
this.tokenProvisioning = tokenProvisioning;
}



public boolean isUserSelf(HttpServletRequest request, int pathParameterIndex) {
String pathInfo = UaaUrlUtils.getRequestPath(request);
if (!StringUtils.hasText(pathInfo)) {
return false;
}

String idFromUrl = extractIdFromUrl(pathParameterIndex, pathInfo);
if (idFromUrl==null) {
return false;
}
String idFromAuth = extractUserIdFromAuthentication(SecurityContextHolder.getContext().getAuthentication());

String idFromAuth = extractIdFromAuthentication(SecurityContextHolder.getContext().getAuthentication(), false);
if (idFromAuth==null) {
return false;
}
return idFromAuth!=null &&
idFromAuth.equals(idFromUrl);
}

return idFromAuth.equals(idFromUrl);
protected String extractClientIdFromAuthentication(Authentication authentication) {
if (authentication==null) {
return null;
}
if (authentication instanceof OAuth2Authentication) {
OAuth2Authentication a = (OAuth2Authentication)authentication;
return a.getOAuth2Request().getClientId();
}
return null;
}

protected String extractIdFromAuthentication(Authentication authentication, boolean clientAuthenticationAllowed) {
protected String extractUserIdFromAuthentication(Authentication authentication) {
if (authentication==null) {
return null;
}
Expand All @@ -62,9 +66,7 @@ protected String extractIdFromAuthentication(Authentication authentication, bool
}
if (authentication instanceof OAuth2Authentication) {
OAuth2Authentication a = (OAuth2Authentication)authentication;
if (a.isClientOnly()) {
return clientAuthenticationAllowed ? a.getOAuth2Request().getClientId() : null;
} else {
if (!a.isClientOnly()) {
if (a.getUserAuthentication().getPrincipal() instanceof UaaPrincipal) {
return ((UaaPrincipal)a.getUserAuthentication().getPrincipal()).getId();
}
Expand All @@ -74,29 +76,47 @@ protected String extractIdFromAuthentication(Authentication authentication, bool
}

protected String extractIdFromUrl(int pathParameterIndex, String pathInfo) {
if (!hasText(pathInfo)) {
return null;
}
return UaaUrlUtils.extractPathVariableFromUrl(pathParameterIndex, pathInfo);
}

public boolean isTokenRevocationForSelf(HttpServletRequest request) {

public boolean isUserTokenRevocationForSelf(HttpServletRequest request) {
String pathInfo = UaaUrlUtils.getRequestPath(request);
if (!StringUtils.hasText(pathInfo)) {
return false;
}

String tokenId = extractIdFromUrl(3, pathInfo);
if (tokenId==null) {
return false;
String idFromAuth = extractUserIdFromAuthentication(SecurityContextHolder.getContext().getAuthentication());
if (hasText(idFromAuth) && hasText(tokenId)) {
RevocableToken revocableToken = tokenProvisioning.retrieve(tokenId);
String subjectId = revocableToken.getUserId();
return idFromAuth.equals(subjectId);
}
return false;
}

String idFromAuth = extractIdFromAuthentication(SecurityContextHolder.getContext().getAuthentication(), true);
if (idFromAuth==null) {
return false;
public boolean isClientTokenRevocationForSelf(HttpServletRequest request) {
String pathInfo = UaaUrlUtils.getRequestPath(request);
String tokenId = extractIdFromUrl(3, pathInfo);
String idFromAuth = extractClientIdFromAuthentication(SecurityContextHolder.getContext().getAuthentication());
if (hasText(idFromAuth) && hasText(tokenId)) {
RevocableToken revocableToken = tokenProvisioning.retrieve(tokenId);
String subjectId = revocableToken.getClientId();
return idFromAuth.equals(subjectId);
}
return false;
}

RevocableToken revocableToken = tokenProvisioning.retrieve(tokenId);
String subjectId = revocableToken.getUserId() != null ? revocableToken.getUserId() : revocableToken.getClientId();
public boolean isTokenListForAuthenticatedClient(HttpServletRequest request) {
String pathInfo = UaaUrlUtils.getRequestPath(request);
String clientId = extractIdFromUrl(4, pathInfo);
String idFromAuth = extractClientIdFromAuthentication(SecurityContextHolder.getContext().getAuthentication());
return hasText(idFromAuth) && idFromAuth.equals(clientId);
}

return idFromAuth.equals(subjectId);
public boolean isTokenListForAuthenticatedUser(HttpServletRequest request) {
String pathInfo = UaaUrlUtils.getRequestPath(request);
String userId = extractIdFromUrl(4, pathInfo);
String idFromAuth = extractUserIdFromAuthentication(SecurityContextHolder.getContext().getAuthentication());
return hasText(idFromAuth) && idFromAuth.equals(userId);
}
}
Expand Up @@ -129,7 +129,7 @@ public void testSelfUserToken() throws Exception {
SecurityContextHolder.getContext().setAuthentication(authentication);
request.setPathInfo("/oauth/token/revoke/" + tokenId);

assertTrue(bean.isTokenRevocationForSelf(request));
assertTrue(bean.isUserTokenRevocationForSelf(request));
}

@Test
Expand All @@ -150,7 +150,7 @@ public void testSelfClientToken() throws Exception {
when(tokenProvisioning.retrieve(tokenId)).thenReturn(revocableToken);
request.setPathInfo("/oauth/token/revoke/" + tokenId);

assertTrue(bean.isTokenRevocationForSelf(request));
assertTrue(bean.isClientTokenRevocationForSelf(request));
}

@Test
Expand All @@ -164,6 +164,6 @@ public void testNotSelfToken() throws Exception {
SecurityContextHolder.getContext().setAuthentication(authentication);
request.setPathInfo("/oauth/token/revoke/" + tokenId);

assertFalse(bean.isTokenRevocationForSelf(request));
assertFalse(bean.isUserTokenRevocationForSelf(request));
}
}
19 changes: 16 additions & 3 deletions uaa/src/main/webapp/WEB-INF/spring/oauth-endpoints.xml
Expand Up @@ -69,9 +69,22 @@
authentication-manager-ref="emptyAuthenticationManager"
entry-point-ref="oauthAuthenticationEntryPoint"
xmlns="http://www.springframework.org/schema/security" use-expressions="true">
<intercept-url pattern="/oauth/token/revoke/client/**" access="#oauth2.hasScope('uaa.admin')" />
<intercept-url pattern="/oauth/token/revoke/user/**" access="#oauth2.hasScope('uaa.admin')" />
<intercept-url pattern="/**" access="#oauth2.hasScope('tokens.revoke') or @self.isTokenRevocationForSelf(request)" />
<intercept-url pattern="/oauth/token/revoke/client/**" access="#oauth2.hasScope('tokens.revoke') or @self.isClientTokenRevocationForSelf(request)" />
<intercept-url pattern="/oauth/token/revoke/user/**" access="#oauth2.hasScope('tokens.revoke') or @self.isUserTokenRevocationForSelf(request)" />
<custom-filter ref="resourceAgnosticAuthenticationFilter" position="PRE_AUTH_FILTER" />
<access-denied-handler ref="oauthAccessDeniedHandler" />
<expression-handler ref="oauthWebExpressionHandler" />
<csrf disabled="true"/>
</http>

<http name="tokenListFilter"
pattern="/oauth/token/list/**"
create-session="stateless"
authentication-manager-ref="emptyAuthenticationManager"
entry-point-ref="oauthAuthenticationEntryPoint"
xmlns="http://www.springframework.org/schema/security" use-expressions="true">
<intercept-url pattern="/oauth/token/list/user/**" access="(#oauth2.hasScope('uaa.user') and @self.isTokenListForAuthenticatedUser(request)) or (#oauth2.hasScope('uaa.admin'))" />
<intercept-url pattern="/oauth/token/list/client/**" access="@self.isTokenListForAuthenticatedClient(request) or #oauth2.hasScope('uaa.admin')" />
<custom-filter ref="resourceAgnosticAuthenticationFilter" position="PRE_AUTH_FILTER" />
<access-denied-handler ref="oauthAccessDeniedHandler" />
<expression-handler ref="oauthWebExpressionHandler" />
Expand Down
Expand Up @@ -15,18 +15,174 @@
package org.cloudfoundry.identity.uaa.mock.token;


import com.fasterxml.jackson.core.type.TypeReference;
import org.cloudfoundry.identity.uaa.constants.OriginKeys;
import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils;
import org.cloudfoundry.identity.uaa.oauth.token.RevocableToken;
import org.cloudfoundry.identity.uaa.scim.ScimUser;
import org.cloudfoundry.identity.uaa.util.JsonUtils;
import org.cloudfoundry.identity.uaa.zone.IdentityZone;
import org.junit.Before;
import org.junit.Test;
import org.springframework.security.oauth2.common.util.RandomValueStringGenerator;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultMatcher;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

import static org.junit.Assert.assertTrue;
import static org.springframework.http.HttpHeaders.AUTHORIZATION;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

public class ListUserTokenMockMvcTests extends AbstractTokenMockMvcTests {

private ClientDetails client1, client2,client3;
private ScimUser user1, user2, user3;
private RandomValueStringGenerator generator = new RandomValueStringGenerator();
MultiValueMap<String, String> tokensPerUser = new LinkedMultiValueMap<>();
MultiValueMap<String, String> tokensPerClient = new LinkedMultiValueMap<>();
private String adminClientToken;
private String clientWithUser1IdAsIdToken;



@Before
public void createUsersAndClients() throws Exception {
user1 = setUpUser(generator.generate(), "uaa.user,scim.read,scim.write", OriginKeys.UAA, IdentityZone.getUaa().getId());
user2 = setUpUser(generator.generate(), "uaa.user,scim.read,scim.write", OriginKeys.UAA, IdentityZone.getUaa().getId());
user3 = setUpUser(generator.generate(), "uaa.user,scim.read,scim.write", OriginKeys.UAA, IdentityZone.getUaa().getId());
client1 = setUpClients(generator.generate(), "", "uaa.user,scim.read","password,refresh_token", false);
client2 = setUpClients(generator.generate(), "", "uaa.user,scim.read","password,refresh_token", false);
client3 = setUpClients(generator.generate(), "", "uaa.user,scim.read","password,refresh_token", false);
setUpClients(user1.getId(), "uaa.user", "uaa.user,scim.read","client_credentials,password,refresh_token", false);

for (ScimUser user : Arrays.asList(user1, user2, user3)) {
for (ClientDetails client : Arrays.asList(client1, client2, client3)) {
String token = MockMvcUtils.getUserOAuthAccessToken(
getMockMvc(),
client.getClientId(),
SECRET,
user.getUserName(),
SECRET,
null,
null,
true);
tokensPerUser.add(user.getId(), token);
tokensPerClient.add(client.getClientId(), token);
}
}
adminClientToken = MockMvcUtils.getClientCredentialsOAuthAccessToken(
getMockMvc(),
"admin",
"adminsecret",
null,
null,
true);

clientWithUser1IdAsIdToken = MockMvcUtils.getClientCredentialsOAuthAccessToken(
getMockMvc(),
user1.getId(),
SECRET,
null,
null,
true);

}

@Test
public void listUserTokens() throws Exception {
public void listUserTokenAsAdmin() throws Exception {
List<RevocableToken> tokens = getTokenList("/oauth/token/list/user/" + user1.getId(),
adminClientToken,
status().isOk());
List<String> tokenIds = getTokenIds(tokens);
validateTokens(tokenIds, tokensPerUser.get(user1.getId()));
}

protected void validateTokens(List<String> actual, List<String> expected) {
for (String t : expected) {
assertTrue("Expecting token:"+t+" to be present in list.", actual.contains(t));
}
}

protected List<String> getTokenIds(List<RevocableToken> tokens) {
List<String> accessTokens = tokens.stream().map(RevocableToken::getTokenId).collect(Collectors.toList());
return accessTokens;

}

@Test
public void listClientTokenAsAdmin() throws Exception {
List<RevocableToken> tokens = getTokenList("/oauth/token/list/client/" + client1.getClientId(),
adminClientToken,
status().isOk());
List<String> tokenIds = getTokenIds(tokens);
validateTokens(tokenIds, tokensPerClient.get(client1.getClientId()));
}

@Test
public void listUserTokenAsAnotherUser() throws Exception {
getTokenList("/oauth/token/list/user/" + user1.getId(),
tokensPerUser.getFirst(user2.getId()),
status().isForbidden());
}

@Test
public void listClientTokensAsAnotherClient() throws Exception {
getTokenList("/oauth/token/list/client/" + client1.getClientId(),
tokensPerClient.getFirst(client3.getClientId()),
status().isForbidden());
}

@Test
public void listUserTokensAsAClient() throws Exception {
getTokenList("/oauth/token/list/user/" + user1.getId(),
clientWithUser1IdAsIdToken,
status().isForbidden());
}

@Test
public void listUserTokens() throws Exception {
List<RevocableToken> tokens = getTokenList("/oauth/token/list/user/" + user1.getId(),
tokensPerUser.getFirst(user1.getId()),
status().isOk());
List<String> tokenIds = getTokenIds(tokens);
validateTokens(tokenIds, tokensPerUser.get(user1.getId()));
}

@Test
public void listClientTokens() throws Exception {
List<RevocableToken> tokens = getTokenList("/oauth/token/list/client/" + client1.getClientId(),
tokensPerClient.getFirst(client1.getClientId()),
status().isOk());
List<String> tokenIds = getTokenIds(tokens);
validateTokens(tokenIds, tokensPerClient.get(client1.getClientId()));
}

protected List<RevocableToken> getTokenList(String urlTemplate,
String accessToken,
ResultMatcher status) throws Exception {
MvcResult result = getMockMvc()
.perform(
get(urlTemplate)
.header(AUTHORIZATION, "Bearer "+ accessToken)
)
.andExpect(status)
.andReturn();
if (result.getResponse().getStatus() == 200) {
String response = result.getResponse().getContentAsString();
return JsonUtils.readValue(response, new TypeReference<List<RevocableToken>>() {});
} else {
return Collections.emptyList();
}
}



}

0 comments on commit 7bceab7

Please sign in to comment.