/*******************************************************************************
* Copyright 2017 The MITRE Corporation
* and the MIT 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 static com.google.common.collect.Sets.newHashSet;
import static org.mockito.BDDMockito.given;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.Set;
import javax.swing.text.DateFormatter;
import org.junit.Test;
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.mitre.uma.model.Permission;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
import static org.mockito.Mockito.mock;
import static org.junit.Assert.assertThat;
public class TestDefaultIntrospectionResultAssembler {
private IntrospectionResultAssembler assembler = new DefaultIntrospectionResultAssembler();
private static DateFormatter dateFormat = new DateFormatter(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"));
@Test
public void shouldAssembleExpectedResultForAccessToken() throws ParseException {
// given
OAuth2AccessTokenEntity accessToken = accessToken(new Date(123 * 1000L), scopes("foo", "bar"), null, "Bearer",
oauth2AuthenticationWithUser(oauth2Request("clientId"), "name"));
UserInfo userInfo = userInfo("sub");
Set<String> authScopes = scopes("foo", "bar", "baz");
// when
Map<String, Object> result = assembler.assembleFrom(accessToken, userInfo, authScopes);
// then
Map<String, Object> expected = new ImmutableMap.Builder<String, Object>()
.put("sub", "sub")
.put("exp", 123L)
.put("expires_at", dateFormat.valueToString(new Date(123 * 1000L)))
.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 shouldAssembleExpectedResultForAccessToken_withPermissions() throws ParseException {
// given
OAuth2AccessTokenEntity accessToken = accessToken(new Date(123 * 1000L), scopes("foo", "bar"),
permissions(permission(1L, "foo", "bar")),
"Bearer", oauth2AuthenticationWithUser(oauth2Request("clientId"), "name"));
UserInfo userInfo = userInfo("sub");
Set<String> authScopes = scopes("foo", "bar", "baz");
// when
Map<String, Object> result = assembler.assembleFrom(accessToken, userInfo, authScopes);
// then
Map<String, Object> expected = new ImmutableMap.Builder<String, Object>()
.put("sub", "sub")
.put("exp", 123L)
.put("expires_at", dateFormat.valueToString(new Date(123 * 1000L)))
.put("permissions", new ImmutableSet.Builder<>()
.add(new ImmutableMap.Builder<String, Object>()
.put("resource_set_id", "1") // note that the resource ID comes out as a string
.put("scopes", new ImmutableSet.Builder<>()
.add("bar")
.add("foo")
.build())
.build())
.build())
// note that scopes are not included if permissions are included
.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() throws ParseException {
// given
OAuth2AccessTokenEntity accessToken = accessToken(new Date(123 * 1000L), scopes("foo", "bar"), null, "Bearer",
oauth2AuthenticationWithUser(oauth2Request("clientId"), "name"));
Set<String> authScopes = scopes("foo", "bar", "baz");
// when
Map<String, Object> result = assembler.assembleFrom(accessToken, null, authScopes);
// then
Map<String, Object> expected = new ImmutableMap.Builder<String, Object>()
.put("sub", "name")
.put("exp", 123L)
.put("expires_at", dateFormat.valueToString(new Date(123 * 1000L)))
.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"), null, "Bearer",
oauth2AuthenticationWithUser(oauth2Request("clientId"), "name"));
UserInfo userInfo = userInfo("sub");
Set<String> authScopes = scopes("foo", "bar", "baz");
// when
Map<String, Object> result = assembler.assembleFrom(accessToken, userInfo, authScopes);
// then
Map<String, Object> expected = new ImmutableMap.Builder<String, Object>()
.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 shouldAssembleExpectedResultForAccessTokenWithoutUserAuthentication() throws ParseException {
// given
OAuth2AccessTokenEntity accessToken = accessToken(new Date(123 * 1000L), scopes("foo", "bar"), null, "Bearer",
oauth2Authentication(oauth2Request("clientId"), null));
Set<String> authScopes = scopes("foo", "bar", "baz");
// when
Map<String, Object> result = assembler.assembleFrom(accessToken, null, authScopes);
// then `user_id` should not be present
Map<String, Object> expected = new ImmutableMap.Builder<String, Object>()
.put("sub", "clientId")
.put("exp", 123L)
.put("expires_at", dateFormat.valueToString(new Date(123 * 1000L)))
.put("scope", "bar foo")
.put("active", Boolean.TRUE)
.put("client_id", "clientId")
.put("token_type", "Bearer")
.build();
assertThat(result, is(equalTo(expected)));
}
@Test
public void shouldAssembleExpectedResultForRefreshToken() throws ParseException {
// given
OAuth2RefreshTokenEntity refreshToken = refreshToken(new Date(123 * 1000L),
oauth2AuthenticationWithUser(oauth2Request("clientId", scopes("foo", "bar")), "name"));
UserInfo userInfo = userInfo("sub");
Set<String> authScopes = scopes("foo", "bar", "baz");
// when
Map<String, Object> result = assembler.assembleFrom(refreshToken, userInfo, authScopes);
// then
Map<String, Object> expected = new ImmutableMap.Builder<String, Object>()
.put("sub", "sub")
.put("exp", 123L)
.put("expires_at", dateFormat.valueToString(new Date(123 * 1000L)))
.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() throws ParseException {
// given
OAuth2RefreshTokenEntity refreshToken = refreshToken(new Date(123 * 1000L),
oauth2AuthenticationWithUser(oauth2Request("clientId", scopes("foo", "bar")), "name"));
Set<String> authScopes = scopes("foo", "bar", "baz");
// when
Map<String, Object> result = assembler.assembleFrom(refreshToken, null, authScopes);
// then
Map<String, Object> expected = new ImmutableMap.Builder<String, Object>()
.put("sub", "name")
.put("exp", 123L)
.put("expires_at", dateFormat.valueToString(new Date(123 * 1000L)))
.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,
oauth2AuthenticationWithUser(oauth2Request("clientId", scopes("foo", "bar")), "name"));
UserInfo userInfo = userInfo("sub");
Set<String> authScopes = scopes("foo", "bar", "baz");
// when
Map<String, Object> result = assembler.assembleFrom(refreshToken, userInfo, authScopes);
// then
Map<String, Object> expected = new ImmutableMap.Builder<String, Object>()
.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)));
}
@Test
public void shouldAssembleExpectedResultForRefreshTokenWithoutUserAuthentication() throws ParseException {
// given
OAuth2RefreshTokenEntity refreshToken = refreshToken(null,
oauth2Authentication(oauth2Request("clientId", scopes("foo", "bar")), null));
Set<String> authScopes = scopes("foo", "bar", "baz");
// when
Map<String, Object> result = assembler.assembleFrom(refreshToken, null, authScopes);
// then `user_id` should not be present
Map<String, Object> expected = new ImmutableMap.Builder<String, Object>()
.put("sub", "clientId")
.put("scope", "bar foo")
.put("active", Boolean.TRUE)
.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<String> scopes, Set<Permission> permissions, String tokenType, OAuth2Authentication authentication) {
OAuth2AccessTokenEntity accessToken = mock(OAuth2AccessTokenEntity.class, RETURNS_DEEP_STUBS);
given(accessToken.getExpiration()).willReturn(exp);
given(accessToken.getScope()).willReturn(scopes);
given(accessToken.getPermissions()).willReturn(permissions);
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 oauth2AuthenticationWithUser(OAuth2Request request, String username) {
UsernamePasswordAuthenticationToken userAuthentication = new UsernamePasswordAuthenticationToken(username, "somepassword");
return oauth2Authentication(request, userAuthentication);
}
private OAuth2Authentication oauth2Authentication(OAuth2Request request, Authentication userAuthentication) {
return new OAuth2Authentication(request, userAuthentication);
}
private OAuth2Request oauth2Request(String clientId) {
return oauth2Request(clientId, null);
}
private OAuth2Request oauth2Request(String clientId, Set<String> scopes) {
return new OAuth2Request(null, clientId, null, true, scopes, null, null, null, null);
}
private Set<String> scopes(String... scopes) {
return newHashSet(scopes);
}
private Set<Permission> permissions(Permission... permissions) {
return newHashSet(permissions);
}
private Permission permission(Long resourceSetId, String... scopes) {
Permission permission = mock(Permission.class, RETURNS_DEEP_STUBS);
given(permission.getResourceSet().getId()).willReturn(resourceSetId);
given(permission.getScopes()).willReturn(scopes(scopes));
return permission;
}
}