diff --git a/openid-connect-common/src/main/java/org/mitre/oauth2/service/IntrospectionResultAssembler.java b/openid-connect-common/src/main/java/org/mitre/oauth2/service/IntrospectionResultAssembler.java new file mode 100644 index 000000000..2a81aa35f --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/oauth2/service/IntrospectionResultAssembler.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * Copyright 2014 The MITRE Corporation + * and the MIT Kerberos and Internet Trust Consortium + * + * 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.mitre.oauth2.service; + +import org.mitre.oauth2.model.OAuth2AccessTokenEntity; +import org.mitre.oauth2.model.OAuth2RefreshTokenEntity; +import org.mitre.openid.connect.model.UserInfo; + +import java.util.Map; + +/** + * Strategy interface for assembling a token introspection result. + */ +public interface IntrospectionResultAssembler { + + /** + * Assemble a token introspection result from the given access token and user info. + * + * @param accessToken the access token + * @param userInfo the user info + * @return the token introspection result + */ + Map assembleFrom(OAuth2AccessTokenEntity accessToken, UserInfo userInfo); + + /** + * Assemble a token introspection result from the given refresh token and user info. + * + * @param refreshToken the refresh token + * @param userInfo the user info + * @return the token introspection result + */ + Map assembleFrom(OAuth2RefreshTokenEntity refreshToken, UserInfo userInfo); + +} diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultIntrospectionResultAssembler.java b/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultIntrospectionResultAssembler.java new file mode 100644 index 000000000..abf8c126c --- /dev/null +++ b/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultIntrospectionResultAssembler.java @@ -0,0 +1,96 @@ +/******************************************************************************* + * Copyright 2014 The MITRE Corporation + * and the MIT Kerberos and Internet Trust Consortium + * + * 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.mitre.oauth2.service.impl; + +import com.google.common.base.Joiner; +import org.mitre.oauth2.model.OAuth2AccessTokenEntity; +import org.mitre.oauth2.model.OAuth2RefreshTokenEntity; +import org.mitre.oauth2.service.IntrospectionResultAssembler; +import org.mitre.openid.connect.model.UserInfo; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.stereotype.Service; + +import java.util.Map; + +import static com.google.common.collect.Maps.newLinkedHashMap; + +/** + * Default implementation of the {@link IntrospectionResultAssembler} interface. + */ +@Service +public class DefaultIntrospectionResultAssembler implements IntrospectionResultAssembler { + + @Override + public Map assembleFrom(OAuth2AccessTokenEntity accessToken, UserInfo userInfo) { + + Map result = newLinkedHashMap(); + OAuth2Authentication authentication = accessToken.getAuthenticationHolder().getAuthentication(); + + result.put("active", true); + + result.put("scope", Joiner.on(" ").join(accessToken.getScope())); + + if (accessToken.getExpiration() != null) { + result.put("exp", accessToken.getExpiration()); + } + + if (userInfo != null) { + // if we have a UserInfo, use that for the subject + result.put("sub", userInfo.getSub()); + } else { + // otherwise, use the authentication's username + result.put("sub", authentication.getName()); + } + + result.put("user_id", authentication.getName()); + + result.put("client_id", authentication.getOAuth2Request().getClientId()); + + result.put("token_type", accessToken.getTokenType()); + + return result; + } + + @Override + public Map assembleFrom(OAuth2RefreshTokenEntity refreshToken, UserInfo userInfo) { + + Map result = newLinkedHashMap(); + OAuth2Authentication authentication = refreshToken.getAuthenticationHolder().getAuthentication(); + + result.put("active", true); + + result.put("scope", Joiner.on(" ").join(authentication.getOAuth2Request().getScope())); + + if (refreshToken.getExpiration() != null) { + result.put("exp", refreshToken.getExpiration()); + } + + if (userInfo != null) { + // if we have a UserInfo, use that for the subject + result.put("sub", userInfo.getSub()); + } else { + // otherwise, use the authentication's username + result.put("sub", authentication.getName()); + } + + result.put("user_id", authentication.getName()); + + result.put("client_id", authentication.getOAuth2Request().getClientId()); + + return result; + } +} diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/view/TokenIntrospectionView.java b/openid-connect-server/src/main/java/org/mitre/oauth2/view/TokenIntrospectionView.java deleted file mode 100644 index f307cfcf7..000000000 --- a/openid-connect-server/src/main/java/org/mitre/oauth2/view/TokenIntrospectionView.java +++ /dev/null @@ -1,143 +0,0 @@ -/******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * - * 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.mitre.oauth2.view; - -import java.io.IOException; -import java.io.Writer; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Map; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.swing.text.DateFormatter; - -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; -import org.mitre.oauth2.model.OAuth2RefreshTokenEntity; -import org.mitre.openid.connect.model.UserInfo; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Component; -import org.springframework.web.servlet.view.AbstractView; - -import com.google.common.base.Joiner; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonObject; - -@Component(TokenIntrospectionView.VIEWNAME) -public class TokenIntrospectionView extends AbstractView { - - public static final String VIEWNAME = "tokenIntrospection"; - - private static Logger logger = LoggerFactory.getLogger(TokenIntrospectionView.class); - - private static DateFormatter isoDateFormatter = new DateFormatter(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")); - - private Gson gson = new GsonBuilder().create(); - - @Override - protected void renderMergedOutputModel(Map model, HttpServletRequest request, HttpServletResponse response) { - - response.setContentType("application/json"); - - Writer out; - - try { - - out = response.getWriter(); - UserInfo user = (UserInfo)model.get("user"); - Object obj = model.get("token"); - if (obj instanceof OAuth2AccessTokenEntity) { - gson.toJson(renderAccessToken((OAuth2AccessTokenEntity)obj, user), out); - } else if (obj instanceof OAuth2RefreshTokenEntity) { - gson.toJson(renderRefreshToken((OAuth2RefreshTokenEntity)obj, user), out); - } else { - throw new IOException("Couldn't find a valid entity to render"); - } - - } catch (IOException e) { - - logger.error("IOException occurred in TokenIntrospectionView.java: ", e); - - } - - } - - private JsonObject renderAccessToken(OAuth2AccessTokenEntity src, UserInfo user) { - JsonObject token = new JsonObject(); - - token.addProperty("active", true); - - token.addProperty("scope", Joiner.on(" ").join(src.getScope())); - - if (src.getExpiration() != null) { - try { - token.addProperty("exp", isoDateFormatter.valueToString(src.getExpiration())); - } catch (ParseException e) { - logger.error("Problem formatting expiration date: " + src.getExpiration(), e); - } - } - - if (user != null) { - // if we have a UserInfo, use that for the subject - token.addProperty("sub", user.getSub()); - token.addProperty("user_id", src.getAuthenticationHolder().getAuthentication().getName()); - } else { - // otherwise, use the authentication's username - token.addProperty("sub", src.getAuthenticationHolder().getAuthentication().getName()); - token.addProperty("user_id", src.getAuthenticationHolder().getAuthentication().getName()); - } - - token.addProperty("client_id", src.getAuthenticationHolder().getAuthentication().getOAuth2Request().getClientId()); - - token.addProperty("token_type", src.getTokenType()); - - return token; - } - - private JsonObject renderRefreshToken(OAuth2RefreshTokenEntity src, UserInfo user) { - JsonObject token = new JsonObject(); - - token.addProperty("active", true); - - token.addProperty("scope", Joiner.on(" ").join(src.getAuthenticationHolder().getAuthentication().getOAuth2Request().getScope())); - - if (src.getExpiration() != null) { - try { - token.addProperty("exp", isoDateFormatter.valueToString(src.getExpiration())); - } catch (ParseException e) { - logger.error("Problem formatting expiration date: " + src.getExpiration(), e); - } - } - - if (user != null) { - // if we have a UserInfo, use that for the subject - token.addProperty("sub", user.getSub()); - token.addProperty("user_id", src.getAuthenticationHolder().getAuthentication().getName()); - } else { - // otherwise, use the authentication's username - token.addProperty("sub", src.getAuthenticationHolder().getAuthentication().getName()); - token.addProperty("user_id", src.getAuthenticationHolder().getAuthentication().getName()); - } - - token.addProperty("client_id", src.getAuthenticationHolder().getAuthentication().getOAuth2Request().getClientId()); - - return token; - } - -} diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/web/IntrospectionEndpoint.java b/openid-connect-server/src/main/java/org/mitre/oauth2/web/IntrospectionEndpoint.java index 3547f3eab..2cb663620 100644 --- a/openid-connect-server/src/main/java/org/mitre/oauth2/web/IntrospectionEndpoint.java +++ b/openid-connect-server/src/main/java/org/mitre/oauth2/web/IntrospectionEndpoint.java @@ -1,13 +1,13 @@ /******************************************************************************* * Copyright 2014 The MITRE Corporation * and the MIT Kerberos and Internet Trust Consortium - * + * * 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. @@ -16,17 +16,15 @@ ******************************************************************************/ package org.mitre.oauth2.web; -import java.security.Principal; -import java.util.Map; -import java.util.Set; - +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableMap; import org.mitre.oauth2.model.ClientDetailsEntity; import org.mitre.oauth2.model.OAuth2AccessTokenEntity; import org.mitre.oauth2.model.OAuth2RefreshTokenEntity; import org.mitre.oauth2.service.ClientDetailsEntityService; import org.mitre.oauth2.service.IntrospectionAuthorizer; +import org.mitre.oauth2.service.IntrospectionResultAssembler; import org.mitre.oauth2.service.OAuth2TokenEntityService; -import org.mitre.oauth2.view.TokenIntrospectionView; import org.mitre.openid.connect.model.UserInfo; import org.mitre.openid.connect.service.UserInfoService; import org.mitre.openid.connect.view.HttpCodeView; @@ -42,8 +40,9 @@ import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; -import com.google.common.base.Strings; -import com.google.common.collect.ImmutableMap; +import java.security.Principal; +import java.util.Map; +import java.util.Set; @Controller public class IntrospectionEndpoint { @@ -56,7 +55,10 @@ public class IntrospectionEndpoint { @Autowired private IntrospectionAuthorizer introspectionAuthorizer; - + + @Autowired + private IntrospectionResultAssembler introspectionResultAssembler; + @Autowired private UserInfoService userInfoService; @@ -84,72 +86,64 @@ public class IntrospectionEndpoint { return JsonEntityView.VIEWNAME; } - // clientID is the principal name in the authentication - String clientId = p.getName(); - ClientDetailsEntity authClient = clientService.loadClientByClientId(clientId); - - ClientDetailsEntity tokenClient = null; - Set scopes = null; - Object token = null; - UserInfo user = null; + OAuth2AccessTokenEntity accessToken = null; + OAuth2RefreshTokenEntity refreshToken = null; + ClientDetailsEntity tokenClient; + Set scopes; + UserInfo user; try { // check access tokens first (includes ID tokens) - OAuth2AccessTokenEntity access = tokenServices.readAccessToken(tokenValue); + accessToken = tokenServices.readAccessToken(tokenValue); - tokenClient = access.getClient(); - scopes = access.getScope(); + tokenClient = accessToken.getClient(); + scopes = accessToken.getScope(); - token = access; + user = userInfoService.getByUsernameAndClientId(accessToken.getAuthenticationHolder().getAuthentication().getName(), tokenClient.getClientId()); - user = userInfoService.getByUsernameAndClientId(access.getAuthenticationHolder().getAuthentication().getName(), tokenClient.getClientId()); - } catch (InvalidTokenException e) { - logger.error("Verify failed; Invalid access token. Checking refresh token.", e); + logger.info("Verify failed; Invalid access token. Checking refresh token."); try { // check refresh tokens next - OAuth2RefreshTokenEntity refresh = tokenServices.getRefreshToken(tokenValue); + refreshToken = tokenServices.getRefreshToken(tokenValue); - tokenClient = refresh.getClient(); - scopes = refresh.getAuthenticationHolder().getAuthentication().getOAuth2Request().getScope(); + tokenClient = refreshToken.getClient(); + scopes = refreshToken.getAuthenticationHolder().getAuthentication().getOAuth2Request().getScope(); - user = userInfoService.getByUsernameAndClientId(refresh.getAuthenticationHolder().getAuthentication().getName(), tokenClient.getClientId()); - - token = refresh; + user = userInfoService.getByUsernameAndClientId(refreshToken.getAuthenticationHolder().getAuthentication().getName(), tokenClient.getClientId()); } catch (InvalidTokenException e2) { - logger.error("Verify failed; Invalid refresh token", e2); + logger.error("Verify failed; Invalid access/refresh token", e2); Map entity = ImmutableMap.of("active", Boolean.FALSE); model.addAttribute("entity", entity); return JsonEntityView.VIEWNAME; } } - if (tokenClient != null && authClient != null) { - if (authClient.isAllowIntrospection()) { - if (introspectionAuthorizer.isIntrospectionPermitted(authClient, tokenClient, scopes)) { - // if it's a valid token, we'll print out information on it - model.addAttribute("token", token); - model.addAttribute("user", user); - return TokenIntrospectionView.VIEWNAME; - } else { - logger.error("Verify failed; client configuration or scope don't permit token introspection"); - model.addAttribute("code", HttpStatus.FORBIDDEN); - return HttpCodeView.VIEWNAME; - } - } else { - logger.error("Verify failed; client " + clientId + " is not allowed to call introspection endpoint"); - model.addAttribute("code", HttpStatus.FORBIDDEN); - return HttpCodeView.VIEWNAME; - } - } else { - // This is a bad error -- I think it means we have a token outstanding that doesn't map to a client? - logger.error("Verify failed; client " + clientId + " not found."); - model.addAttribute("code", HttpStatus.NOT_FOUND); - return HttpCodeView.VIEWNAME; - } + // clientID is the principal name in the authentication + String clientId = p.getName(); + ClientDetailsEntity authClient = clientService.loadClientByClientId(clientId); + + if (authClient.isAllowIntrospection()) { + if (introspectionAuthorizer.isIntrospectionPermitted(authClient, tokenClient, scopes)) { + // if it's a valid token, we'll print out information on it + Map entity = accessToken != null + ? introspectionResultAssembler.assembleFrom(accessToken, user) + : introspectionResultAssembler.assembleFrom(refreshToken, user); + model.addAttribute("entity", entity); + return JsonEntityView.VIEWNAME; + } else { + logger.error("Verify failed; client configuration or scope don't permit token introspection"); + model.addAttribute("code", HttpStatus.FORBIDDEN); + return HttpCodeView.VIEWNAME; + } + } else { + logger.error("Verify failed; client " + clientId + " is not allowed to call introspection endpoint"); + model.addAttribute("code", HttpStatus.FORBIDDEN); + return HttpCodeView.VIEWNAME; + } } diff --git a/openid-connect-server/src/test/java/org/mitre/oauth2/service/impl/TestDefaultIntrospectionResultAssembler.java b/openid-connect-server/src/test/java/org/mitre/oauth2/service/impl/TestDefaultIntrospectionResultAssembler.java new file mode 100644 index 000000000..cff392105 --- /dev/null +++ b/openid-connect-server/src/test/java/org/mitre/oauth2/service/impl/TestDefaultIntrospectionResultAssembler.java @@ -0,0 +1,230 @@ +/******************************************************************************* + * Copyright 2014 The MITRE Corporation + * and the MIT Kerberos and Internet Trust Consortium + * + * 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.mitre.oauth2.service.impl; + +import com.google.common.collect.ImmutableMap; +import org.junit.Test; +import org.mitre.oauth2.model.OAuth2AccessTokenEntity; +import org.mitre.oauth2.model.OAuth2RefreshTokenEntity; +import org.mitre.openid.connect.model.UserInfo; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.OAuth2Request; + +import java.util.Date; +import java.util.Map; +import java.util.Set; + +import static com.google.common.collect.Sets.newHashSet; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.RETURNS_DEEP_STUBS; +import static org.mockito.Mockito.mock; + +public class TestDefaultIntrospectionResultAssembler { + + private DefaultIntrospectionResultAssembler assembler = new DefaultIntrospectionResultAssembler(); + + @Test + public void shouldAssembleExpectedResultForAccessToken() { + + // given + OAuth2AccessTokenEntity accessToken = accessToken(new Date(123), scopes("foo", "bar"), "Bearer", + authentication("name", request("clientId"))); + + UserInfo userInfo = userInfo("sub"); + + // when + Map result = assembler.assembleFrom(accessToken, userInfo); + + + // then + Map expected = new ImmutableMap.Builder() + .put("sub", "sub") + .put("exp", new Date(123)) + .put("scope", "bar foo") + .put("active", Boolean.TRUE) + .put("user_id", "name") + .put("client_id", "clientId") + .put("token_type", "Bearer") + .build(); + assertThat(result, is(equalTo(expected))); + } + + @Test + public void shouldAssembleExpectedResultForAccessTokenWithoutUserInfo() { + + // given + OAuth2AccessTokenEntity accessToken = accessToken(new Date(123), scopes("foo", "bar"), "Bearer", + authentication("name", request("clientId"))); + + // when + Map result = assembler.assembleFrom(accessToken, null); + + + // then + Map expected = new ImmutableMap.Builder() + .put("sub", "name") + .put("exp", new Date(123)) + .put("scope", "bar foo") + .put("active", Boolean.TRUE) + .put("user_id", "name") + .put("client_id", "clientId") + .put("token_type", "Bearer") + .build(); + assertThat(result, is(equalTo(expected))); + } + + @Test + public void shouldAssembleExpectedResultForAccessTokenWithoutExpiry() { + + // given + OAuth2AccessTokenEntity accessToken = accessToken(null, scopes("foo", "bar"), "Bearer", + authentication("name", request("clientId"))); + + UserInfo userInfo = userInfo("sub"); + + // when + Map result = assembler.assembleFrom(accessToken, userInfo); + + + // then + Map expected = new ImmutableMap.Builder() + .put("sub", "sub") + .put("scope", "bar foo") + .put("active", Boolean.TRUE) + .put("user_id", "name") + .put("client_id", "clientId") + .put("token_type", "Bearer") + .build(); + assertThat(result, is(equalTo(expected))); + } + + @Test + public void shouldAssembleExpectedResultForRefreshToken() { + + // given + OAuth2RefreshTokenEntity refreshToken = refreshToken(new Date(123), + authentication("name", request("clientId", scopes("foo", "bar")))); + + UserInfo userInfo = userInfo("sub"); + + // when + Map result = assembler.assembleFrom(refreshToken, userInfo); + + + // then + Map expected = new ImmutableMap.Builder() + .put("sub", "sub") + .put("exp", new Date(123)) + .put("scope", "bar foo") + .put("active", Boolean.TRUE) + .put("user_id", "name") + .put("client_id", "clientId") + .build(); + assertThat(result, is(equalTo(expected))); + } + + @Test + public void shouldAssembleExpectedResultForRefreshTokenWithoutUserInfo() { + + // given + OAuth2RefreshTokenEntity refreshToken = refreshToken(new Date(123), + authentication("name", request("clientId", scopes("foo", "bar")))); + + // when + Map result = assembler.assembleFrom(refreshToken, null); + + + // then + Map expected = new ImmutableMap.Builder() + .put("sub", "name") + .put("exp", new Date(123)) + .put("scope", "bar foo") + .put("active", Boolean.TRUE) + .put("user_id", "name") + .put("client_id", "clientId") + .build(); + assertThat(result, is(equalTo(expected))); + } + + @Test + public void shouldAssembleExpectedResultForRefreshTokenWithoutExpiry() { + + // given + OAuth2RefreshTokenEntity refreshToken = refreshToken(null, + authentication("name", request("clientId", scopes("foo", "bar")))); + + UserInfo userInfo = userInfo("sub"); + + // when + Map result = assembler.assembleFrom(refreshToken, userInfo); + + + // then + Map expected = new ImmutableMap.Builder() + .put("sub", "sub") + .put("scope", "bar foo") + .put("active", Boolean.TRUE) + .put("user_id", "name") + .put("client_id", "clientId") + .build(); + assertThat(result, is(equalTo(expected))); + } + + private UserInfo userInfo(String sub) { + UserInfo userInfo = mock(UserInfo.class); + given(userInfo.getSub()).willReturn(sub); + return userInfo; + } + + private OAuth2AccessTokenEntity accessToken(Date exp, Set scopes, String tokenType, OAuth2Authentication authentication) { + OAuth2AccessTokenEntity accessToken = mock(OAuth2AccessTokenEntity.class, RETURNS_DEEP_STUBS); + given(accessToken.getExpiration()).willReturn(exp); + given(accessToken.getScope()).willReturn(scopes); + given(accessToken.getTokenType()).willReturn(tokenType); + given(accessToken.getAuthenticationHolder().getAuthentication()).willReturn(authentication); + return accessToken; + } + + private OAuth2RefreshTokenEntity refreshToken(Date exp, OAuth2Authentication authentication) { + OAuth2RefreshTokenEntity refreshToken = mock(OAuth2RefreshTokenEntity.class, RETURNS_DEEP_STUBS); + given(refreshToken.getExpiration()).willReturn(exp); + given(refreshToken.getAuthenticationHolder().getAuthentication()).willReturn(authentication); + return refreshToken; + } + + private OAuth2Authentication authentication(String name, OAuth2Request request) { + OAuth2Authentication authentication = mock(OAuth2Authentication.class); + given(authentication.getName()).willReturn(name); + given(authentication.getOAuth2Request()).willReturn(request); + return authentication; + } + + private OAuth2Request request(String clientId) { + return request(clientId, null); + } + + private OAuth2Request request(String clientId, Set scopes) { + return new OAuth2Request(null, clientId, null, true, scopes, null, null, null, null); + } + + private Set scopes(String... scopes) { + return newHashSet(scopes); + } +}