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

Feature/pmd and spotbugs gateway #175

Merged
merged 5 commits into from
Sep 20, 2024
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
1 change: 1 addition & 0 deletions .github/workflows/dependency_review.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ jobs:
uses: actions/dependency-review-action@5a2ce3f5b92ee19cbb1541a4984c76d921601d7c # v4.3.4
with:
config-file: it-at-m/.github/workflow-configs/dependency_review.yaml@main
allow-dependencies-licenses: 'pkg:npm/escape-string-regexp, pkg:npm/path-exists, pkg:npm/slash, pkg:npm/yocto-queue, pkg:npm/load-script, pkg:npm/node-forge, pkg:maven/com.puppycrawl.tools/checkstyle, pkg:maven/com.hazelcast/hazelcast-spring, pkg:maven/com.github.spotbugs/spotbugs-annotations, pkg:maven/com.h3xstream.findsecbugs/findsecbugs-plugin'
55 changes: 55 additions & 0 deletions refarch-gateway/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@
<!-- Linting & Formatting -->
<spotless-maven-plugin.version>2.34.0</spotless-maven-plugin.version>
<itm-java-codeformat.version>1.0.10</itm-java-codeformat.version>
<pmd-maven-plugin.version>3.25.0</pmd-maven-plugin.version>
<spotbugs-maven-plugin.version>4.8.6.2</spotbugs-maven-plugin.version>
<spotbugs-annotations.version>4.8.6</spotbugs-annotations.version>
<findsecbugs-plugin.version>1.13.0</findsecbugs-plugin.version>

<!-- Testing -->
<jacoco.version>0.8.12</jacoco.version>
Expand Down Expand Up @@ -237,6 +241,57 @@
</execution>
</executions>
</plugin>

<!-- Linting -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-pmd-plugin</artifactId>
<version>${pmd-maven-plugin.version}</version>
<configuration>
<minimumTokens>100</minimumTokens>
<targetJdk>${java.version}</targetJdk>
<printFailingErrors>true</printFailingErrors>
<linkXRef>false</linkXRef>
<includeTests>true</includeTests>
<excludeRoots>
<excludeRoot>target/generated-sources</excludeRoot>
<excludeRoot>target/generated-test-sources</excludeRoot>
</excludeRoots>
<rulesets>
<ruleset>../shared-files/pmd-ruleset.xml</ruleset>
</rulesets>
</configuration>
<executions>
<execution>
<goals>
<goal>check</goal>
<goal>cpd-check</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-maven-plugin</artifactId>
<version>${spotbugs-maven-plugin.version}</version>
<configuration>
<effort>Max</effort>
<plugins>
<plugin>
<groupId>com.h3xstream.findsecbugs</groupId>
<artifactId>findsecbugs-plugin</artifactId>
<version>${findsecbugs-plugin.version}</version>
</plugin>
</plugins>
</configuration>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
@EnableConfigurationProperties(SecurityProperties.class)
public class ApiGatewayApplication {

public static void main(String[] args) {
public static void main(final String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,16 @@ public class CsrfProtectionMatcher implements ServerWebExchangeMatcher {
private final SecurityProperties securityProperties;

@Override
public Mono<MatchResult> matches(ServerWebExchange exchange) {
public Mono<MatchResult> matches(final ServerWebExchange exchange) {
return Mono.just(exchange.getRequest())
.flatMap((r) -> Mono.justOrEmpty(new MethodAndPath(r.getMethod(), r.getPath().toString())))
.filter((mp) -> ALLOWED_METHODS.contains(mp.method) || isWhitelisted(mp.path))
.flatMap((m) -> MatchResult.notMatch())
.switchIfEmpty(MatchResult.match());
}

private boolean isWhitelisted(String path) {
for (String whitelisted : securityProperties.getCsrfWhitelisted()) {
private boolean isWhitelisted(final String path) {
for (final String whitelisted : securityProperties.getCsrfWhitelisted()) {
if (new AntPathMatcher().match(whitelisted, path)) {
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
public class NoSecurityConfiguration {

@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
public SecurityWebFilterChain springSecurityFilterChain(final ServerHttpSecurity http) {
return http
.authorizeExchange(authorizeExchangeSpec -> authorizeExchangeSpec.anyExchange().permitAll())
.cors(corsSpec -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public class SecurityConfiguration {

@Bean
@Order(0)
public SecurityWebFilterChain clientAccessFilterChain(ServerHttpSecurity http) {
public SecurityWebFilterChain clientAccessFilterChain(final ServerHttpSecurity http) {
http
.securityMatcher(ServerWebExchangeMatchers.pathMatchers("/clients/**"))
.authorizeExchange(authorizeExchangeSpec -> authorizeExchangeSpec
Expand All @@ -47,7 +47,7 @@ public SecurityWebFilterChain clientAccessFilterChain(ServerHttpSecurity http) {

@Bean
@Order(1)
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
public SecurityWebFilterChain springSecurityFilterChain(final ServerHttpSecurity http) {
http
.logout(ServerHttpSecurity.LogoutSpec::disable)
.authorizeExchange(authorizeExchangeSpec -> {
Expand Down Expand Up @@ -83,7 +83,7 @@ public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http)
})
.oauth2Login(oAuth2LoginSpec -> oAuth2LoginSpec.authenticationSuccessHandler(new RedirectServerAuthenticationSuccessHandler() {
@Override
public Mono<Void> onAuthenticationSuccess(WebFilterExchange webFilterExchange, Authentication authentication) {
public Mono<Void> onAuthenticationSuccess(final WebFilterExchange webFilterExchange, final Authentication authentication) {
webFilterExchange.getExchange().getSession().subscribe(
webSession -> webSession.setMaxIdleTime(Duration.ofSeconds(springSessionTimeoutSeconds)));
return super.onAuthenticationSuccess(webFilterExchange, authentication);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
package de.muenchen.refarch.gateway.configuration;

import java.util.List;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;

@Getter
@Setter
@ConfigurationProperties("refarch.security")
public class SecurityProperties {
/**
* List of url patterns excluded from csrf protection.
*/
private List<String> csrfWhitelisted = List.of();
private final List<String> csrfWhitelisted = List.of();

public List<String> getCsrfWhitelisted() {
return List.copyOf(this.csrfWhitelisted);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public class SpaServerCsrfTokenRequestHandler extends ServerCsrfTokenRequestAttr
private final ServerCsrfTokenRequestHandler delegate = new XorServerCsrfTokenRequestAttributeHandler();

@Override
public void handle(ServerWebExchange exchange, Mono<CsrfToken> csrfToken) {
public void handle(final ServerWebExchange exchange, final Mono<CsrfToken> csrfToken) {
/*
* Always use XorCsrfTokenRequestAttributeHandler to provide BREACH protection of
* the CsrfToken when it is rendered in the response body.
Expand All @@ -20,7 +20,7 @@ public void handle(ServerWebExchange exchange, Mono<CsrfToken> csrfToken) {
}

@Override
public Mono<String> resolveCsrfTokenValue(ServerWebExchange exchange, CsrfToken csrfToken) {
public Mono<String> resolveCsrfTokenValue(final ServerWebExchange exchange, final CsrfToken csrfToken) {
/*
* If the request contains a request header, use CsrfTokenRequestAttributeHandler
* to resolve the CsrfToken. This applies when a single-page application includes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

import com.hazelcast.config.Config;
import com.hazelcast.config.EvictionPolicy;
import com.hazelcast.config.JoinConfig;
import com.hazelcast.config.MapConfig;
import com.hazelcast.config.NetworkConfig;
import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.map.IMap;
Expand Down Expand Up @@ -46,7 +48,7 @@ public ServerOAuth2AuthorizedClientRepository authorizedClientRepository() {
}

@Bean
public ReactiveSessionRepository<MapSession> reactiveSessionRepository(@Autowired HazelcastInstance hazelcastInstance) {
public ReactiveSessionRepository<MapSession> reactiveSessionRepository(@Autowired final HazelcastInstance hazelcastInstance) {
final IMap<String, Session> map = hazelcastInstance.getMap(HazelcastIndexedSessionRepository.DEFAULT_SESSION_MAP_NAME);
return new ReactiveMapSessionRepository(map);
}
Expand All @@ -60,28 +62,28 @@ public HazelcastInstance hazelcastInstance(@Autowired final Config config) {
@Profile({ "hazelcast-local" })
public Config localConfig(@Value(
"${spring.session.timeout}"
) int timeout) {
final var hazelcastConfig = new Config();
) final int timeout) {
final Config hazelcastConfig = new Config();
hazelcastConfig.setInstanceName(hazelcastInstanceName);
hazelcastConfig.setClusterName(groupConfigName);

addSessionTimeoutToHazelcastConfig(hazelcastConfig, timeout);

final var networkConfig = hazelcastConfig.getNetworkConfig();
final NetworkConfig networkConfig = hazelcastConfig.getNetworkConfig();

final var joinConfig = networkConfig.getJoin();
final JoinConfig joinConfig = networkConfig.getJoin();
joinConfig.getMulticastConfig().setEnabled(false);
joinConfig.getTcpIpConfig()
.setEnabled(true)
.addMember("127.0.0.1");
.addMember("localhost");

return hazelcastConfig;
}

@Bean
@Profile({ "hazelcast-k8s" })
public Config config(@Value("${spring.session.timeout}") int timeout) {
final var hazelcastConfig = new Config();
public Config config(@Value("${spring.session.timeout}") final int timeout) {
final Config hazelcastConfig = new Config();
hazelcastConfig.setInstanceName(hazelcastInstanceName);
hazelcastConfig.setClusterName(groupConfigName);

Expand All @@ -107,7 +109,7 @@ public Config config(@Value("${spring.session.timeout}") int timeout) {
* @param sessionTimeout for security session.
*/
private void addSessionTimeoutToHazelcastConfig(final Config hazelcastConfig, final int sessionTimeout) {
final var sessionConfig = new MapConfig();
final MapConfig sessionConfig = new MapConfig();
sessionConfig.setName(HazelcastIndexedSessionRepository.DEFAULT_SESSION_MAP_NAME);
sessionConfig.setTimeToLiveSeconds(sessionTimeout);
sessionConfig.getEvictionConfig().setEvictionPolicy(EvictionPolicy.LRU);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package de.muenchen.refarch.gateway.exception;

import de.muenchen.refarch.gateway.filter.GlobalRequestParameterPollutionFilter;
import java.io.Serial;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
Expand All @@ -19,5 +20,7 @@
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class ParameterPollutionException extends RuntimeException {
@Serial
private static final long serialVersionUID = 1L;
// default Ctor
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@
@Slf4j
public class CsrfTokenAppendingHelperFilter implements WebFilter {

public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
@Override
public Mono<Void> filter(final ServerWebExchange exchange, final WebFilterChain chain) {
log.debug("Trigger to append CSRF token to response");
Mono<CsrfToken> csrfToken = exchange.getAttributeOrDefault(CsrfToken.class.getName(), Mono.empty());
final Mono<CsrfToken> csrfToken = exchange.getAttributeOrDefault(CsrfToken.class.getName(), Mono.empty());
return csrfToken.doOnSuccess(token -> {
// do nothing -> CSRF-Token is added as cookie in class CookieServerCsrfTokenRepository#saveToken
}).then(chain.filter(exchange));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package de.muenchen.refarch.gateway.filter;

import io.micrometer.tracing.Span;
import io.micrometer.tracing.Tracer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
Expand Down Expand Up @@ -34,13 +35,13 @@ public class DistributedTracingFilter implements WebFilter {
* complete
*/
@Override
public Mono<Void> filter(ServerWebExchange serverWebExchange,
WebFilterChain webFilterChain) {
ServerHttpResponse response = serverWebExchange.getResponse();
public Mono<Void> filter(final ServerWebExchange serverWebExchange,
final WebFilterChain webFilterChain) {
final ServerHttpResponse response = serverWebExchange.getResponse();
response.beforeCommit(() -> {
var span = tracer.currentSpan();
final Span span = tracer.currentSpan();
if (span != null) {
HttpHeaders headers = response.getHeaders();
final MultiValueMap<String, String> headers = response.getHeaders();
headers.add(TRACE_ID, span.context().traceId());
headers.add(SPAN_ID, span.context().spanId());
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public Mono<Void> filter(final ServerWebExchange exchange, final GatewayFilterCh
log.debug("Check for authentication errors");
final HttpStatus httpStatus = HttpStatus.UNAUTHORIZED;
final String newResponseBody = GENERIC_AUTHENTICATION_ERROR;
final String EMPTY_JSON_OBJECT = "{}";
final String emptyJsonObject = "{}";

final ServerHttpResponse response = exchange.getResponse();

Expand All @@ -63,12 +63,12 @@ public Mono<Void> filter(final ServerWebExchange exchange, final GatewayFilterCh
*/
@Override
@NonNull
public Mono<Void> writeWith(@NonNull Publisher<? extends DataBuffer> body) {
public Mono<Void> writeWith(@NonNull final Publisher<? extends DataBuffer> body) {
final HttpStatusCode responseHttpStatus = getDelegate().getStatusCode();
if (body instanceof Flux<? extends DataBuffer> flux && responseHttpStatus.equals(httpStatus)) {
final DataBufferFactory dataBufferFactory = response.bufferFactory();
final DataBuffer newDataBuffer = dataBufferFactory.wrap(
ObjectUtils.defaultIfNull(newResponseBody, EMPTY_JSON_OBJECT).getBytes(StandardCharsets.UTF_8));
ObjectUtils.defaultIfNull(newResponseBody, emptyJsonObject).getBytes(StandardCharsets.UTF_8));

log.debug("Response from upstream {} get new response body: {}", httpStatus, newResponseBody);
getDelegate().getHeaders().setContentLength(newDataBuffer.readableByteCount());
Expand Down
Loading