Skip to content

Commit 98ed172

Browse files
committed
code refactoring
1 parent 53061a7 commit 98ed172

10 files changed

Lines changed: 25 additions & 162 deletions

File tree

contexa-autoconfigure/src/main/java/io/contexa/autoconfigure/compat/ContexaDefaultPropertiesPostProcessor.java

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -17,58 +17,24 @@
1717

1818
import org.springframework.boot.SpringApplication;
1919
import org.springframework.boot.env.EnvironmentPostProcessor;
20-
import org.springframework.boot.env.YamlPropertySourceLoader;
21-
import org.springframework.core.env.PropertySource;
2220
import org.springframework.core.env.ConfigurableEnvironment;
2321
import org.springframework.core.env.MapPropertySource;
24-
import org.springframework.core.io.ClassPathResource;
25-
import org.springframework.core.io.Resource;
2622

2723
import java.util.LinkedHashMap;
2824
import java.util.Arrays;
2925
import java.util.List;
3026
import java.util.Locale;
3127
import java.util.Map;
3228
import java.util.Set;
33-
import java.io.IOException;
3429

3530
public class ContexaDefaultPropertiesPostProcessor implements EnvironmentPostProcessor {
3631

3732
static final String SOURCE_NAME = "contexaDefaultProperties";
38-
static final String OVERLAY_RESOURCE = "application-contexa.yml";
3933

40-
private final Resource overlayResource;
41-
42-
public ContexaDefaultPropertiesPostProcessor() {
43-
this(new ClassPathResource(OVERLAY_RESOURCE));
44-
}
45-
46-
ContexaDefaultPropertiesPostProcessor(Resource overlayResource) {
47-
this.overlayResource = overlayResource;
48-
}
49-
5034
@Override
5135
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
52-
loadOwnedOverlay(environment);
5336
environment.getPropertySources().addLast(new MapPropertySource(SOURCE_NAME, defaults(environment)));
5437
}
55-
56-
private void loadOwnedOverlay(ConfigurableEnvironment environment) {
57-
if (!overlayResource.exists()) {
58-
return;
59-
}
60-
try {
61-
List<PropertySource<?>> sources =
62-
new YamlPropertySourceLoader().load("contexaOwnedOverlay", overlayResource);
63-
for (PropertySource<?> source : sources) {
64-
environment.getPropertySources().addLast(source);
65-
}
66-
} catch (IOException exception) {
67-
throw new IllegalStateException(
68-
"Contexa-owned configuration overlay could not be loaded: " + OVERLAY_RESOURCE,
69-
exception);
70-
}
71-
}
7238

7339
private Map<String, Object> defaults(ConfigurableEnvironment environment) {
7440
EmbeddingDimensionResolver.ResolvedDimension dimension =

contexa-autoconfigure/src/main/java/io/contexa/autoconfigure/core/infra/StandaloneAutoConfigurationFilter.java

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,6 @@ public class StandaloneAutoConfigurationFilter implements AutoConfigurationImpor
3535
private static final String CONTEXA_PACKAGE_PREFIX = "io.contexa.";
3636
private static final String CONTEXA_OWNED_DATASOURCE_AUTO_CONFIGURATION =
3737
"io.contexa.autoconfigure.core.ContexaOwnedDataSourceAutoConfiguration";
38-
private static final String DEPENDENCY_ONLY_SECURITY_ISOLATION_AUTO_CONFIGURATION =
39-
"io.contexa.autoconfigure.identity.DependencyOnlySecurityIsolationAutoConfiguration";
4038

4139
private Environment environment;
4240

@@ -55,9 +53,7 @@ public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetad
5553
}
5654

5755
if (!contexaPlatformActive) {
58-
if (isDependencyOnlySecurityIsolationAutoConfiguration(autoConfigurationClass)) {
59-
result[i] = true;
60-
} else if (isContexaOwnedDataSourceAutoConfiguration(autoConfigurationClass)) {
56+
if (isContexaOwnedDataSourceAutoConfiguration(autoConfigurationClass)) {
6157
result[i] = hasContexaOwnedDataSource();
6258
} else if (isContexaAutoConfiguration(autoConfigurationClass)) {
6359
result[i] = false;
@@ -103,10 +99,6 @@ private boolean isContexaOwnedDataSourceAutoConfiguration(String autoConfigurati
10399
return CONTEXA_OWNED_DATASOURCE_AUTO_CONFIGURATION.equals(autoConfigurationClass);
104100
}
105101

106-
private boolean isDependencyOnlySecurityIsolationAutoConfiguration(String autoConfigurationClass) {
107-
return DEPENDENCY_ONLY_SECURITY_ISOLATION_AUTO_CONFIGURATION.equals(autoConfigurationClass);
108-
}
109-
110102
private boolean hasContexaOwnedDataSource() {
111103
return environment != null
112104
&& environment.containsProperty("contexa.datasource.url")

contexa-autoconfigure/src/main/java/io/contexa/autoconfigure/identity/DependencyOnlySecurityIsolationAutoConfiguration.java

Lines changed: 0 additions & 40 deletions
This file was deleted.

contexa-autoconfigure/src/main/java/io/contexa/autoconfigure/identity/IdentityOAuth2AutoConfiguration.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
import org.springframework.boot.autoconfigure.AutoConfiguration;
7676
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
7777
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
78+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
7879
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
7980
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
8081
import org.springframework.context.annotation.Bean;
@@ -121,6 +122,7 @@
121122
@Slf4j
122123
@AutoConfiguration
123124
@AutoConfigureAfter({IdentitySecurityCoreAutoConfiguration.class, CoreDataAutoConfiguration.class})
125+
@ConditionalOnClass(name = "org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository")
124126
@ConditionalOnBean(value = PlatformConfig.class, name = {"contexaJdbcTemplate", "contexaTransactionTemplate"})
125127
@ConditionalOnProperty(prefix = "contexa.bridge", name = "ownership", havingValue = "CONTEXA_OWNED")
126128
public class IdentityOAuth2AutoConfiguration {

contexa-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ io.contexa.autoconfigure.identity.IdentityStateMachineAutoConfiguration
5151
io.contexa.autoconfigure.identity.IdentityMfaAutoConfiguration
5252

5353
# Identity Security Core
54-
io.contexa.autoconfigure.identity.DependencyOnlySecurityIsolationAutoConfiguration
5554
io.contexa.autoconfigure.identity.IdentitySecurityCoreAutoConfiguration
5655

5756
# Identity ASEP (Security Exception Handler)

contexa-autoconfigure/src/test/java/io/contexa/autoconfigure/compat/ContexaDefaultPropertiesPostProcessorTest.java

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,6 @@
2121
import org.springframework.boot.env.EnvironmentPostProcessor;
2222
import org.springframework.core.io.support.SpringFactoriesLoader;
2323
import org.springframework.mock.env.MockEnvironment;
24-
import org.springframework.core.io.ByteArrayResource;
25-
26-
import java.nio.charset.StandardCharsets;
2724

2825
import static org.assertj.core.api.Assertions.assertThat;
2926

@@ -109,33 +106,6 @@ void keepsExplicitOllamaRuntimeOnProductDimension() {
109106
assertThat(environment.getProperty("spring.ai.model.audio.transcription")).isEqualTo("none");
110107
}
111108

112-
@Test
113-
@DisplayName("loads the Contexa-owned overlay below host properties and above module defaults")
114-
void loadsOwnedOverlayWithoutOverridingHostProperties() {
115-
ByteArrayResource overlay = new ByteArrayResource("""
116-
server:
117-
port: 9080
118-
contexa:
119-
security:
120-
zerotrust:
121-
mode: ENFORCE
122-
spring:
123-
ai:
124-
vectorstore:
125-
pgvector:
126-
dimensions: 2048
127-
""".getBytes(StandardCharsets.UTF_8));
128-
MockEnvironment environment = new MockEnvironment()
129-
.withProperty("server.port", "9191");
130-
131-
new ContexaDefaultPropertiesPostProcessor(overlay)
132-
.postProcessEnvironment(environment, new SpringApplication());
133-
134-
assertThat(environment.getProperty("server.port")).isEqualTo("9191");
135-
assertThat(environment.getProperty("contexa.security.zerotrust.mode")).isEqualTo("ENFORCE");
136-
assertThat(environment.getProperty("spring.ai.vectorstore.pgvector.dimensions")).isEqualTo("2048");
137-
}
138-
139109
@Test
140110
@DisplayName("keeps multi-provider chat dynamic while selecting the fixed embedding provider")
141111
void keepsMultiProviderChatDynamic() {

contexa-autoconfigure/src/test/java/io/contexa/autoconfigure/core/infra/StandaloneAutoConfigurationFilterTest.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,6 @@ class ContexaPlatformActivation {
174174
void shouldExcludeContexaAutoConfigurationsWhenPlatformIsInactive() {
175175
StandaloneAutoConfigurationFilter filter = createFilter("standalone");
176176
String[] classes = {
177-
"io.contexa.autoconfigure.identity.DependencyOnlySecurityIsolationAutoConfiguration",
178177
"io.contexa.autoconfigure.core.CoreDataAutoConfiguration",
179178
"io.contexa.contexacommon.cache.ContexaCacheAutoConfiguration",
180179
"io.contexa.contexacore.config.CoreSecurityAutoConfiguration",
@@ -184,7 +183,7 @@ void shouldExcludeContexaAutoConfigurationsWhenPlatformIsInactive() {
184183

185184
boolean[] result = filter.match(classes, metadata);
186185

187-
assertThat(result).containsExactly(true, false, false, false, false, true);
186+
assertThat(result).containsExactly(false, false, false, false, true);
188187
}
189188

190189
@Test

contexa-autoconfigure/src/test/java/io/contexa/autoconfigure/identity/IdentityOAuth2AutoConfigurationTest.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
import io.contexa.contexaidentity.security.core.adapter.state.oauth2.grant.AuthenticatedUserGrantAuthenticationToken;
2222
import org.junit.jupiter.api.DisplayName;
2323
import org.junit.jupiter.api.Test;
24+
import org.springframework.boot.autoconfigure.AutoConfigurations;
25+
import org.springframework.boot.test.context.FilteredClassLoader;
26+
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
2427
import org.springframework.security.oauth2.client.registration.ClientRegistration;
2528
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
2629
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
@@ -35,6 +38,16 @@
3538

3639
class IdentityOAuth2AutoConfigurationTest {
3740

41+
@Test
42+
@DisplayName("Missing authorization server should not break a dependency-only application context")
43+
void missingAuthorizationServerDoesNotBreakDependencyOnlyContext() {
44+
new ApplicationContextRunner()
45+
.withConfiguration(AutoConfigurations.of(IdentityOAuth2AutoConfiguration.class))
46+
.withClassLoader(new FilteredClassLoader(
47+
"org.springframework.security.oauth2.server.authorization"))
48+
.run(context -> assertThat(context.getStartupFailure()).isNull());
49+
}
50+
3851
@Test
3952
@DisplayName("JWK fallback should be allowed for the internal token engine")
4053
void jwkFallbackAllowedForInternalTokenEngine() {

spring-boot-starter-contexa/build.gradle

Lines changed: 7 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,13 @@ dependencies {
1111
api platform("io.github.resilience4j:resilience4j-bom:2.1.0")
1212
api platform("org.springframework.statemachine:spring-statemachine-bom:4.0.0")
1313

14-
// Host security remains host-owned. Applications that explicitly activate
15-
// @EnableAISecurity declare Spring Security themselves.
14+
// Spring Security runtime required by the OSS identity and IAM modules.
15+
// Auto-configuration activation, not dependency removal, protects HOST_OWNED applications.
16+
api 'org.springframework.boot:spring-boot-starter-security'
17+
api 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
18+
api 'org.springframework.boot:spring-boot-starter-oauth2-client'
19+
api 'org.springframework.security:spring-security-oauth2-authorization-server:1.5.1'
20+
api 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'
1621
// Spring AI provider starters are application-level choices and are not forced by the library starter
1722

1823
// PgVector implementation only. Do not expose Spring AI's PgVector starter because
@@ -24,7 +29,6 @@ dependencies {
2429
compileOnly 'org.springframework.boot:spring-boot-starter-data-redis'
2530
testImplementation 'org.springframework.boot:spring-boot-starter-data-redis'
2631

27-
testImplementation 'org.springframework.boot:spring-boot-starter-security'
2832
testImplementation 'org.springframework.security:spring-security-test'
2933
testImplementation 'org.springframework.boot:spring-boot-starter-webflux'
3034
testImplementation 'org.awaitility:awaitility:4.2.0'
@@ -102,31 +106,3 @@ tasks.named('processResources') {
102106
exclude 'static/js/token-test.js'
103107
exclude 'static/css/token-test.css'
104108
}
105-
106-
tasks.register('verifyDependencyOnlyRuntimeClasspath') {
107-
group = 'verification'
108-
description = 'Fails when the dependency-only starter exports host Spring Security.'
109-
doLast {
110-
def forbidden = configurations.runtimeClasspath.resolvedConfiguration.resolvedArtifacts.findAll { artifact ->
111-
def id = artifact.moduleVersion.id
112-
id.group == 'org.springframework.security'
113-
|| (id.group == 'org.springframework.boot' && id.name in [
114-
'spring-boot-starter-security',
115-
'spring-boot-starter-oauth2-client',
116-
'spring-boot-starter-oauth2-resource-server'
117-
])
118-
|| (id.group == 'org.thymeleaf.extras'
119-
&& id.name == 'thymeleaf-extras-springsecurity6')
120-
}
121-
if (!forbidden.isEmpty()) {
122-
def coordinates = forbidden.collect {
123-
"${it.moduleVersion.id.group}:${it.name}:${it.moduleVersion.id.version}"
124-
}.sort().join(', ')
125-
throw new GradleException("Dependency-only starter exports host security artifacts: ${coordinates}")
126-
}
127-
}
128-
}
129-
130-
tasks.named('check') {
131-
dependsOn tasks.named('verifyDependencyOnlyRuntimeClasspath')
132-
}

spring-boot-starter-contexa/src/test/java/io/contexa/springbootstartercontexa/DependencyOnlySecurityIsolationIntegrationTest.java

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import io.contexa.autoconfigure.identity.IdentitySecurityCoreAutoConfiguration;
99
import io.contexa.contexacommon.security.bridge.web.BridgeResolutionFilter;
1010
import io.contexa.contexaidentity.security.core.bootstrap.SecurityPlatformInitializer;
11-
import io.contexa.contexaidentity.security.core.config.PlatformConfig;
1211
import jakarta.servlet.Filter;
1312
import org.junit.jupiter.api.Test;
1413
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
@@ -74,30 +73,17 @@ class DependencyOnlySecurityIsolationIntegrationTest {
7473
.withUserConfiguration(DependencyOnlyApplication.class);
7574

7675
@Test
77-
void dependencyOnlyDoesNotSecureHostRequestsOrLoadContexaSecurity() {
76+
void dependencyOnlyKeepsBootDefaultSecurityChainWithoutContexaFilters() {
7877
contextRunner.run(context -> {
7978
assertThat(context).hasNotFailed();
8079
assertThat(context).hasSingleBean(SecurityFilterChain.class);
81-
assertThat(context).doesNotHaveBean(PlatformConfig.class);
82-
assertThat(context.getBeansOfType(SecurityFilterChain.class))
83-
.containsOnlyKeys("contexaDependencyOnlyIsolationFilterChain");
8480
assertThat(context).doesNotHaveBean("securityFilterChain");
8581
assertThat(context).doesNotHaveBean(IdentitySecurityCoreAutoConfiguration.class);
8682
assertThat(context).doesNotHaveBean(SecurityPlatformInitializer.class);
8783
assertThat(context).doesNotHaveBean(BridgeResolutionFilter.class);
8884

8985
SecurityFilterChain chain = context.getBean(SecurityFilterChain.class);
9086
assertNoContexaFilters(chain);
91-
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context.getSourceApplicationContext())
92-
.apply(springSecurity()).build();
93-
try {
94-
int status = mockMvc.perform(get("/host-route-not-mapped"))
95-
.andReturn().getResponse().getStatus();
96-
assertThat(status).isEqualTo(404);
97-
}
98-
catch (Exception exception) {
99-
throw new AssertionError("Dependency-only host request must bypass security", exception);
100-
}
10187
});
10288
}
10389

0 commit comments

Comments
 (0)