/*******************************************************************************
* Cloud Foundry
* Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved.
*
* This product is licensed to you under the Apache License, Version 2.0 (the "License").
* You may not use this product except in compliance with the License.
*
* This product includes a number of subcomponents with
* separate copyright notices and license terms. Your use of these
* subcomponents is subject to the terms and conditions of the
* subcomponent's license, as noted in the LICENSE file.
*******************************************************************************/
package org.cloudfoundry.identity.uaa.oauth;
import org.cloudfoundry.identity.uaa.approval.Approval;
import org.cloudfoundry.identity.uaa.approval.Approval.ApprovalStatus;
import org.cloudfoundry.identity.uaa.approval.ApprovalStore;
import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationTestFactory;
import org.cloudfoundry.identity.uaa.constants.OriginKeys;
import org.cloudfoundry.identity.uaa.oauth.approval.InMemoryApprovalStore;
import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants;
import org.cloudfoundry.identity.uaa.oauth.token.Claims;
import org.cloudfoundry.identity.uaa.oauth.token.RevocableToken;
import org.cloudfoundry.identity.uaa.oauth.token.RevocableTokenProvisioning;
import org.cloudfoundry.identity.uaa.oauth.token.TokenConstants;
import org.cloudfoundry.identity.uaa.user.UaaAuthority;
import org.cloudfoundry.identity.uaa.user.UaaUser;
import org.cloudfoundry.identity.uaa.user.UaaUserDatabase;
import org.cloudfoundry.identity.uaa.zone.IdentityZone;
import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration;
import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder;
import org.cloudfoundry.identity.uaa.zone.IdentityZoneProvisioning;
import org.cloudfoundry.identity.uaa.zone.MultitenancyFixture;
import org.cloudfoundry.identity.uaa.zone.TokenPolicy;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.mockito.AdditionalMatchers;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InvalidScopeException;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.oauth2.provider.AuthorizationRequest;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
import org.springframework.security.oauth2.provider.client.InMemoryClientDetailsService;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@RunWith(Parameterized.class)
public class CheckTokenEndpointTests {
private IdentityZone defaultZone;
private CheckTokenEndpoint endpoint = new CheckTokenEndpoint();
private OAuth2Authentication authentication;
private OAuth2AccessToken accessToken;
private UaaTokenServices tokenServices = new UaaTokenServices();
private InMemoryClientDetailsService clientDetailsService = new InMemoryClientDetailsService();
private ApprovalStore approvalStore = new InMemoryApprovalStore();
private String userId = "12345";
private String userName = "olds";
private String userEmail = "olds@vmware.com";
private String signerKey;
private final boolean useOpaque;
private AuthorizationRequest authorizationRequest = null;
private UaaUser user;
private BaseClientDetails defaultClient;
private Map<String, ? extends ClientDetails> clientDetailsStore;
private List<GrantedAuthority> userAuthorities;
private final IdentityZoneProvisioning zoneProvisioning = mock(IdentityZoneProvisioning.class);
private RevocableTokenProvisioning tokenProvisioning;
private HashMap<String, RevocableToken> tokenMap;
@Rule
public ExpectedException exception = ExpectedException.none();
private MockHttpServletRequest request = new MockHttpServletRequest();
@Parameterized.Parameters
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][]{
{
"abc", false
},
{
"-----BEGIN RSA PRIVATE KEY-----\n" +
"MIIEowIBAAKCAQEA0m59l2u9iDnMbrXHfqkOrn2dVQ3vfBJqcDuFUK03d+1PZGbV\n" +
"lNCqnkpIJ8syFppW8ljnWweP7+LiWpRoz0I7fYb3d8TjhV86Y997Fl4DBrxgM6KT\n" +
"JOuE/uxnoDhZQ14LgOU2ckXjOzOdTsnGMKQBLCl0vpcXBtFLMaSbpv1ozi8h7DJy\n" +
"VZ6EnFQZUWGdgTMhDrmqevfx95U/16c5WBDOkqwIn7Glry9n9Suxygbf8g5AzpWc\n" +
"usZgDLIIZ7JTUldBb8qU2a0Dl4mvLZOn4wPojfj9Cw2QICsc5+Pwf21fP+hzf+1W\n" +
"SRHbnYv8uanRO0gZ8ekGaghM/2H6gqJbo2nIJwIDAQABAoIBAHPV9rSfzllq16op\n" +
"zoNetIJBC5aCcU4vJQBbA2wBrgMKUyXFpdSheQphgY7GP/BJTYtifRiS9RzsHAYY\n" +
"pAlTQEQ9Q4RekZAdd5r6rlsFrUzL7Xj/CVjNfQyHPhPocNqwrkxp4KrO5eL06qcw\n" +
"UzT7UtnoiCdSLI7IL0hIgJZP8J1uPNdXH+kkDEHE9xzU1q0vsi8nBLlim+ioYfEa\n" +
"Q/Q/ovMNviLKVs+ZUz+wayglDbCzsevuU+dh3Gmfc98DJw6n6iClpd4fDPqvhxUO\n" +
"BDeQT1mFeHxexDse/kH9nygxT6E4wlU1sw0TQANcT6sHReyHT1TlwnWlCQzoR3l2\n" +
"RmkzUsECgYEA8W/VIkfyYdUd5ri+yJ3iLdYF2tDvkiuzVmJeA5AK2KO1fNc7cSPK\n" +
"/sShHruc0WWZKWiR8Tp3d1XwA2rHMFHwC78RsTds+NpROs3Ya5sWd5mvmpEBbL+z\n" +
"cl3AU9NLHVvsZjogmgI9HIMTTl4ld7GDsFMt0qlCDztqG6W/iguQCx8CgYEA3x/j\n" +
"UkP45/PaFWd5c1DkWvmfmi9UxrIM7KeyBtDExGIkffwBMWFMCWm9DODw14bpnqAA\n" +
"jH5AhQCzVYaXIdp12b+1+eOOckYHwzjWOFpJ3nLgNK3wi067jVp0N0UfgV5nfYw/\n" +
"+YoHfYRCGsM91fowh7wLcyPPwmSAbQAKwbOZKfkCgYEAnccDdZ+m2iA3pitdIiVr\n" +
"RaDzuoeHx/IfBHjMD2/2ZpS1aZwOEGXfppZA5KCeXokSimj31rjqkWXrr4/8E6u4\n" +
"PzTiDvm1kPq60r7qi4eSKx6YD15rm/G7ByYVJbKTB+CmoDekToDgBt3xo+kKeyna\n" +
"cUQqUdyieunM8bxja4ca3ukCgYAfrDAhomJ30qa3eRvFYcs4msysH2HiXq30/g0I\n" +
"aKQ12FSjyZ0FvHEFuQvMAzZM8erByKarStSvzJyoXFWhyZgHE+6qDUJQOF6ruKq4\n" +
"DyEDQb1P3Q0TSVbYRunOWrKRM6xvJvSB4LUVfSvBDsv9TumKqwfZDVFVn9yXHHVq\n" +
"b6sjSQKBgDkcyYkAjpOHoG3XKMw06OE4OKpP9N6qU8uZOuA8ZF9ZyR7vFf4bCsKv\n" +
"QH+xY/4h8tgL+eASz5QWhj8DItm8wYGI5lKJr8f36jk0JLPUXODyDAeN6ekXY9LI\n" +
"fudkijw0dnh28LJqbkFF5wLNtATzyCfzjp+czrPMn9uqLNKt/iVD\n" +
"-----END RSA PRIVATE KEY-----\n", false
},
{
"signing_key_does_not_affect_opaque_token", true
},
});
}
private String alternateSignerKey = "-----BEGIN RSA PRIVATE KEY-----\n" +
"MIIEowIBAAKCAQEAsLZaEu+98J6neClnaCBy82xg9/DdVgLuO4fr0X9N/nmzaJ1L\n" +
"vBmhBdRA8zCLMHQXQmNko7vAZa2/L+A1zQL110puyB4YeInE5lJmGuAADVE2s2ep\n" +
"dritrHKVVVv2eCucKRMbQSbhXG2YX0QLp0T4z35Mw3Pa2Q1EDKVinL0o6deW4cX6\n" +
"AyUhmqanUphIplQKDrSGp4Lk14aPz/05/IJFA73y5qHJEIlmvuH6RZTZC3H1X1Xs\n" +
"pEo2dLOKt9rpvBo4tQkBxG6ejTIAfyu4+1429Zuvn5VCTkKHKgRmSgo6totBrBjR\n" +
"1Y7U+k8A+8YbZh3TS4t09i9E4jEmSt7lSUhTjQIDAQABAoIBAF8Rm5/4bt1W3Y4d\n" +
"6E3ytyUSt5BsewddCEHqvAm3TYSMgOLVTPtjZme2a0LqaNemfSTwSCJ2Tenl8aeW\n" +
"HhuvbgdnOfZbipq+s7mdtuTageyoNp+KM3d1n6nY81I66Xx5KchHSTBh9Hg/Vexa\n" +
"tVJGHv2yWyYD3EdNhcCv8T+V3L8Aon3a38y+manNNnM/jI9BfOR2reUn6LWGo8S1\n" +
"kUP9CA9vnM1MpLyGONHoVSzzIh/TTOR108FWlQr++ez1OB/sjA66Us2P72yFwRdW\n" +
"Wq2KSP75/g21x9nXInMhKHMmeO9Wm2QfwXZRDTr/vJ4jvfwLdUl3CMfdMl0bHPNG\n" +
"jB36/8ECgYEA2HNGM53fOoxqPzdYGkWNJosaWyyNvyNxIUO6Mb8vB8jQUWus5hIR\n" +
"GkL7XBSOGKGOpPN5nkZ79DArXdBZh+cXBGPQ9EGtE8H1E2wTM2l+3Ez3mzFoCISH\n" +
"w/fj9pxm/eA+9GPzSJ95j+6zzpMkjhXYQQcGiJc1Y1RUvfWhs0mhhzkCgYEA0QBJ\n" +
"C70YqkBFUjCrgtvCZocTc3b3Mh+bF9R/Kn/CTKnF//NjPEr9zMfefhbxhyI+L0U6\n" +
"Y7gZHVP32pFXQwnDrD3FmPY50RqTNz4c0ey9v1eEOgOl369HV+E66XuL1A0XUnI4\n" +
"wD9QpsoT/WCCy2UG7iruEmkvVUncRsVZUDqHOvUCgYEAzQk9ae3VpP+YMbP6eECE\n" +
"Oguw9scYqwQmyUz/1tn08hnPBCHMkdBxdQAYXZx3EmwP1L9y6HR6PNFYczDHbs6A\n" +
"Zj8rlAWWr02fGzvYYG5Bpuwd7Vv64X6xoPh0cIqtoTZITHdV4Oh4XdjPaRLHoPSe\n" +
"etLt5HvgLeyXra4987j/EzkCgYBCMSjxQs5Q/VH3Gdr38sm61wTeCMt5YHEqNu6f\n" +
"cx8CULKYwWioa8e9138rx/Bur/Wp2u8HLgMmOrXAz08nuCv0nQu7yh+9jgEZ+d3+\n" +
"zk+6DemexhD+qvCZcIfL8ojye8LrJam7mVHdwRpboPlLmY98VrRXuGB5To8pCs+i\n" +
"jSbPEQKBgEbrOYmJ4p2Esse55Bs+NP+HVuYEOBcKUVHxBG2ILMqA2GjQWO886siu\n" +
"Fg9454+Y1xN9DT768RIqkadKXR4r4Tnu8SesrqqqsRub8+RCZFe/JRxEetRBfE3g\n" +
"xEo7mKPEF+x8IhJuw6m3kMc4nvFg30KzUKgspAJGPo6kwTVNdT/W\n" +
"-----END RSA PRIVATE KEY-----\n";
public CheckTokenEndpointTests(String signerKey, boolean useOpaque) {
this.signerKey = signerKey;
this.useOpaque = useOpaque;
}
@Before
public void setUp() throws Exception {
setUp(useOpaque);
}
public void setUp(boolean opaque) throws URISyntaxException {
defaultZone = IdentityZone.getUaa();
userAuthorities = new ArrayList<>();
userAuthorities.add(new SimpleGrantedAuthority("read"));
userAuthorities.add(new SimpleGrantedAuthority("write"));
userAuthorities.add(new SimpleGrantedAuthority("zones.myzone.admin"));
userAuthorities.addAll(UaaAuthority.USER_AUTHORITIES);
user = new UaaUser(
userId,
userName,
"password",
userEmail,
userAuthorities,
"GivenName",
"FamilyName",
new Date(System.currentTimeMillis() - 2000),
new Date(System.currentTimeMillis() - 2000),
OriginKeys.UAA,
"externalId",
false,
IdentityZoneHolder.get().getId(),
"salt",
new Date(System.currentTimeMillis() - 2000));
mockUserDatabase(userId, user);
authorizationRequest = new AuthorizationRequest("client", Collections.singleton("read"));
authorizationRequest.setResourceIds(new HashSet<>(Arrays.asList("client", "scim")));
Map<String, String> requestParameters = new HashMap<>();
tokenProvisioning = mock(RevocableTokenProvisioning.class);
if (opaque) {
tokenMap = new HashMap<>();
when(tokenProvisioning.create(any())).thenAnswer(invocation -> {
RevocableToken token = (RevocableToken) invocation.getArguments()[0];
tokenMap.put(token.getTokenId(), token);
return token;
});
when(tokenProvisioning.retrieve(anyString())).thenAnswer(invocation -> {
String id = (String) invocation.getArguments()[0];
return tokenMap.get(id);
});
requestParameters.put(TokenConstants.REQUEST_TOKEN_FORMAT, TokenConstants.OPAQUE);
}
authorizationRequest.setRequestParameters(requestParameters);
authentication = new OAuth2Authentication(authorizationRequest.createOAuth2Request(),
UaaAuthenticationTestFactory.getAuthentication(userId, userName, "olds@vmware.com"));
configureDefaultZoneKeys(Collections.singletonMap("testKey", signerKey));
IdentityZoneHolder.set(defaultZone);
when(zoneProvisioning.retrieve("uaa")).thenReturn(defaultZone);
endpoint.setTokenServices(tokenServices);
Date oneSecondAgo = new Date(System.currentTimeMillis() - 1000);
Date thirtySecondsAhead = new Date(System.currentTimeMillis() + 30000);
approvalStore.addApproval(new Approval()
.setUserId(userId)
.setClientId("client")
.setScope("read")
.setExpiresAt(thirtySecondsAhead)
.setStatus(ApprovalStatus.APPROVED)
.setLastUpdatedAt(oneSecondAgo));
approvalStore.addApproval(new Approval()
.setUserId(userId)
.setClientId("client")
.setScope("write")
.setExpiresAt(thirtySecondsAhead)
.setStatus(ApprovalStatus.APPROVED)
.setLastUpdatedAt(oneSecondAgo));
tokenServices.setApprovalStore(approvalStore);
tokenServices.setTokenPolicy(IdentityZoneHolder.get().getConfig().getTokenPolicy());
defaultClient = new BaseClientDetails("client", "scim, cc", "read, write", "authorization_code, password", "scim.read, scim.write, cat.pet", "http://localhost:8080/uaa");
clientDetailsStore =
Collections.singletonMap(
"client",
defaultClient
);
clientDetailsService.setClientDetailsStore(clientDetailsStore);
tokenServices.setClientDetailsService(clientDetailsService);
tokenServices.setTokenProvisioning(tokenProvisioning);
tokenServices.setIssuer("http://localhost:8080/uaa");
tokenServices.afterPropertiesSet();
}
private void configureDefaultZoneKeys(Map<String, String> keys) {
IdentityZoneHolder.clear();
IdentityZoneHolder.setProvisioning(zoneProvisioning);
IdentityZoneConfiguration config = defaultZone.getConfig();
TokenPolicy tokenPolicy = config.getTokenPolicy();
tokenPolicy.setActiveKeyId(keys.keySet().stream().findFirst().get());
tokenPolicy.setAccessTokenValidity(43200);
tokenPolicy.setRefreshTokenValidity(2592000);
tokenPolicy.setKeys(keys);
}
private void mockUserDatabase(String userId, UaaUser user) {
UaaUserDatabase userDatabase = mock(UaaUserDatabase.class);
when(userDatabase.retrieveUserById(eq(userId))).thenReturn(user);
when(userDatabase.retrieveUserById(AdditionalMatchers.not(eq(userId)))).thenThrow(new UsernameNotFoundException("mock"));
tokenServices.setUserDatabase(userDatabase);
}
@Test
public void testClientWildcard() throws Exception {
BaseClientDetails theclient = new BaseClientDetails("client", "zones", "zones.*.admin", "authorization_code, password",
"scim.read, scim.write", "http://localhost:8080/uaa");
theclient.setAutoApproveScopes(Arrays.asList("zones.*.admin"));
Map<String, ? extends ClientDetails> clientDetailsStore = Collections.singletonMap("client", theclient);
clientDetailsService.setClientDetailsStore(clientDetailsStore);
tokenServices.setClientDetailsService(clientDetailsService);
authorizationRequest = new AuthorizationRequest("client", Collections.singleton("zones.myzone.admin"));
authorizationRequest.setResourceIds(new HashSet<>(Arrays.asList("client", "zones")));
authentication = new OAuth2Authentication(authorizationRequest.createOAuth2Request(),
UaaAuthenticationTestFactory.getAuthentication(userId, userName, "olds@vmware.com"));
setAccessToken(tokenServices.createAccessToken(authentication));
endpoint.checkToken(getAccessToken(), Collections.emptyList(), request);
}
private String getAccessToken() {
return accessToken.getValue();
}
public void setAccessToken(OAuth2AccessToken accessToken) {
this.accessToken = accessToken;
}
@Test(expected = InvalidTokenException.class)
public void testRejectInvalidIssuer() throws Exception {
setAccessToken(tokenServices.createAccessToken(authentication));
tokenServices.setIssuer("http://some.other.issuer");
endpoint.checkToken(getAccessToken(), Collections.emptyList(), request);
}
@Test()
public void testRejectInvalidVerifier() throws Exception {
try {
setAccessToken(tokenServices.createAccessToken(authentication));
configureDefaultZoneKeys(Collections.singletonMap("testKey", alternateSignerKey));
endpoint.checkToken(getAccessToken(), Collections.emptyList(), request);
fail("Token validation should fail");
} catch (InvalidTokenException ex) {
}
}
@Test(expected = TokenRevokedException.class)
public void testRejectUserSaltChange() throws Exception {
setAccessToken(tokenServices.createAccessToken(authentication));
user = new UaaUser(
userId,
userName,
"password",
userEmail,
userAuthorities,
"GivenName",
"FamilyName",
new Date(System.currentTimeMillis() - 2000),
new Date(System.currentTimeMillis() - 2000),
OriginKeys.UAA,
"externalId",
false,
IdentityZoneHolder.get().getId(),
"changedsalt",
new Date(System.currentTimeMillis() - 2000));
mockUserDatabase(userId, user);
endpoint.checkToken(getAccessToken(), Collections.emptyList(), request);
}
@Test(expected = TokenRevokedException.class)
public void testRejectUserUsernameChange() throws Exception {
setAccessToken(tokenServices.createAccessToken(authentication));
user = new UaaUser(
userId,
"newUsername@test.org",
"password",
userEmail,
userAuthorities,
"GivenName",
"FamilyName",
new Date(System.currentTimeMillis() - 2000),
new Date(System.currentTimeMillis() - 2000),
OriginKeys.UAA,
"externalId",
false,
IdentityZoneHolder.get().getId(),
"salt",
new Date(System.currentTimeMillis() - 2000));
mockUserDatabase(userId, user);
endpoint.checkToken(getAccessToken(), Collections.emptyList(), request);
}
@Test(expected = TokenRevokedException.class)
public void testRejectUserEmailChange() throws Exception {
setAccessToken(tokenServices.createAccessToken(authentication));
user = new UaaUser(
userId,
userName,
"password",
"newEmail@test.org",
userAuthorities,
"GivenName",
"FamilyName",
new Date(System.currentTimeMillis() - 2000),
new Date(System.currentTimeMillis() - 2000),
OriginKeys.UAA,
"externalId",
false,
IdentityZoneHolder.get().getId(),
"salt",
new Date(System.currentTimeMillis() - 2000));
mockUserDatabase(userId, user);
endpoint.checkToken(getAccessToken(), Collections.emptyList(), request);
}
@Test(expected = TokenRevokedException.class)
public void testRejectUserPasswordChange() throws Exception {
setAccessToken(tokenServices.createAccessToken(authentication));
user = new UaaUser(
userId,
userName,
"changedpassword",
userEmail,
userAuthorities,
"GivenName",
"FamilyName",
new Date(System.currentTimeMillis() - 2000),
new Date(System.currentTimeMillis() - 2000),
OriginKeys.UAA,
"externalId",
false,
IdentityZoneHolder.get().getId(),
"salt",
new Date(System.currentTimeMillis() - 2000));
mockUserDatabase(userId, user);
endpoint.checkToken(getAccessToken(), Collections.emptyList(), request);
}
@Test(expected = TokenRevokedException.class)
public void testRejectClientSaltChange() throws Exception {
setAccessToken(tokenServices.createAccessToken(authentication));
defaultClient.addAdditionalInformation(ClientConstants.TOKEN_SALT, "changedsalt");
endpoint.checkToken(getAccessToken(), Collections.emptyList(), request);
}
@Test(expected = TokenRevokedException.class)
public void testRejectClientPasswordChange() throws Exception {
setAccessToken(tokenServices.createAccessToken(authentication));
defaultClient.setClientSecret("changedsecret");
endpoint.checkToken(getAccessToken(), Collections.emptyList(), request);
}
private static String missingScopeMessage(String... scopes) {
return "Some requested scopes are missing: " + String.join(",", scopes);
}
@Test(expected = InvalidScopeException.class)
public void testValidateScopesNotPresent() throws Exception {
try {
authentication = new OAuth2Authentication(new AuthorizationRequest("client",
Collections.singleton("scim.read")).createOAuth2Request(), null);
setAccessToken(tokenServices.createAccessToken(authentication));
endpoint.checkToken(getAccessToken(), Collections.singletonList("scim.write"), request);
} catch (InvalidScopeException ex) {
assertEquals(missingScopeMessage("scim.write"), ex.getMessage());
throw ex;
}
}
@Test(expected = InvalidScopeException.class)
public void testValidateScopesMultipleNotPresent() throws Exception {
try {
authentication = new OAuth2Authentication(new AuthorizationRequest("client",
Collections.singletonList("cat.pet")).createOAuth2Request(), null);
setAccessToken(tokenServices.createAccessToken(authentication));
endpoint.checkToken(getAccessToken(), Arrays.asList("scim.write", "scim.read"), request);
} catch (InvalidScopeException ex) {
assertEquals(missingScopeMessage("scim.write", "scim.read"), ex.getMessage());
throw ex;
}
}
@Test
public void testValidateScopeSinglePresent() throws Exception {
authentication = new OAuth2Authentication(new AuthorizationRequest("client",
Collections.singleton("scim.read")).createOAuth2Request(), null);
setAccessToken(tokenServices.createAccessToken(authentication));
endpoint.checkToken(getAccessToken(), Collections.singletonList("scim.read"), request);
}
@Test
public void testValidateScopesMultiplePresent() throws Exception {
authentication = new OAuth2Authentication(new AuthorizationRequest("client",
Arrays.asList("scim.read", "scim.write")).createOAuth2Request(), null);
setAccessToken(tokenServices.createAccessToken(authentication));
endpoint.checkToken(getAccessToken(), Arrays.asList("scim.write", "scim.read"), request);
}
@Test(expected = InvalidScopeException.class)
public void testValidateScopesSomeNotPresent() throws Exception {
try {
authentication = new OAuth2Authentication(new AuthorizationRequest("client",
Arrays.asList("scim.read", "scim.write")).createOAuth2Request(), null);
setAccessToken(tokenServices.createAccessToken(authentication));
endpoint.checkToken(getAccessToken(), Arrays.asList("scim.read", "ponies.ride"), request);
} catch (InvalidScopeException ex) {
assertEquals(missingScopeMessage("ponies.ride"), ex.getMessage());
throw ex;
}
}
@Test(expected = InvalidTokenException.class)
public void revokingScopesFromUser_invalidatesToken() throws Exception {
setAccessToken(tokenServices.createAccessToken(authentication));
user = user.authorities(UaaAuthority.NONE_AUTHORITIES);
mockUserDatabase(userId, user);
endpoint.checkToken(getAccessToken(), Collections.emptyList(), request);
}
@Test(expected = InvalidTokenException.class)
public void revokingScopesFromClient_invalidatesToken() throws Exception {
setAccessToken(tokenServices.createAccessToken(authentication));
defaultClient = new BaseClientDetails("client", "scim, cc", "write", "authorization_code, password", "scim.read, scim.write", "http://localhost:8080/uaa");
clientDetailsStore = Collections.singletonMap(
"client",
defaultClient
);
clientDetailsService.setClientDetailsStore(clientDetailsStore);
endpoint.checkToken(getAccessToken(), Collections.emptyList(), request);
}
@Test(expected = InvalidTokenException.class)
public void revokingAuthoritiesFromClients_invalidatesToken() throws Exception {
defaultClient = new BaseClientDetails("client", "scim, cc", "write,read", "authorization_code, password", "scim.write", "http://localhost:8080/uaa");
clientDetailsStore = Collections.singletonMap(
"client",
defaultClient
);
clientDetailsService.setClientDetailsStore(clientDetailsStore);
mockUserDatabase(userId, user);
authentication = new OAuth2Authentication(new AuthorizationRequest("client",
Collections.singleton("scim.read")).createOAuth2Request(), null);
setAccessToken(tokenServices.createAccessToken(authentication));
endpoint.checkToken(getAccessToken(), Collections.emptyList(), request);
}
@Test
public void testSwitchVerifierKey() throws Exception {
try {
setAccessToken(tokenServices.createAccessToken(authentication));
configureDefaultZoneKeys(Collections.singletonMap("testKey", alternateSignerKey));
OAuth2AccessToken alternateToken = tokenServices.createAccessToken(authentication);
endpoint.checkToken(alternateToken.getValue(), Collections.emptyList(), request);
endpoint.checkToken(getAccessToken(), Collections.emptyList(), request);
fail("Token validation should fail");
} catch (InvalidTokenException ex) {
assertTrue("expected - rewrite to use a rule", true);
}
}
@Test
public void testClientAddSecret() throws Exception {
String firstClientSecret = "oldsecret";
String secondClientSecret = "newsecret";
defaultClient.setClientSecret(firstClientSecret);
setAccessToken(tokenServices.createAccessToken(authentication));
defaultClient.setClientSecret(firstClientSecret + " " + secondClientSecret);
endpoint.checkToken(getAccessToken(), Collections.emptyList(), request);
setAccessToken(tokenServices.createAccessToken(authentication));
endpoint.checkToken(getAccessToken(), Collections.emptyList(), request);
}
@Test
public void testClientDeleteSecret() throws Exception {
String firstClientSecret = "oldsecret";
String secondClientSecret = "newsecret";
defaultClient.setClientSecret(firstClientSecret + " " + secondClientSecret);
setAccessToken(tokenServices.createAccessToken(authentication));
endpoint.checkToken(getAccessToken(), Collections.emptyList(), request);
defaultClient.setClientSecret(secondClientSecret);
setAccessToken(tokenServices.createAccessToken(authentication));
endpoint.checkToken(getAccessToken(), Collections.emptyList(), request);
}
@Test
public void testUserIdInResult() throws Exception {
setAccessToken(tokenServices.createAccessToken(authentication));
Claims result = endpoint.checkToken(getAccessToken(), Collections.emptyList(), request);
assertEquals("olds", result.getUserName());
assertEquals("12345", result.getUserId());
assertNull("external attributes must not present", result.getExtAttr());
}
@Test
public void testExtAttrInResult() throws Exception {
tokenServices.setUaaTokenEnhancer(new TestTokenEnhancer());
setAccessToken(tokenServices.createAccessToken(authentication));
Claims result = endpoint.checkToken(getAccessToken(), Collections.emptyList(), request);
assertNotNull("external attributes not present", result.getExtAttr());
assertEquals("test", result.getExtAttr().get("purpose"));
}
@Test
public void testIssuerInResults() throws Exception {
tokenServices.setIssuer("http://some.other.issuer");
tokenServices.afterPropertiesSet();
setAccessToken(tokenServices.createAccessToken(authentication));
Claims result = endpoint.checkToken(getAccessToken(), Collections.emptyList(), request);
assertNotNull("iss field is not present", result.getIss());
assertEquals("http://some.other.issuer/oauth/token", result.getIss());
}
@Test
public void testIssuerInResultsInNonDefaultZone() throws Exception {
try {
IdentityZone zone = MultitenancyFixture.identityZone("id", "subdomain");
IdentityZoneHolder.set(zone);
tokenServices.setIssuer("http://some.other.issuer");
tokenServices.afterPropertiesSet();
setAccessToken(tokenServices.createAccessToken(authentication));
Claims result = endpoint.checkToken(getAccessToken(), Collections.emptyList(), request);
assertNotNull("iss field is not present", result.getIss());
assertEquals("http://subdomain.some.other.issuer/oauth/token", result.getIss());
} finally {
IdentityZoneHolder.clear();
}
}
@Test(expected = InvalidTokenException.class)
public void testZoneRejectsTokenSignedWithKeyFromOtherZone() throws Exception {
setAccessToken(tokenServices.createAccessToken(authentication));
try {
IdentityZone zone = MultitenancyFixture.identityZone("id", "subdomain");
zone.getConfig().getTokenPolicy().setKeys(Collections.singletonMap("testKey",
"-----BEGIN RSA PRIVATE KEY-----\n" +
"MIIBOgIBAAJAcEJMJ3ZT4GgdxipJe4uXvRQFfSpOneGjHfFTLjECMd0OkNtIWoIU\n" +
"8OisQRmhBDdXk2owne2SGJcqsVN/pd9pMQIDAQABAkAV/KY1xHNBLKNIQNgLnpel\n" +
"rNo2XabwPVVZc/66uVaYtVSwQjOxlo7mIzp77dpiM6o0kT4v3/9eyfKZte4uB/pR\n" +
"AiEAtF6MXrNeqEoJVCQ6LOUFgc1HtS1tqHBk6Fo3WO44ctMCIQCfVI3bTCY09F82\n" +
"TgIHtKdBtKzCGS56EzqbnbNodAoJawIhAJ25dCw31BV7sI6oo0qw9tDcDtGrKRI7\n" +
"PrJEedPFdQ1LAiEAklI6fHywUc1iayK0ppL3T1Y3mYE6t41VM3hePLzkQsUCIFjE\n" +
"NEUwGQmhVae7YpA8dgs0wFjsfdX15q+4wwWKu9oN\n" +
"-----END RSA PRIVATE KEY-----"));
IdentityZoneHolder.set(zone);
tokenServices.setTokenPolicy(zone.getConfig().getTokenPolicy());
tokenServices.setIssuer("http://some.other.issuer");
tokenServices.afterPropertiesSet();
endpoint.checkToken(getAccessToken(), Collections.emptyList(), request);
} finally {
IdentityZoneHolder.clear();
}
}
@Test
public void testZoneValidatesTokenSignedWithOwnKey() throws Exception {
try {
IdentityZone zone = MultitenancyFixture.identityZone("id", "subdomain");
zone.getConfig().getTokenPolicy().setKeys(Collections.singletonMap("zoneKey",
"-----BEGIN RSA PRIVATE KEY-----\n" +
"MIIBOgIBAAJAcEJMJ3ZT4GgdxipJe4uXvRQFfSpOneGjHfFTLjECMd0OkNtIWoIU\n" +
"8OisQRmhBDdXk2owne2SGJcqsVN/pd9pMQIDAQABAkAV/KY1xHNBLKNIQNgLnpel\n" +
"rNo2XabwPVVZc/66uVaYtVSwQjOxlo7mIzp77dpiM6o0kT4v3/9eyfKZte4uB/pR\n" +
"AiEAtF6MXrNeqEoJVCQ6LOUFgc1HtS1tqHBk6Fo3WO44ctMCIQCfVI3bTCY09F82\n" +
"TgIHtKdBtKzCGS56EzqbnbNodAoJawIhAJ25dCw31BV7sI6oo0qw9tDcDtGrKRI7\n" +
"PrJEedPFdQ1LAiEAklI6fHywUc1iayK0ppL3T1Y3mYE6t41VM3hePLzkQsUCIFjE\n" +
"NEUwGQmhVae7YpA8dgs0wFjsfdX15q+4wwWKu9oN\n" +
"-----END RSA PRIVATE KEY-----"));
IdentityZoneHolder.set(zone);
tokenServices.setIssuer("http://some.other.issuer");
tokenServices.afterPropertiesSet();
setAccessToken(tokenServices.createAccessToken(authentication));
endpoint.checkToken(getAccessToken(), Collections.emptyList(), request);
} finally {
IdentityZoneHolder.clear();
}
}
@Test
public void testZoneValidatesTokenSignedWithInactiveKey() throws Exception {
HashMap<String, String> keys = new HashMap<>();
keys.put("oldKey", "-----BEGIN RSA PRIVATE KEY-----\n" +
"MIIBOgIBAAJAcEJMJ3ZT4GgdxipJe4uXvRQFfSpOneGjHfFTLjECMd0OkNtIWoIU\n" +
"8OisQRmhBDdXk2owne2SGJcqsVN/pd9pMQIDAQABAkAV/KY1xHNBLKNIQNgLnpel\n" +
"rNo2XabwPVVZc/66uVaYtVSwQjOxlo7mIzp77dpiM6o0kT4v3/9eyfKZte4uB/pR\n" +
"AiEAtF6MXrNeqEoJVCQ6LOUFgc1HtS1tqHBk6Fo3WO44ctMCIQCfVI3bTCY09F82\n" +
"TgIHtKdBtKzCGS56EzqbnbNodAoJawIhAJ25dCw31BV7sI6oo0qw9tDcDtGrKRI7\n" +
"PrJEedPFdQ1LAiEAklI6fHywUc1iayK0ppL3T1Y3mYE6t41VM3hePLzkQsUCIFjE\n" +
"NEUwGQmhVae7YpA8dgs0wFjsfdX15q+4wwWKu9oN\n" +
"-----END RSA PRIVATE KEY-----");
configureDefaultZoneKeys(keys);
tokenServices.setIssuer("http://some.other.issuer");
tokenServices.afterPropertiesSet();
setAccessToken(tokenServices.createAccessToken(authentication));
keys.put("newKey", "nc978y78o3cg5i7env587geehn89mcehgc46");
configureDefaultZoneKeys(keys);
IdentityZoneHolder.get().getConfig().getTokenPolicy().setActiveKeyId("newKey");
endpoint.checkToken(getAccessToken(), Collections.emptyList(), request);
}
@Test
public void testZoneValidatesTokenSignedWithRemovedKey() throws Exception {
try {
HashMap<String, String> keys = new HashMap<>();
keys.put("oldKey", "-----BEGIN RSA PRIVATE KEY-----\n" +
"MIIBOgIBAAJAcEJMJ3ZT4GgdxipJe4uXvRQFfSpOneGjHfFTLjECMd0OkNtIWoIU\n" +
"8OisQRmhBDdXk2owne2SGJcqsVN/pd9pMQIDAQABAkAV/KY1xHNBLKNIQNgLnpel\n" +
"rNo2XabwPVVZc/66uVaYtVSwQjOxlo7mIzp77dpiM6o0kT4v3/9eyfKZte4uB/pR\n" +
"AiEAtF6MXrNeqEoJVCQ6LOUFgc1HtS1tqHBk6Fo3WO44ctMCIQCfVI3bTCY09F82\n" +
"TgIHtKdBtKzCGS56EzqbnbNodAoJawIhAJ25dCw31BV7sI6oo0qw9tDcDtGrKRI7\n" +
"PrJEedPFdQ1LAiEAklI6fHywUc1iayK0ppL3T1Y3mYE6t41VM3hePLzkQsUCIFjE\n" +
"NEUwGQmhVae7YpA8dgs0wFjsfdX15q+4wwWKu9oN\n" +
"-----END RSA PRIVATE KEY-----");
configureDefaultZoneKeys(keys);
tokenServices.setIssuer("http://some.other.issuer");
tokenServices.afterPropertiesSet();
setAccessToken(tokenServices.createAccessToken(authentication));
keys.remove("oldKey");
keys.put("newKey", "nc978y78o3cg5i7env587geehn89mcehgc46");
configureDefaultZoneKeys(keys);
IdentityZoneHolder.get().getConfig().getTokenPolicy().setActiveKeyId("newKey");
endpoint.checkToken(getAccessToken(), Collections.emptyList(), request);
fail("Token validation should fail");
} catch (InvalidTokenException ex) {
assertTrue("expected - rewrite to use a rule", true);
}
}
@Test(expected = InvalidTokenException.class)
public void testDefaultZoneRejectsTokenSignedWithOtherZoneKey() throws Exception {
IdentityZone zone = MultitenancyFixture.identityZone("id", "subdomain");
zone.getConfig().getTokenPolicy().setKeys(Collections.singletonMap("zoneKey",
"-----BEGIN RSA PRIVATE KEY-----\n" +
"MIIBOgIBAAJAcEJMJ3ZT4GgdxipJe4uXvRQFfSpOneGjHfFTLjECMd0OkNtIWoIU\n" +
"8OisQRmhBDdXk2owne2SGJcqsVN/pd9pMQIDAQABAkAV/KY1xHNBLKNIQNgLnpel\n" +
"rNo2XabwPVVZc/66uVaYtVSwQjOxlo7mIzp77dpiM6o0kT4v3/9eyfKZte4uB/pR\n" +
"AiEAtF6MXrNeqEoJVCQ6LOUFgc1HtS1tqHBk6Fo3WO44ctMCIQCfVI3bTCY09F82\n" +
"TgIHtKdBtKzCGS56EzqbnbNodAoJawIhAJ25dCw31BV7sI6oo0qw9tDcDtGrKRI7\n" +
"PrJEedPFdQ1LAiEAklI6fHywUc1iayK0ppL3T1Y3mYE6t41VM3hePLzkQsUCIFjE\n" +
"NEUwGQmhVae7YpA8dgs0wFjsfdX15q+4wwWKu9oN\n" +
"-----END RSA PRIVATE KEY-----"));
IdentityZoneHolder.set(zone);
tokenServices.setIssuer("http://some.other.issuer");
tokenServices.afterPropertiesSet();
setAccessToken(tokenServices.createAccessToken(authentication));
IdentityZoneHolder.clear();
Claims result = endpoint.checkToken(getAccessToken(), Collections.emptyList(), request);
}
@Test
public void testValidateAudParameter() throws Exception {
setAccessToken(tokenServices.createAccessToken(authentication));
Claims result = endpoint.checkToken(getAccessToken(), Collections.emptyList(), request);
List<String> aud = result.getAud();
assertEquals(2, aud.size());
assertTrue(aud.contains("scim"));
assertTrue(aud.contains("client"));
}
@Test
public void by_default_query_string_is_allowed() throws Exception {
setAccessToken(tokenServices.createAccessToken(authentication));
request.setQueryString("token="+getAccessToken());
endpoint.checkToken(getAccessToken(), Collections.emptyList(), request);
}
@Test
public void by_default_get_is_allowed() throws Exception {
setAccessToken(tokenServices.createAccessToken(authentication));
request.setQueryString("token="+getAccessToken());
request.setParameter("token", getAccessToken());
endpoint.checkToken(request);
}
@Test(expected = HttpRequestMethodNotSupportedException.class)
public void disable_query_string() throws Exception {
endpoint.setAllowQueryString(false);
by_default_query_string_is_allowed();
}
@Test(expected = HttpRequestMethodNotSupportedException.class)
public void disable_get_method() throws Exception {
endpoint.setAllowQueryString(false);
by_default_get_is_allowed();
}
@Test
public void testClientId() throws Exception {
setAccessToken(tokenServices.createAccessToken(authentication));
Claims result = endpoint.checkToken(getAccessToken(), Collections.emptyList(), request);
assertEquals("client", result.getAzp());
assertEquals("client", result.getCid());
assertEquals("client", result.getClientId());
}
@Test
public void validateAuthTime() throws Exception {
setAccessToken(tokenServices.createAccessToken(authentication));
Claims result = endpoint.checkToken(getAccessToken(), Collections.emptyList(), request);
assertNotNull(result.getAuthTime());
}
@Test(expected = TokenRevokedException.class)
public void revokedToken_ThrowsTokenRevokedException() throws Exception {
setUp();
when(tokenProvisioning.retrieve(anyString())).thenThrow(new EmptyResultDataAccessException(1));
IdentityZoneHolder.get().getConfig().getTokenPolicy().setJwtRevocable(true);
setAccessToken(tokenServices.createAccessToken(authentication));
endpoint.checkToken(getAccessToken(), Collections.emptyList(), request);
}
@Test
public void validateIssuedAtIsSmallerThanExpiredAt() throws Exception {
setAccessToken(tokenServices.createAccessToken(authentication));
Claims result = endpoint.checkToken(getAccessToken(), Collections.emptyList(), request);
Integer iat = result.getIat();
assertNotNull(iat);
Integer exp = result.getExp();
assertNotNull(exp);
assertTrue(iat < exp);
}
@Test
public void testEmailInResult() throws Exception {
setAccessToken(tokenServices.createAccessToken(authentication));
Claims result = endpoint.checkToken(getAccessToken(), Collections.emptyList(), request);
assertEquals("olds@vmware.com", result.getEmail());
}
@Test
public void testClientIdInResult() throws Exception {
setAccessToken(tokenServices.createAccessToken(authentication));
Claims result = endpoint.checkToken(getAccessToken(), Collections.emptyList(), request);
assertEquals("client", result.getClientId());
}
@Test
public void testClientIdInAud() throws Exception {
setAccessToken(tokenServices.createAccessToken(authentication));
Claims result = endpoint.checkToken(getAccessToken(), Collections.emptyList(), request);
assertTrue(result.getAud().contains("client"));
}
@Test
public void testExpiryResult() throws Exception {
setAccessToken(tokenServices.createAccessToken(authentication));
Claims result = endpoint.checkToken(getAccessToken(), Collections.emptyList(), request);
int expiresIn = 60 * 60 * 12;
assertTrue(expiresIn + System.currentTimeMillis() / 1000 >= result.getExp());
}
@Test
public void testUserAuthoritiesNotInResult() throws Exception {
setAccessToken(tokenServices.createAccessToken(authentication));
Claims result = endpoint.checkToken(getAccessToken(), Collections.emptyList(), request);
assertEquals(null, result.getAuthorities());
}
@Test
public void testClientAuthoritiesNotInResult() throws Exception {
setAccessToken(tokenServices.createAccessToken(authentication));
Claims result = endpoint.checkToken(getAccessToken(), Collections.emptyList(), request);
assertEquals(null, result.getAuthorities());
}
@Test(expected = InvalidTokenException.class)
public void testExpiredToken() throws Exception {
BaseClientDetails clientDetails = new BaseClientDetails("client", "scim, cc", "read, write",
"authorization_code, password", "scim.read, scim.write", "http://localhost:8080/uaa");
clientDetails.setAccessTokenValiditySeconds(1);
Map<String, ? extends ClientDetails> clientDetailsStore = Collections.singletonMap("client", clientDetails);
clientDetailsService.setClientDetailsStore(clientDetailsStore);
tokenServices.setClientDetailsService(clientDetailsService);
setAccessToken(tokenServices.createAccessToken(authentication));
Thread.sleep(1000);
endpoint.checkToken(getAccessToken(), Collections.emptyList(), request);
}
@Test(expected = InvalidTokenException.class)
public void testDeniedApprovals() throws Exception {
setAccessToken(tokenServices.createAccessToken(authentication));
Date oneSecondAgo = new Date(System.currentTimeMillis() - 1000);
Date thirtySecondsAhead = new Date(System.currentTimeMillis() + 30000);
approvalStore.revokeApproval(new Approval()
.setUserId(userId)
.setClientId("client")
.setScope("read")
.setExpiresAt(thirtySecondsAhead)
.setStatus(ApprovalStatus.APPROVED)
.setLastUpdatedAt(oneSecondAgo));
approvalStore.addApproval(new Approval()
.setUserId(userId)
.setClientId("client")
.setScope("read")
.setExpiresAt(thirtySecondsAhead)
.setStatus(ApprovalStatus.DENIED)
.setLastUpdatedAt(oneSecondAgo));
Claims result = endpoint.checkToken(getAccessToken(), Collections.emptyList(), request);
assertEquals(null, result.getAuthorities());
}
@Test(expected = InvalidTokenException.class)
public void testExpiredApprovals() throws Exception {
setAccessToken(tokenServices.createAccessToken(authentication));
approvalStore.revokeApproval(new Approval()
.setUserId(userId)
.setClientId("client")
.setScope("read")
.setExpiresAt(new Date())
.setStatus(ApprovalStatus.APPROVED));
approvalStore.addApproval(new Approval()
.setUserId(userId)
.setClientId("client")
.setScope("read")
.setExpiresAt(new Date())
.setStatus(ApprovalStatus.APPROVED));
Claims result = endpoint.checkToken(getAccessToken(), Collections.emptyList(), request);
assertEquals(null, result.getAuthorities());
}
@Test
public void testClientOnly() throws Exception {
authentication = new OAuth2Authentication(new AuthorizationRequest("client",
Collections.singleton("scim.read")).createOAuth2Request(), null);
setAccessToken(tokenServices.createAccessToken(authentication));
Claims result = endpoint.checkToken(getAccessToken(), Collections.emptyList(), request);
assertEquals("client", result.getClientId());
assertNull(result.getUserId());
}
}