/*******************************************************************************
* 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 java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mitre.oauth2.model.ClientDetailsEntity;
import org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod;
import org.mitre.oauth2.model.SystemScope;
import org.mitre.oauth2.repository.OAuth2ClientRepository;
import org.mitre.oauth2.repository.OAuth2TokenRepository;
import org.mitre.oauth2.service.SystemScopeService;
import org.mitre.openid.connect.config.ConfigurationPropertiesBean;
import org.mitre.openid.connect.model.WhitelistedSite;
import org.mitre.openid.connect.service.ApprovedSiteService;
import org.mitre.openid.connect.service.BlacklistedSiteService;
import org.mitre.openid.connect.service.StatsService;
import org.mitre.openid.connect.service.WhitelistedSiteService;
import org.mitre.uma.model.ResourceSet;
import org.mitre.uma.service.ResourceSetService;
import org.mockito.AdditionalAnswers;
import org.mockito.InjectMocks;
import org.mockito.Matchers;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
import com.google.common.collect.Sets;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
/**
* @author wkim
*
*/
@RunWith(MockitoJUnitRunner.class)
public class TestDefaultOAuth2ClientDetailsEntityService {
@Mock
private OAuth2ClientRepository clientRepository;
@Mock
private OAuth2TokenRepository tokenRepository;
@Mock
private ApprovedSiteService approvedSiteService;
@Mock
private WhitelistedSiteService whitelistedSiteService;
@Mock
private BlacklistedSiteService blacklistedSiteService;
@Mock
private SystemScopeService scopeService;
@Mock
private ResourceSetService resourceSetService;
@Mock
private StatsService statsService;
@Mock
private ConfigurationPropertiesBean config;
@InjectMocks
private DefaultOAuth2ClientDetailsEntityService service;
@Before
public void prepare() {
Mockito.reset(clientRepository, tokenRepository, approvedSiteService, whitelistedSiteService, blacklistedSiteService, scopeService, statsService);
Mockito.when(clientRepository.saveClient(Matchers.any(ClientDetailsEntity.class))).thenAnswer(new Answer<ClientDetailsEntity>() {
@Override
public ClientDetailsEntity answer(InvocationOnMock invocation) throws Throwable {
Object[] args = invocation.getArguments();
return (ClientDetailsEntity) args[0];
}
});
Mockito.when(clientRepository.updateClient(Matchers.anyLong(), Matchers.any(ClientDetailsEntity.class))).thenAnswer(new Answer<ClientDetailsEntity>() {
@Override
public ClientDetailsEntity answer(InvocationOnMock invocation) throws Throwable {
Object[] args = invocation.getArguments();
return (ClientDetailsEntity) args[1];
}
});
Mockito.when(scopeService.fromStrings(Matchers.anySet())).thenAnswer(new Answer<Set<SystemScope>>() {
@Override
public Set<SystemScope> answer(InvocationOnMock invocation) throws Throwable {
Object[] args = invocation.getArguments();
Set<String> input = (Set<String>) args[0];
Set<SystemScope> output = new HashSet<>();
for (String scope : input) {
output.add(new SystemScope(scope));
}
return output;
}
});
Mockito.when(scopeService.toStrings(Matchers.anySet())).thenAnswer(new Answer<Set<String>>() {
@Override
public Set<String> answer(InvocationOnMock invocation) throws Throwable {
Object[] args = invocation.getArguments();
Set<SystemScope> input = (Set<SystemScope>) args[0];
Set<String> output = new HashSet<>();
for (SystemScope scope : input) {
output.add(scope.getValue());
}
return output;
}
});
// we're not testing reserved scopes here, just pass through when it's called
Mockito.when(scopeService.removeReservedScopes(Matchers.anySet())).then(AdditionalAnswers.returnsFirstArg());
Mockito.when(config.isHeartMode()).thenReturn(false);
}
/**
* Failure case of existing client id.
*/
@Test(expected = IllegalArgumentException.class)
public void saveNewClient_badId() {
// Set up a mock client.
ClientDetailsEntity client = Mockito.mock(ClientDetailsEntity.class);
Mockito.when(client.getId()).thenReturn(12345L); // any non-null ID will work
service.saveNewClient(client);
}
/**
* Failure case of blacklisted client uri.
*/
@Test(expected = IllegalArgumentException.class)
public void saveNewClient_blacklisted() {
ClientDetailsEntity client = Mockito.mock(ClientDetailsEntity.class);
Mockito.when(client.getId()).thenReturn(null);
String badUri = "badplace.xxx";
Mockito.when(blacklistedSiteService.isBlacklisted(badUri)).thenReturn(true);
Mockito.when(client.getRegisteredRedirectUri()).thenReturn(Sets.newHashSet(badUri));
service.saveNewClient(client);
}
@Test
public void saveNewClient_idWasAssigned() {
// Set up a mock client.
ClientDetailsEntity client = Mockito.mock(ClientDetailsEntity.class);
Mockito.when(client.getId()).thenReturn(null);
service.saveNewClient(client);
Mockito.verify(client).setClientId(Matchers.anyString());
}
/**
* Makes sure client has offline access granted scope if allowed refresh tokens.
*/
@Test
public void saveNewClient_yesOfflineAccess() {
ClientDetailsEntity client = new ClientDetailsEntity();
Set<String> grantTypes = new HashSet<>();
grantTypes.add("refresh_token");
client.setGrantTypes(grantTypes);
client = service.saveNewClient(client);
assertThat(client.getScope().contains(SystemScopeService.OFFLINE_ACCESS), is(equalTo(true)));
}
/**
* Makes sure client does not have offline access if not allowed to have refresh tokens.
*/
@Test
public void saveNewClient_noOfflineAccess() {
ClientDetailsEntity client = new ClientDetailsEntity();
client = service.saveNewClient(client);
Mockito.verify(scopeService, Mockito.atLeastOnce()).removeReservedScopes(Matchers.anySet());
assertThat(client.getScope().contains(SystemScopeService.OFFLINE_ACCESS), is(equalTo(false)));
}
@Test
public void loadClientByClientId_badId() {
// null id
try {
service.loadClientByClientId(null);
fail("Null client id. Expected an IllegalArgumentException.");
} catch (IllegalArgumentException e) {
assertThat(e, is(notNullValue()));
}
// empty id
try {
service.loadClientByClientId("");
fail("Empty client id. Expected an IllegalArgumentException.");
} catch (IllegalArgumentException e) {
assertThat(e, is(notNullValue()));
}
// id not found
String clientId = "b00g3r";
Mockito.when(clientRepository.getClientByClientId(clientId)).thenReturn(null);
try {
service.loadClientByClientId(clientId);
fail("Client id not found. Expected an InvalidClientException.");
} catch (InvalidClientException e) {
assertThat(e, is(notNullValue()));
}
}
@Test(expected = InvalidClientException.class)
public void deleteClient_badId() {
Long id = 12345L;
ClientDetailsEntity client = Mockito.mock(ClientDetailsEntity.class);
Mockito.when(client.getId()).thenReturn(id);
Mockito.when(clientRepository.getById(id)).thenReturn(null);
service.deleteClient(client);
}
@Test
public void deleteClient() {
Long id = 12345L;
String clientId = "b00g3r";
ClientDetailsEntity client = Mockito.mock(ClientDetailsEntity.class);
Mockito.when(client.getId()).thenReturn(id);
Mockito.when(client.getClientId()).thenReturn(clientId);
Mockito.when(clientRepository.getById(id)).thenReturn(client);
WhitelistedSite site = Mockito.mock(WhitelistedSite.class);
Mockito.when(whitelistedSiteService.getByClientId(clientId)).thenReturn(site);
Mockito.when(resourceSetService.getAllForClient(client)).thenReturn(new HashSet<ResourceSet>());
service.deleteClient(client);
Mockito.verify(tokenRepository).clearTokensForClient(client);
Mockito.verify(approvedSiteService).clearApprovedSitesForClient(client);
Mockito.verify(whitelistedSiteService).remove(site);
Mockito.verify(clientRepository).deleteClient(client);
}
@Test
public void updateClient_nullClients() {
ClientDetailsEntity oldClient = Mockito.mock(ClientDetailsEntity.class);
ClientDetailsEntity newClient = Mockito.mock(ClientDetailsEntity.class);
try {
service.updateClient(oldClient, null);
fail("New client is null. Expected an IllegalArgumentException.");
} catch (IllegalArgumentException e) {
assertThat(e, is(notNullValue()));
}
try {
service.updateClient(null, newClient);
fail("Old client is null. Expected an IllegalArgumentException.");
} catch (IllegalArgumentException e) {
assertThat(e, is(notNullValue()));
}
try {
service.updateClient(null, null);
fail("Both clients are null. Expected an IllegalArgumentException.");
} catch (IllegalArgumentException e) {
assertThat(e, is(notNullValue()));
}
}
@Test(expected = IllegalArgumentException.class)
public void updateClient_blacklistedUri() {
ClientDetailsEntity oldClient = Mockito.mock(ClientDetailsEntity.class);
ClientDetailsEntity newClient = Mockito.mock(ClientDetailsEntity.class);
String badSite = "badsite.xxx";
Mockito.when(newClient.getRegisteredRedirectUri()).thenReturn(Sets.newHashSet(badSite));
Mockito.when(blacklistedSiteService.isBlacklisted(badSite)).thenReturn(true);
service.updateClient(oldClient, newClient);
}
@Test
public void updateClient_yesOfflineAccess() {
ClientDetailsEntity oldClient = new ClientDetailsEntity();
ClientDetailsEntity client = new ClientDetailsEntity();
Set<String> grantTypes = new HashSet<>();
grantTypes.add("refresh_token");
client.setGrantTypes(grantTypes);
client = service.updateClient(oldClient, client);
Mockito.verify(scopeService, Mockito.atLeastOnce()).removeReservedScopes(Matchers.anySet());
assertThat(client.getScope().contains(SystemScopeService.OFFLINE_ACCESS), is(equalTo(true)));
}
@Test
public void updateClient_noOfflineAccess() {
ClientDetailsEntity oldClient = new ClientDetailsEntity();
oldClient.getScope().add(SystemScopeService.OFFLINE_ACCESS);
ClientDetailsEntity client = new ClientDetailsEntity();
client = service.updateClient(oldClient, client);
Mockito.verify(scopeService, Mockito.atLeastOnce()).removeReservedScopes(Matchers.anySet());
assertThat(client.getScope().contains(SystemScopeService.OFFLINE_ACCESS), is(equalTo(false)));
}
@Test(expected = IllegalArgumentException.class)
public void heartMode_authcode_invalidGrants() {
Mockito.when(config.isHeartMode()).thenReturn(true);
ClientDetailsEntity client = new ClientDetailsEntity();
Set<String> grantTypes = new LinkedHashSet<>();
grantTypes.add("authorization_code");
grantTypes.add("implicit");
grantTypes.add("client_credentials");
client.setGrantTypes(grantTypes);
client.setTokenEndpointAuthMethod(AuthMethod.PRIVATE_KEY);
client.setRedirectUris(Sets.newHashSet("https://foo.bar/"));
client.setJwksUri("https://foo.bar/jwks");
service.saveNewClient(client);
}
@Test(expected = IllegalArgumentException.class)
public void heartMode_implicit_invalidGrants() {
Mockito.when(config.isHeartMode()).thenReturn(true);
ClientDetailsEntity client = new ClientDetailsEntity();
Set<String> grantTypes = new LinkedHashSet<>();
grantTypes.add("implicit");
grantTypes.add("authorization_code");
grantTypes.add("client_credentials");
client.setGrantTypes(grantTypes);
client.setTokenEndpointAuthMethod(AuthMethod.NONE);
client.setRedirectUris(Sets.newHashSet("https://foo.bar/"));
client.setJwksUri("https://foo.bar/jwks");
service.saveNewClient(client);
}
@Test(expected = IllegalArgumentException.class)
public void heartMode_clientcreds_invalidGrants() {
Mockito.when(config.isHeartMode()).thenReturn(true);
ClientDetailsEntity client = new ClientDetailsEntity();
Set<String> grantTypes = new LinkedHashSet<>();
grantTypes.add("client_credentials");
grantTypes.add("authorization_code");
grantTypes.add("implicit");
client.setGrantTypes(grantTypes);
client.setTokenEndpointAuthMethod(AuthMethod.PRIVATE_KEY);
client.setJwksUri("https://foo.bar/jwks");
service.saveNewClient(client);
}
@Test(expected = IllegalArgumentException.class)
public void heartMode_authcode_authMethod() {
Mockito.when(config.isHeartMode()).thenReturn(true);
ClientDetailsEntity client = new ClientDetailsEntity();
Set<String> grantTypes = new LinkedHashSet<>();
grantTypes.add("authorization_code");
client.setGrantTypes(grantTypes);
client.setTokenEndpointAuthMethod(AuthMethod.SECRET_POST);
client.setRedirectUris(Sets.newHashSet("https://foo.bar/"));
client.setJwksUri("https://foo.bar/jwks");
service.saveNewClient(client);
}
@Test(expected = IllegalArgumentException.class)
public void heartMode_implicit_authMethod() {
Mockito.when(config.isHeartMode()).thenReturn(true);
ClientDetailsEntity client = new ClientDetailsEntity();
Set<String> grantTypes = new LinkedHashSet<>();
grantTypes.add("implicit");
client.setGrantTypes(grantTypes);
client.setTokenEndpointAuthMethod(AuthMethod.PRIVATE_KEY);
client.setRedirectUris(Sets.newHashSet("https://foo.bar/"));
client.setJwksUri("https://foo.bar/jwks");
service.saveNewClient(client);
}
@Test(expected = IllegalArgumentException.class)
public void heartMode_clientcreds_authMethod() {
Mockito.when(config.isHeartMode()).thenReturn(true);
ClientDetailsEntity client = new ClientDetailsEntity();
Set<String> grantTypes = new LinkedHashSet<>();
grantTypes.add("client_credentials");
client.setGrantTypes(grantTypes);
client.setTokenEndpointAuthMethod(AuthMethod.SECRET_BASIC);
client.setRedirectUris(Sets.newHashSet("https://foo.bar/"));
client.setJwksUri("https://foo.bar/jwks");
service.saveNewClient(client);
}
@Test(expected = IllegalArgumentException.class)
public void heartMode_authcode_redirectUris() {
Mockito.when(config.isHeartMode()).thenReturn(true);
ClientDetailsEntity client = new ClientDetailsEntity();
Set<String> grantTypes = new LinkedHashSet<>();
grantTypes.add("authorization_code");
client.setGrantTypes(grantTypes);
client.setTokenEndpointAuthMethod(AuthMethod.PRIVATE_KEY);
service.saveNewClient(client);
}
@Test(expected = IllegalArgumentException.class)
public void heartMode_implicit_redirectUris() {
Mockito.when(config.isHeartMode()).thenReturn(true);
ClientDetailsEntity client = new ClientDetailsEntity();
Set<String> grantTypes = new LinkedHashSet<>();
grantTypes.add("implicit");
client.setGrantTypes(grantTypes);
client.setTokenEndpointAuthMethod(AuthMethod.NONE);
service.saveNewClient(client);
}
@Test(expected = IllegalArgumentException.class)
public void heartMode_clientcreds_redirectUris() {
Mockito.when(config.isHeartMode()).thenReturn(true);
ClientDetailsEntity client = new ClientDetailsEntity();
Set<String> grantTypes = new LinkedHashSet<>();
grantTypes.add("client_credentials");
client.setGrantTypes(grantTypes);
client.setTokenEndpointAuthMethod(AuthMethod.PRIVATE_KEY);
client.setRedirectUris(Sets.newHashSet("http://foo.bar/"));
service.saveNewClient(client);
}
@Test(expected = IllegalArgumentException.class)
public void heartMode_clientSecret() {
Mockito.when(config.isHeartMode()).thenReturn(true);
ClientDetailsEntity client = new ClientDetailsEntity();
Set<String> grantTypes = new LinkedHashSet<>();
grantTypes.add("authorization_code");
client.setGrantTypes(grantTypes);
client.setTokenEndpointAuthMethod(AuthMethod.PRIVATE_KEY);
client.setRedirectUris(Sets.newHashSet("http://foo.bar/"));
client.setClientSecret("secret!");
service.saveNewClient(client);
}
@Test(expected = IllegalArgumentException.class)
public void heartMode_noJwks() {
Mockito.when(config.isHeartMode()).thenReturn(true);
ClientDetailsEntity client = new ClientDetailsEntity();
Set<String> grantTypes = new LinkedHashSet<>();
grantTypes.add("authorization_code");
client.setGrantTypes(grantTypes);
client.setTokenEndpointAuthMethod(AuthMethod.PRIVATE_KEY);
client.setRedirectUris(Sets.newHashSet("https://foo.bar/"));
client.setJwks(null);
client.setJwksUri(null);
service.saveNewClient(client);
}
@Test
public void heartMode_validAuthcodeClient() {
Mockito.when(config.isHeartMode()).thenReturn(true);
ClientDetailsEntity client = new ClientDetailsEntity();
Set<String> grantTypes = new LinkedHashSet<>();
grantTypes.add("authorization_code");
grantTypes.add("refresh_token");
client.setGrantTypes(grantTypes);
client.setTokenEndpointAuthMethod(AuthMethod.PRIVATE_KEY);
client.setRedirectUris(Sets.newHashSet("https://foo.bar/"));
client.setJwksUri("https://foo.bar/jwks");
service.saveNewClient(client);
assertThat(client.getClientId(), is(notNullValue(String.class)));
assertThat(client.getClientSecret(), is(nullValue()));
}
@Test(expected = IllegalArgumentException.class)
public void heartMode_nonLocalHttpRedirect() {
Mockito.when(config.isHeartMode()).thenReturn(true);
ClientDetailsEntity client = new ClientDetailsEntity();
Set<String> grantTypes = new LinkedHashSet<>();
grantTypes.add("authorization_code");
grantTypes.add("refresh_token");
client.setGrantTypes(grantTypes);
client.setTokenEndpointAuthMethod(AuthMethod.PRIVATE_KEY);
client.setRedirectUris(Sets.newHashSet("http://foo.bar/"));
client.setJwksUri("https://foo.bar/jwks");
service.saveNewClient(client);
}
@Test(expected = IllegalArgumentException.class)
public void heartMode_multipleRedirectClass() {
Mockito.when(config.isHeartMode()).thenReturn(true);
ClientDetailsEntity client = new ClientDetailsEntity();
Set<String> grantTypes = new LinkedHashSet<>();
grantTypes.add("authorization_code");
grantTypes.add("refresh_token");
client.setGrantTypes(grantTypes);
client.setTokenEndpointAuthMethod(AuthMethod.PRIVATE_KEY);
client.setRedirectUris(Sets.newHashSet("http://localhost/", "https://foo.bar", "foo://bar"));
client.setJwksUri("https://foo.bar/jwks");
service.saveNewClient(client);
}
}