Skip to content
This repository has been archived by the owner on May 16, 2023. It is now read-only.

Chore: Update CWA-Parent to 2.0.2 #137

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
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
2 changes: 1 addition & 1 deletion .github/workflows/ci-master.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
restore-keys: ${{ env.cache-name }}-
- uses: actions/setup-java@v1
with:
java-version: 11
java-version: 17
- name: environment
run: |
sudo apt-get install --yes --no-install-recommends libxml-xpath-perl
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci-pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
restore-keys: ${{ env.cache-name }}-
- uses: actions/setup-java@v1
with:
java-version: 11
java-version: 17
- name: mvn package
run: mvn --batch-mode package
env:
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM gcr.io/distroless/java-debian10:11
FROM gcr.io/distroless/java17-debian11:latest
COPY target/*.jar app.jar
COPY scripts/Dpkg.java Dpkg.java
RUN ["java", "Dpkg.java"]
Expand Down
7 changes: 5 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<parent>
<groupId>app.coronawarn</groupId>
<artifactId>cwa-parent</artifactId>
<version>1.8</version>
<version>2.0.2</version>
</parent>

<name>cwa-verification-portal</name>
Expand Down Expand Up @@ -72,7 +72,10 @@
<version>${project.parent.version}</version>
<type>pom</type>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,10 @@

package app.coronawarn.verification.portal;

import org.apache.coyote.http11.AbstractHttp11Protocol;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;

/**
* The Spring Boot application class.
Expand All @@ -43,11 +38,11 @@ public static void main(String[] args) {
SpringApplication.run(VerificationPortalApplication.class, args);
}

/**
/*
* Enable the cipher suites from server to be preferred.
*
* @return the WebServerFactoryCustomizer with cipher suites configuration
*/
*
@Bean
@ConditionalOnProperty(value = "server.ssl.cipher.suites.order", havingValue = "true")
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> webServerFactoryCustomizer() {
Expand All @@ -56,6 +51,6 @@ public WebServerFactoryCustomizer<TomcatServletWebServerFactory> webServerFactor
((AbstractHttp11Protocol<?>) connector.getProtocolHandler())
.setUseServerCipherSuitesOrder(true)
);
}
}*/

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@

package app.coronawarn.verification.portal;

import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package app.coronawarn.verification.portal.config;


import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

@Component
public class KeycloakLogoutHandler implements LogoutHandler {

private static final Logger logger = LoggerFactory.getLogger(KeycloakLogoutHandler.class);
private final RestTemplate restTemplate;

public KeycloakLogoutHandler() {
this.restTemplate = new RestTemplate();
}

@Override
public void logout(HttpServletRequest request, HttpServletResponse response,
Authentication auth) {
logoutFromKeycloak((OidcUser) auth.getPrincipal());
}

private void logoutFromKeycloak(OidcUser user) {
String endSessionEndpoint = user.getIssuer() + "/protocol/openid-connect/logout";
UriComponentsBuilder builder = UriComponentsBuilder
.fromUriString(endSessionEndpoint)
.queryParam("id_token_hint", user.getIdToken().getTokenValue());

ResponseEntity<String> logoutResponse = restTemplate.getForEntity(
builder.toUriString(), String.class);
if (logoutResponse.getStatusCode().is2xxSuccessful()) {
logger.info("Successfulley logged out from Keycloak");
} else {
logger.error("Could not propagate logout to Keycloak");
}
}

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* Corona-Warn-App / cwa-verification-portal
* Corona-Warn-App / cwa-log-upload
*
* (C) 2020 - 2022, T-Systems International GmbH
* (C) 2021 - 2022, T-Systems International GmbH
*
* Deutsche Telekom AG and all other contributors /
* copyright owners license this file to you under the Apache
Expand All @@ -23,83 +23,85 @@

import app.coronawarn.verification.portal.VerificationPortalHttpFilter;
import app.coronawarn.verification.portal.controller.VerificationPortalController;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver;
import org.keycloak.adapters.springsecurity.KeycloakSecurityComponents;
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider;
import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter;
import org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.session.MapSessionRepository;
import org.springframework.session.SessionRepository;
import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession;
import org.springframework.session.web.http.CookieSerializer;
import org.springframework.session.web.http.DefaultCookieSerializer;


@Slf4j
@EnableSpringHttpSession
@Configuration
@EnableWebSecurity
@ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
@RequiredArgsConstructor
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
@Profile("!test")
public class OAuth2SecurityConfig {

public static final String ROLE_C19HOTLINE = "c19hotline";
public static final String ROLE_C19HOTLINE_EVENT = "c19hotline_event";
private static final String ACTUATOR_ROUTE = "/actuator/**";

private static final String REALM_ACCESS_CLAIM = "realm_access";
private static final String ROLES_CLAIM = "roles";
private static final String SAMESITE_LAX = "Lax";
private static final String OAUTH_TOKEN_REQUEST_STATE_COOKIE = "OAuth_Token_Request_State";
private static final String SESSION_COOKIE = "SESSION";

private final VerificationPortalHttpFilter verificationPortalHttpFilter;

/**
* Configures Keycloak.
*/
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
private final KeycloakLogoutHandler keycloakLogoutHandler;

@Bean
@Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}

@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
/**
* filter Chain.
*/
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.addFilterBefore(verificationPortalHttpFilter, KeycloakAuthenticationProcessingFilter.class)
.addFilterBefore(verificationPortalHttpFilter, BasicAuthenticationFilter.class)
.headers().addHeaderWriter(this::addSameSiteToOAuthCookie)
.and()
.authorizeRequests()
.mvcMatchers(HttpMethod.GET, ACTUATOR_ROUTE).permitAll()
.antMatchers(VerificationPortalController.ROUTE_TELETAN)
.hasAnyRole(ROLE_C19HOTLINE, ROLE_C19HOTLINE_EVENT)
.authorizeHttpRequests()
.requestMatchers(HttpMethod.GET, ACTUATOR_ROUTE).permitAll()
.requestMatchers(VerificationPortalController.ROUTE_TELETAN).hasAnyRole(ROLE_C19HOTLINE, ROLE_C19HOTLINE_EVENT)
.anyRequest().authenticated();

http.oauth2Login()
.and()
.logout()
.addLogoutHandler(keycloakLogoutHandler)
.logoutSuccessUrl("/");

return http.build();
}

/**
Expand Down Expand Up @@ -132,4 +134,41 @@ private String addSameSiteStrict(String setCookie) {
return setCookie + "; SameSite=" + SAMESITE_LAX;
}

/**
* GrantedAuthoritiesMapper.
*/
@Bean
@SuppressWarnings("unchecked")
public GrantedAuthoritiesMapper userAuthoritiesMapperForKeycloak() {
return authorities -> {
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
var authority = authorities.iterator().next();
boolean isOidc = authority instanceof OidcUserAuthority;

if (isOidc) {
var oidcUserAuthority = (OidcUserAuthority) authority;
var userInfo = oidcUserAuthority.getUserInfo();

if (userInfo.hasClaim(REALM_ACCESS_CLAIM)) {
var realmAccess = userInfo.getClaimAsMap(REALM_ACCESS_CLAIM);
var roles = (Collection<String>) realmAccess.get(ROLES_CLAIM);
mappedAuthorities.addAll(generateAuthoritiesFromClaim(roles));
}
} else {
var oauth2UserAuthority = (OAuth2UserAuthority) authority;
Map<String, Object> userAttributes = oauth2UserAuthority.getAttributes();

if (userAttributes.containsKey(REALM_ACCESS_CLAIM)) {
var realmAccess = (Map<String, Object>) userAttributes.get(REALM_ACCESS_CLAIM);
var roles = (Collection<String>) realmAccess.get(ROLES_CLAIM);
mappedAuthorities.addAll(generateAuthoritiesFromClaim(roles));
}
}
return mappedAuthorities;
};
}

Collection<GrantedAuthority> generateAuthoritiesFromClaim(Collection<String> roles) {
return roles.stream().map(role -> new SimpleGrantedAuthority("ROLE_" + role)).collect(Collectors.toList());
}
}
Loading