/* * Copyright 2006-2011 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ package org.springframework.security.oauth2.client.token; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.mockito.Matchers.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import java.util.Arrays; import java.util.Calendar; import java.util.Collections; import java.util.Date; import org.junit.After; import org.junit.Test; import org.mockito.Mockito; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.authentication.InsufficientAuthenticationException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.client.resource.BaseOAuth2ProtectedResourceDetails; import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails; import org.springframework.security.oauth2.client.resource.UserRedirectRequiredException; import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails; import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider; import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails; import org.springframework.security.oauth2.common.DefaultExpiringOAuth2RefreshToken; import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; import org.springframework.security.oauth2.common.DefaultOAuth2RefreshToken; import org.springframework.security.oauth2.common.ExpiringOAuth2RefreshToken; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.common.OAuth2RefreshToken; import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; /** * @author Dave Syer * */ public class AccessTokenProviderChainTests { private BaseOAuth2ProtectedResourceDetails resource; private DefaultOAuth2AccessToken accessToken = new DefaultOAuth2AccessToken("FOO"); private DefaultOAuth2AccessToken refreshedToken = new DefaultOAuth2AccessToken("BAR"); private UsernamePasswordAuthenticationToken user = new UsernamePasswordAuthenticationToken("foo", "bar", Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"))); private ClientTokenServices clientTokenServices = Mockito.mock(ClientTokenServices.class); public AccessTokenProviderChainTests() { resource = new BaseOAuth2ProtectedResourceDetails(); resource.setId("resource"); } @After public void close() { SecurityContextHolder.clearContext(); } @Test public void testSunnyDay() throws Exception { AccessTokenProviderChain chain = new AccessTokenProviderChain(Arrays.asList(new StubAccessTokenProvider())); AccessTokenRequest request = new DefaultAccessTokenRequest(); SecurityContextHolder.getContext().setAuthentication(user); OAuth2AccessToken token = chain.obtainAccessToken(resource, request); assertNotNull(token); } @Test public void testSunnyDayWithTokenServicesGet() throws Exception { AccessTokenProviderChain chain = new AccessTokenProviderChain(Collections.<AccessTokenProvider> emptyList()); when(clientTokenServices.getAccessToken(resource, user)).thenReturn(accessToken); chain.setClientTokenServices(clientTokenServices); AccessTokenRequest request = new DefaultAccessTokenRequest(); SecurityContextHolder.getContext().setAuthentication(user); OAuth2AccessToken token = chain.obtainAccessToken(resource, request); assertEquals(accessToken, token); Mockito.verify(clientTokenServices).saveAccessToken(resource, user, token); } @Test public void testSunnyDayWithTokenServicesSave() throws Exception { AccessTokenProviderChain chain = new AccessTokenProviderChain(Arrays.asList(new StubAccessTokenProvider())); chain.setClientTokenServices(clientTokenServices); AccessTokenRequest request = new DefaultAccessTokenRequest(); SecurityContextHolder.getContext().setAuthentication(user); OAuth2AccessToken token = chain.obtainAccessToken(resource, request); assertNotNull(token); Mockito.verify(clientTokenServices).saveAccessToken(resource, user, token); } @Test public void testSunnyDayClientCredentialsWithTokenServicesSave() throws Exception { AccessTokenProviderChain chain = new AccessTokenProviderChain(Arrays.asList(new StubAccessTokenProvider())); chain.setClientTokenServices(clientTokenServices); AccessTokenRequest request = new DefaultAccessTokenRequest(); resource = new ClientCredentialsResourceDetails(); resource.setId("resource"); OAuth2AccessToken token = chain.obtainAccessToken(resource, request); assertNotNull(token); Mockito.verify(clientTokenServices).saveAccessToken(resource, null, token); } @Test public void testSunnyDayWithExpiredToken() throws Exception { AccessTokenProviderChain chain = new AccessTokenProviderChain(Arrays.asList(new StubAccessTokenProvider())); accessToken.setExpiration(new Date(System.currentTimeMillis() - 1000)); AccessTokenRequest request = new DefaultAccessTokenRequest(); request.setExistingToken(accessToken); SecurityContextHolder.getContext().setAuthentication(user); OAuth2AccessToken token = chain.obtainAccessToken(resource, request); assertNotNull(token); } @Test public void testSunnyDayWithExpiredTokenAndTokenServices() throws Exception { AccessTokenProviderChain chain = new AccessTokenProviderChain(Arrays.asList(new StubAccessTokenProvider())); chain.setClientTokenServices(clientTokenServices); accessToken.setExpiration(new Date(System.currentTimeMillis() - 1000)); when(clientTokenServices.getAccessToken(resource, user)).thenReturn(accessToken); AccessTokenRequest request = new DefaultAccessTokenRequest(); SecurityContextHolder.getContext().setAuthentication(user); OAuth2AccessToken token = chain.obtainAccessToken(resource, request); assertNotNull(token); Mockito.verify(clientTokenServices).removeAccessToken(resource, user); Mockito.verify(clientTokenServices).saveAccessToken(resource, user, token); } @Test public void testSunnyDayWIthExpiredTokenAndValidRefreshToken() throws Exception { AccessTokenProviderChain chain = new AccessTokenProviderChain(Arrays.asList(new StubAccessTokenProvider())); accessToken.setExpiration(new Date(System.currentTimeMillis() - 1000)); accessToken.setRefreshToken(new DefaultOAuth2RefreshToken("EXP")); AccessTokenRequest request = new DefaultAccessTokenRequest(); request.setExistingToken(accessToken); SecurityContextHolder.getContext().setAuthentication(user); OAuth2AccessToken token = chain.obtainAccessToken(resource, request); assertNotNull(token); } @Test(expected = InvalidTokenException.class) public void testSunnyDayWIthExpiredTokenAndExpiredRefreshToken() throws Exception { AccessTokenProviderChain chain = new AccessTokenProviderChain(Arrays.asList(new StubAccessTokenProvider())); accessToken.setExpiration(new Date(System.currentTimeMillis() - 1000)); DefaultOAuth2RefreshToken refreshToken = new DefaultExpiringOAuth2RefreshToken("EXP", new Date(System.currentTimeMillis() - 1000)); accessToken.setRefreshToken(refreshToken); AccessTokenRequest request = new DefaultAccessTokenRequest(); request.setExistingToken(accessToken); SecurityContextHolder.getContext().setAuthentication(user); OAuth2AccessToken token = chain.obtainAccessToken(resource, request); assertNotNull(token); } @Test public void testMissingSecurityContext() throws Exception { AccessTokenProviderChain chain = new AccessTokenProviderChain(Arrays.asList(new StubAccessTokenProvider())); AccessTokenRequest request = new DefaultAccessTokenRequest(); OAuth2AccessToken token = chain.obtainAccessToken(resource, request); assertNotNull(token); // If there is no authentication to store it with a token is still acquired if // possible } @Test(expected = InsufficientAuthenticationException.class) public void testAnonymousUser() throws Exception { AccessTokenProviderChain chain = new AccessTokenProviderChain(Arrays.asList(new StubAccessTokenProvider())); SecurityContextHolder.getContext() .setAuthentication(new AnonymousAuthenticationToken("foo", "bar", user.getAuthorities())); AccessTokenRequest request = new DefaultAccessTokenRequest(); OAuth2AccessToken token = chain.obtainAccessToken(resource, request); assertNotNull(token); } @Test(expected = UserRedirectRequiredException.class) public void testRequiresAuthenticationButRedirected() throws Exception { final AccessTokenRequest request = new DefaultAccessTokenRequest(); AccessTokenProviderChain chain = new AccessTokenProviderChain(Arrays.asList(new StubAccessTokenProvider() { @Override public OAuth2AccessToken obtainAccessToken(OAuth2ProtectedResourceDetails details, AccessTokenRequest parameters) throws UserRedirectRequiredException, AccessDeniedException { throw new UserRedirectRequiredException("redirect test", request.toSingleValueMap()); } })); OAuth2AccessToken token = chain.obtainAccessToken(resource, request); assertNotNull(token); } @Test public void testRefreshAccessTokenReplacingNullValue() throws Exception { DefaultOAuth2AccessToken accessToken = getExpiredToken(); DefaultOAuth2AccessToken refreshedAccessToken = new DefaultOAuth2AccessToken("refreshed-access-token"); AccessTokenProviderChain chain = getTokenProvider(accessToken, refreshedAccessToken); SecurityContextHolder.getContext().setAuthentication(user); // Obtain a new Access Token AuthorizationCodeResourceDetails resource = new AuthorizationCodeResourceDetails(); AccessTokenRequest request = new DefaultAccessTokenRequest(); OAuth2AccessToken newAccessToken = chain.refreshAccessToken(resource, accessToken.getRefreshToken(), request); // gh-712 assertEquals(newAccessToken.getRefreshToken(), accessToken.getRefreshToken()); } @Test public void testRefreshAccessTokenKeepingOldValue() throws Exception { DefaultOAuth2AccessToken accessToken = getExpiredToken(); DefaultOAuth2AccessToken refreshedAccessToken = new DefaultOAuth2AccessToken("refreshed-access-token"); refreshedAccessToken.setRefreshToken(new DefaultOAuth2RefreshToken("other-refresh-token")); AccessTokenProviderChain chain = getTokenProvider(accessToken, refreshedAccessToken); SecurityContextHolder.getContext().setAuthentication(user); // Obtain a new Access Token AuthorizationCodeResourceDetails resource = new AuthorizationCodeResourceDetails(); AccessTokenRequest request = new DefaultAccessTokenRequest(); OAuth2AccessToken newAccessToken = chain.refreshAccessToken(resource, accessToken.getRefreshToken(), request); // gh-816 assertEquals(newAccessToken.getRefreshToken(), refreshedAccessToken.getRefreshToken()); } private DefaultOAuth2AccessToken getExpiredToken() { Calendar tokenExpiry = Calendar.getInstance(); DefaultOAuth2AccessToken accessToken = new DefaultOAuth2AccessToken("access-token"); accessToken.setExpiration(tokenExpiry.getTime()); accessToken.setRefreshToken(new DefaultOAuth2RefreshToken("refresh-token")); return accessToken; } // gh-712 @Test public void testRefreshAccessTokenTwicePreserveRefreshToken() throws Exception { DefaultOAuth2AccessToken accessToken = getExpiredToken(); DefaultOAuth2AccessToken expectedRefreshedAccessToken = new DefaultOAuth2AccessToken("refreshed-access-token"); expectedRefreshedAccessToken.setExpiration(accessToken.getExpiration()); AccessTokenProviderChain chain = getTokenProvider(accessToken, expectedRefreshedAccessToken); SecurityContextHolder.getContext().setAuthentication(user); // Obtain a new Access Token AuthorizationCodeResourceDetails resource = new AuthorizationCodeResourceDetails(); AccessTokenRequest request = new DefaultAccessTokenRequest(); OAuth2AccessToken tokenResult = chain.obtainAccessToken(resource, request); assertEquals(accessToken, tokenResult); // Obtain the 1st Refreshed Access Token Calendar tokenExpiry = Calendar.getInstance(); tokenExpiry.setTime(tokenResult.getExpiration()); tokenExpiry.add(Calendar.MINUTE, -1); DefaultOAuth2AccessToken.class.cast(tokenResult).setExpiration(tokenExpiry.getTime()); // Expire request = new DefaultAccessTokenRequest(); request.setExistingToken(tokenResult); tokenResult = chain.obtainAccessToken(resource, request); assertEquals(expectedRefreshedAccessToken, tokenResult); // Obtain the 2nd Refreshed Access Token tokenExpiry.setTime(tokenResult.getExpiration()); tokenExpiry.add(Calendar.MINUTE, -1); DefaultOAuth2AccessToken.class.cast(tokenResult).setExpiration(tokenExpiry.getTime()); // Expire request = new DefaultAccessTokenRequest(); request.setExistingToken(tokenResult); tokenResult = chain.obtainAccessToken(resource, request); assertEquals(expectedRefreshedAccessToken, tokenResult); } private AccessTokenProviderChain getTokenProvider(DefaultOAuth2AccessToken accessToken, DefaultOAuth2AccessToken refreshedAccessToken) { AccessTokenProvider accessTokenProvider = new AuthorizationCodeAccessTokenProvider(); accessTokenProvider = spy(accessTokenProvider); doReturn(accessToken).when(accessTokenProvider).obtainAccessToken(any(OAuth2ProtectedResourceDetails.class), any(AccessTokenRequest.class)); doReturn(refreshedAccessToken).when(accessTokenProvider).refreshAccessToken( any(OAuth2ProtectedResourceDetails.class), any(OAuth2RefreshToken.class), any(AccessTokenRequest.class)); AccessTokenProviderChain chain = new AccessTokenProviderChain(Arrays.asList(accessTokenProvider)); return chain; } private class StubAccessTokenProvider implements AccessTokenProvider { public OAuth2AccessToken obtainAccessToken(OAuth2ProtectedResourceDetails details, AccessTokenRequest parameters) throws UserRedirectRequiredException, AccessDeniedException { return accessToken; } public boolean supportsRefresh(OAuth2ProtectedResourceDetails resource) { return true; } public OAuth2AccessToken refreshAccessToken(OAuth2ProtectedResourceDetails resource, OAuth2RefreshToken refreshToken, AccessTokenRequest request) throws UserRedirectRequiredException { if (refreshToken instanceof ExpiringOAuth2RefreshToken) { if (((ExpiringOAuth2RefreshToken) refreshToken).getExpiration().getTime() < System .currentTimeMillis()) { // this is what a real provider would do (re-throw a remote exception) throw new InvalidTokenException("Expired refresh token"); } } return refreshedToken; } public boolean supportsResource(OAuth2ProtectedResourceDetails resource) { return true; } } }