Commit a3a16e18 authored by Maxym Borodenko's avatar Maxym Borodenko Committed by Matija Obreza
Browse files

Client Credential Grant

parent a66c7405
......@@ -130,5 +130,56 @@
<version>${spring.security.oauth2.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>${hsqldb.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
<version>8.5.8</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-hibernate4</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-ehcache</artifactId>
<version>${hibernate.version}</version>
<exclusions>
<exclusion>
<artifactId>slf4j-api</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>
/*
* Copyright 2017 Global Crop Diversity Trust
*
* 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.genesys.blocks.security.config;
import java.util.Arrays;
import org.genesys.blocks.oauth.service.OAuthServiceImpl;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.cache.CacheManager;
import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.cache.support.SimpleCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.Import;
import org.springframework.core.io.ClassPathResource;
/**
* @author Maxym Borodenko
*/
@Configuration
@EnableAspectJAutoProxy
@Import({ DatabaseConfig.class })
@ComponentScan(basePackages = { "org.genesys.blocks.oauth.service" })
public class ApplicationConfig {
@Bean
public static PropertyPlaceholderConfigurer propertyPlaceholderConfigurer() {
final PropertyPlaceholderConfigurer propertyPlaceholderConfigurer = new PropertyPlaceholderConfigurer();
propertyPlaceholderConfigurer.setIgnoreResourceNotFound(true);
propertyPlaceholderConfigurer.setFileEncoding("utf-8");
propertyPlaceholderConfigurer.setLocations(new ClassPathResource("application.properties"));
return propertyPlaceholderConfigurer;
}
@Bean
public CacheManager cacheManager() {
SimpleCacheManager cacheManager = new SimpleCacheManager();
cacheManager.setCaches(Arrays.asList(new ConcurrentMapCache("default")));
return cacheManager;
}
@Bean
public OAuthServiceImpl oauthService() {
return new OAuthServiceImpl();
}
}
/*
* Copyright 2017 Global Crop Diversity Trust
*
* 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.genesys.blocks.security.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.token.TokenStore;
/**
* @author Maxym Borodenko
*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
private static final String APPLICATION_RESOURCE_ID = "app-blocks";
@Autowired
@Qualifier("oauthService")
private TokenStore tokenStore;
@Autowired
@Qualifier("oauthService")
private ClientDetailsService clientDetailsService;
@Autowired
@Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
@Override
public void configure(final AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.realm(APPLICATION_RESOURCE_ID + "/client");
}
@Override
public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.tokenStore(tokenStore)
.authenticationManager(authenticationManager);
}
@Override
public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetailsService);
}
}
/*
* Copyright 2017 Global Crop Diversity Trust
*
* 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.genesys.blocks.security.config;
import org.apache.tomcat.jdbc.pool.DataSource;
import org.genesys.blocks.security.SpringSecurityAuditorAware;
import org.hibernate.jpa.HibernatePersistenceProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.orm.hibernate4.HibernateExceptionTranslator;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.persistence.EntityManagerFactory;
import java.util.Properties;
/**
* @author Matija Obreza
* @author Maxym Borodenko
*/
@Configuration
@EnableJpaRepositories(basePackages = {
"org.genesys.blocks.oauth.persistence",
"org.genesys.blocks.security.persistence",
"org.genesys.blocks.auditlog.persistence"},
repositoryImplementationPostfix = "CustomImpl", entityManagerFactoryRef = "entityManagerFactory", transactionManagerRef = "transactionManager")
@EnableTransactionManagement
@EnableJpaAuditing(auditorAwareRef = "auditorAware", modifyOnCreate = true)
public class DatabaseConfig {
private static final String[] JPA_PACKAGES = {
"org.genesys.blocks.oauth.model",
"org.genesys.blocks.model",
"org.genesys.blocks.security.model",
"org.genesys.blocks.auditlog.model"};
@Value("${db.url}")
private String dbUrl;
@Value("${db.driverClassName}")
private String dbDriverClassName;
@Value("${db.username}")
private String dbUsername;
@Value("${db.password}")
private String dbPassword;
@Value("${db.showSql}")
private boolean dbShowSql;
@Value("${db.updateSchema}")
private boolean dbGenerateDdl;
@Value("${db.hibernate.dialect}")
private String hibernateDialect;
@Value("${db.pool.maxActive}")
private int dbPoolMaxActive;
@Value("${db.pool.initialSize}")
private int dbPoolInitialSize;
@Bean(name = "dataSource")
public DataSource dataSource() throws Exception {
final DataSource dataSource = new DataSource();
dataSource.setUrl(dbUrl);
dataSource.setDriverClassName(dbDriverClassName);
dataSource.setUsername(dbUsername);
dataSource.setPassword(dbPassword);
dataSource.setValidationQuery("SELECT 1");
dataSource.setTestWhileIdle(true);
dataSource.setTestOnBorrow(true);
dataSource.setMaxActive(dbPoolMaxActive);
dataSource.setInitialSize(dbPoolInitialSize);
dataSource.setMinIdle(dbPoolInitialSize);
return dataSource;
}
@Bean
public JdbcTemplate jdbcTemplate(final DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
@Bean(name = "entityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory() throws Exception {
final LocalContainerEntityManagerFactoryBean bean = new LocalContainerEntityManagerFactoryBean();
bean.setDataSource(dataSource());
bean.setPersistenceUnitName("spring-jpa");
bean.setPackagesToScan(JPA_PACKAGES);
bean.setPersistenceProvider(new HibernatePersistenceProvider());
final HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
jpaVendorAdapter.setShowSql(dbShowSql);
jpaVendorAdapter.setGenerateDdl(dbGenerateDdl);
final Properties jpaProperties = jpaProperties();
bean.setJpaProperties(jpaProperties);
bean.setJpaVendorAdapter(jpaVendorAdapter);
return bean;
}
@Bean(name = "transactionManager")
public PlatformTransactionManager jpaTransactionManager(final EntityManagerFactory entityManagerFactory) throws Exception {
final JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setDataSource(dataSource());
transactionManager.setEntityManagerFactory(entityManagerFactory);
return transactionManager;
}
@Bean
public PersistenceExceptionTranslator hibernateExceptionTranslator() {
return new HibernateExceptionTranslator();
}
private Properties jpaProperties() throws Exception {
final Properties jpaProp = new Properties();
jpaProp.load(getClass().getResourceAsStream("/hibernate.properties"));
jpaProp.put("hibernate.dialect", hibernateDialect);
return jpaProp;
}
@Bean
public SpringSecurityAuditorAware auditorAware() {
return new SpringSecurityAuditorAware();
}
}
\ No newline at end of file
/*
* Copyright 2017 Global Crop Diversity Trust
*
* 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.genesys.blocks.security.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
/**
* @author Maxym Borodenko
*/
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
/*
* Copyright 2017 Global Crop Diversity Trust
*
* 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.genesys.blocks.security.oauth2;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.genesys.blocks.oauth.model.OAuthClient;
import org.genesys.blocks.oauth.model.OAuthRole;
import org.genesys.blocks.security.rest.AbstractRestTest;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.util.Base64Utils;
import org.springframework.web.context.WebApplicationContext;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
/**
* @author Maxym Borodenko
*/
public class OAuth2GrantTypeTest extends AbstractRestTest {
@Autowired
private WebApplicationContext context;
private MockMvc mockMvc;
private static final String DEFAULT_CLIENT_ID = "my-trusted-client";
private static final String DEFAULT_CLIENT_SECRET = "my-secret";
private static final ObjectMapper objectMapper;
static {
objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
objectMapper.configure(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS, false);
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
}
@Before
public void setUp() {
FilterChainProxy springSecurityFilterChain = context.getBean("springSecurityFilterChain", FilterChainProxy.class);
mockMvc = MockMvcBuilders.webAppContextSetup(context)
.addFilter(springSecurityFilterChain)
.build();
}
@Test
public void getAccessTokenWithClientCredentialsGrandTest() throws Exception {
OAuthClient client = makeDefault();
client = oAuthClientRepository.save(client);
assertThat("OAuthClient#id must be generated", client.getId(), not(nullValue()));
final OAuthClient storedClient = oAuthClientRepository.findOne(client.getId());
assertThat("Could not load persisted OAuthClient", storedClient, not(nullValue()));
assertThat("OAuthClient#id does not match", storedClient.getId(), equalTo(client.getId()));
final OAuth2AccessToken accessToken = getAccessToken();
assertThat("Could not load AccessToken", accessToken, not(nullValue()));
assertThat(accessToken.getScope().size(), equalTo(1));
}
private OAuth2AccessToken getAccessToken() throws Exception {
MockHttpServletResponse response = mockMvc
.perform(post("/oauth/token")
.header("Authorization", "Basic " +
new String(Base64Utils.encode((DEFAULT_CLIENT_ID + ":" + DEFAULT_CLIENT_SECRET).getBytes())))
.param("grant_type", "client_credentials")
.param("client_id", DEFAULT_CLIENT_ID)
.param("scope", "read"))
.andReturn().getResponse();
OAuth2AccessToken accessToken = null;
try {
accessToken = objectMapper.readValue(response.getContentAsByteArray(), OAuth2AccessToken.class);
} finally {
return accessToken;
}
}
private OAuthClient makeDefault() {
final OAuthClient client = new OAuthClient();
client.setClientId(DEFAULT_CLIENT_ID);
client.setClientSecret(DEFAULT_CLIENT_SECRET);
client.getAuthorizedGrantTypes().add("client_credentials");
client.getRoles().add(OAuthRole.CLIENT);
client.getScope().add("read");
return client;
}
}
/*
* Copyright 2017 Global Crop Diversity Trust
*
* 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.genesys.blocks.security.rest;
import java.util.List;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.hibernate4.Hibernate4Module;
import org.genesys.blocks.security.tests.ServiceTest;
import org.junit.runner.RunWith;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.ContextHierarchy;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
/**
* @author Matija Obreza
*/
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextHierarchy(@ContextConfiguration(name = "rest", classes = { AbstractRestTest.Config.class }))
public abstract class AbstractRestTest extends ServiceTest {
@Configuration
@EnableWebMvc
@DirtiesContext(hierarchyMode = HierarchyMode.CURRENT_LEVEL, classMode = DirtiesContext.ClassMode.AFTER_CLASS)
public static class Config extends WebMvcConfigurerAdapter {
public MappingJackson2HttpMessageConverter jacksonMessageConverter() {
final MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
final ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new Hibernate4Module());
// serialization
mapper.disable(SerializationFeature.EAGER_SERIALIZER_FETCH);
// deserialization
mapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
// ignore stuff we don't understand
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
messageConverter.setObjectMapper(mapper);
return messageConverter;
}
@Override
public void configureMessageConverters(final List<HttpMessageConverter<?>> converters) {
// Here we add our custom-configured HttpMessageConverter
converters.add(jacksonMessageConverter());
// Raw request body -- needed for uploading raw files
converters.add(new ByteArrayHttpMessageConverter());
super.configureMessageConverters(converters);
}
@Override
public void configureContentNegotiation(final ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false);
}
}
}
\ No newline at end of file
/*
* Copyright 2017 Global Crop Diversity Trust
*
* 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
*