Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spring boot starters for API and Gateway services #6

Merged
merged 7 commits into from
Jan 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ include 'thunx-model'
include 'thunx-pdp'
include 'thunx-pdp-opa'
include 'thunx-spring'
include 'thunx-autoconfigure'
include 'thunx-api-spring-boot-starter'
include 'thunx-gateway-spring-boot-starter'

include 'thunx-encoding-json'
include 'thunx-predicates-querydsl'
Expand All @@ -18,3 +21,4 @@ include 'thunx-predicates-querydsl'
// substitute module('eu.xenit.contentcloud:rego-java') with project(':rego-java')
// }
//}

14 changes: 14 additions & 0 deletions thunx-api-spring-boot-starter/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
plugins {
id 'java-library'
}

apply from: "${rootDir}/gradle/publish.gradle"

repositories {
mavenCentral()
}

dependencies {
api project(':thunx-spring')
api project(':thunx-autoconfigure')
}
47 changes: 47 additions & 0 deletions thunx-autoconfigure/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
plugins {
id 'java-library'
}

apply from: "${rootDir}/gradle/publish.gradle"

dependencies {

compileOnly platform("org.springframework.boot:spring-boot-dependencies:${springBootBomVersion}")
compileOnly platform("org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}")

implementation "org.springframework.boot:spring-boot-autoconfigure"

compileOnly "org.projectlombok:lombok"

compileOnly project(':thunx-spring')
compileOnly project(':thunx-pdp-opa')
compileOnly "eu.xenit.contentcloud:opa-async-java-client:0.2.0"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we externalize these versions ?


compileOnly 'org.springframework.security:spring-security-web'

compileOnly 'org.springframework.data:spring-data-rest-core'
compileOnly 'org.springframework.cloud:spring-cloud-starter-gateway'

annotationProcessor "org.springframework.boot:spring-boot-autoconfigure-processor:${springBootBomVersion}"
annotationProcessor "org.projectlombok:lombok:${lombokVersion}"

testImplementation platform("org.springframework.boot:spring-boot-dependencies:${springBootBomVersion}")
testImplementation platform("org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}")

testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.assertj:assertj-core:3.21.0'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These versions should be managed by the spring-boot platform ?

testImplementation 'org.mockito:mockito-core:2.1.0'
testImplementation "org.springframework.boot:spring-boot-test"
testImplementation "org.springframework.boot:spring-boot-starter-data-jpa"
testImplementation "org.springframework.boot:spring-boot-starter-data-rest"
testImplementation 'org.springframework.security:spring-security-web'
testImplementation 'org.springframework.cloud:spring-cloud-starter-gateway'
testImplementation 'com.h2database:h2:1.4.200'
testImplementation project(':thunx-spring')
testImplementation project(':thunx-pdp-opa')
testImplementation "eu.xenit.contentcloud:opa-async-java-client:0.2.0"
}

test {
useJUnitPlatform()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package eu.xenit.contentcloud.thunx.api.autoconfigure;

import eu.xenit.contentcloud.thunx.spring.data.EnableAbac;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;

@Configuration
@ConditionalOnClass(RepositoryRestResource.class)
public class AbacAutoConfiguration {

@Configuration
@EnableAbac
public static class EnableAbacAutoConfiguration {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package eu.xenit.contentcloud.thunx.gateway.autoconfigure;

import eu.xenit.contentcloud.opa.client.OpaClient;
import eu.xenit.contentcloud.opa.client.rest.RestClientConfiguration;
import eu.xenit.contentcloud.thunx.pdp.PolicyDecisionComponentImpl;
import eu.xenit.contentcloud.thunx.pdp.PolicyDecisionPointClient;
import eu.xenit.contentcloud.thunx.pdp.opa.OpaQueryProvider;
import eu.xenit.contentcloud.thunx.pdp.opa.OpenPolicyAgentPDPClient;
import eu.xenit.contentcloud.thunx.spring.gateway.filter.AbacGatewayFilterFactory;
import eu.xenit.contentcloud.thunx.spring.security.ReactivePolicyAuthorizationManager;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.web.server.authorization.AuthorizationContext;

@Configuration
@ConditionalOnClass({OpaClient.class, AbstractGatewayFilterFactory.class})
@EnableConfigurationProperties(OpaProperties.class)
public class GatewayAutoConfiguration {

@Bean
@ConditionalOnMissingBean
public OpaClient opaClient(OpaProperties opaProperties) {
return OpaClient.builder()
.httpLogging(RestClientConfiguration.LogSpecification::all)
.url(opaProperties.getService().getUrl())
.build();
}

@Bean
@ConditionalOnMissingBean
public OpaQueryProvider propertyBasedOpaQueryProvider(OpaProperties opaProperties) {
return request -> opaProperties.getQuery();
}

@Bean
@ConditionalOnMissingBean
public PolicyDecisionPointClient pdpClient(OpaClient opaClient, OpaQueryProvider queryProvider) {
return new OpenPolicyAgentPDPClient(opaClient, queryProvider);
}

@Bean
@ConditionalOnMissingBean
public ReactiveAuthorizationManager<AuthorizationContext> reactiveAuthenticationManager(
PolicyDecisionPointClient pdpClient) {
return new ReactivePolicyAuthorizationManager(new PolicyDecisionComponentImpl(pdpClient));
}

@Bean
@ConditionalOnMissingBean
public AbacGatewayFilterFactory abacGatewayFilterFactory() {
return new AbacGatewayFilterFactory();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package eu.xenit.contentcloud.thunx.gateway.autoconfigure;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

@Data
@ConfigurationProperties(prefix = "opa")
public class OpaProperties {
private OpaServiceProperties service;
private String query;

@Data
public static class OpaServiceProperties {
private String url;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
eu.xenit.contentcloud.thunx.api.autoconfigure.AbacAutoConfiguration,\
eu.xenit.contentcloud.thunx.gateway.autoconfigure.GatewayAutoConfiguration
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package eu.xenit.contentcloud.thunx.api.autoconfigure;

import eu.xenit.contentcloud.thunx.encoding.ThunkExpressionDecoder;
import eu.xenit.contentcloud.thunx.spring.data.rest.AbacExceptionHandler;
import eu.xenit.contentcloud.thunx.spring.data.rest.AbacRequestFilter;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.repository.support.Repositories;

import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.assertThat;

public class AbacAutoConfigurationTest {

@Test
public void shouldEnableAbac() {

ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(
AbacAutoConfiguration.class
));

contextRunner.withUserConfiguration(TestContext.class).run((context) -> {
assertThat(context.getBean(ThunkExpressionDecoder.class), is(not(nullValue())));
assertThat(context.getBean(AbacExceptionHandler.class), is(not(nullValue())));
assertThat(context.getBean(AbacRequestFilter.class), is(not(nullValue())));
assertThat(context.getBean("abacFilterRegistration"), is(not(nullValue())));
assertThat(context.getBean("interceptRepositoryRestMvcConfiguration"), is(not(nullValue())));
assertThat(context.getBean("ensureQueryDslPredication"), is(not(nullValue())));
});
}

@Configuration
@EnableAutoConfiguration(exclude={
org.springframework.cloud.gateway.config.GatewayAutoConfiguration.class,
eu.xenit.contentcloud.thunx.gateway.autoconfigure.GatewayAutoConfiguration.class
})
public static class TestContext {

@Bean
public Repositories repositories(ApplicationContext context) {
return new Repositories(context);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package eu.xenit.contentcloud.thunx.gateway.autoconfigure;

import eu.xenit.contentcloud.opa.client.OpaClient;
import eu.xenit.contentcloud.thunx.pdp.PolicyDecisionPointClient;
import eu.xenit.contentcloud.thunx.pdp.opa.OpaQueryProvider;
import eu.xenit.contentcloud.thunx.spring.gateway.filter.AbacGatewayFilterFactory;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.web.server.authorization.AuthorizationContext;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;

public class GatewayAutoConfigurationTest {

@Test
public void shouldEnableGatewayBeans() {

ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(
GatewayAutoConfiguration.class
));

contextRunner.withUserConfiguration(TestContext.class)
.withPropertyValues("opa.service.url=https://some/opa/service")
.run((context) -> {
assertThat(context.getBean(OpaClient.class)).isNotNull();
assertThat(context.getBean(OpaQueryProvider.class)).isNotNull();
assertThat(context.getBean(PolicyDecisionPointClient.class)).isNotNull();
assertThat(context.getBean(ReactiveAuthorizationManager.class)).isNotNull();
assertThat(context.getBean(AbacGatewayFilterFactory.class)).isNotNull();
});
}

@Test
public void shouldUseProvidedGatewayBeans() {

ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(
GatewayAutoConfiguration.class
));

contextRunner.withUserConfiguration(TestContextWithBeans.class)
.run((context) -> {
assertThat(context.getBean(OpaClient.class)).isSameAs(context.getBean(TestContextWithBeans.class).opaClient());
assertThat(context.getBean(OpaQueryProvider.class)).isSameAs(context.getBean(TestContextWithBeans.class).customQueryProvider());
assertThat(context.getBean(PolicyDecisionPointClient.class)).isSameAs(context.getBean(TestContextWithBeans.class).pdpClient());
assertThat(context.getBean(ReactiveAuthorizationManager.class)).isSameAs(context.getBean(TestContextWithBeans.class).reactiveAuthenticationManager());
assertThat(context.getBean(AbacGatewayFilterFactory.class)).isSameAs(context.getBean(TestContextWithBeans.class).abacGatewayFilterFactory());
});
}

@Configuration
@EnableAutoConfiguration(exclude={
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration.class,
org.springframework.cloud.gateway.config.GatewayAutoConfiguration.class,
eu.xenit.contentcloud.thunx.api.autoconfigure.AbacAutoConfiguration.class
})
public static class TestContext {
}

@Configuration
@EnableAutoConfiguration(exclude={
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration.class,
org.springframework.cloud.gateway.config.GatewayAutoConfiguration.class,
eu.xenit.contentcloud.thunx.api.autoconfigure.AbacAutoConfiguration.class
})
public static class TestContextWithBeans {

@Bean
public OpaClient opaClient() {
return mock(OpaClient.class);
}

@Bean
@ConditionalOnMissingBean
public PolicyDecisionPointClient pdpClient() {
return mock(PolicyDecisionPointClient.class);
}

@Bean
public ReactiveAuthorizationManager<AuthorizationContext> reactiveAuthenticationManager() {
return mock(ReactiveAuthorizationManager.class);
}

@Bean
public AbacGatewayFilterFactory abacGatewayFilterFactory() {
return mock(AbacGatewayFilterFactory.class);
}

@Bean
public OpaQueryProvider customQueryProvider() {
return mock(OpaQueryProvider.class);
}
}
}
14 changes: 14 additions & 0 deletions thunx-gateway-spring-boot-starter/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
plugins {
id 'java-library'
}

apply from: "${rootDir}/gradle/publish.gradle"

repositories {
mavenCentral()
}

dependencies {
api project(':thunx-pdp-opa')
api project(':thunx-autoconfigure')
}