diff --git a/docs/en/security/authentication.md b/docs/en/security/authentication.md index b595ea3e..55fadd69 100644 --- a/docs/en/security/authentication.md +++ b/docs/en/security/authentication.md @@ -5,5 +5,5 @@ Connexion to the identity provider (an Open LDAP directory) is made in this clas ### SecurityContext.java -[include](../../../src/main/java/fr/insee/pogues/config/SecurityContext.java) +[include](../../../src/main/java/fr/insee/pogues/configuration/SecurityContext.java) diff --git a/docs/fr/security/authentication.md b/docs/fr/security/authentication.md index e0dd79d9..cec04d4f 100644 --- a/docs/fr/security/authentication.md +++ b/docs/fr/security/authentication.md @@ -5,5 +5,5 @@ Les informations de configuration d'annuaire sont récupérée dans le fichier p ### Le fichier SecurityContext.java -[include](../../../src/main/java/fr/insee/pogues/config/SecurityContext.java) +[include](../../../src/main/java/fr/insee/pogues/configuration/SecurityContext.java) diff --git a/pom.xml b/pom.xml index 35580681..d71a1fd3 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 2.7.18 + 3.2.4 @@ -13,7 +13,7 @@ fr.insee Pogues-BO jar - 4.4.3 + 4.4.4 Pogues-BO @@ -28,9 +28,6 @@ 0.8.11 12.4 2.15.1 - - 2.2 - jacoco jacoco reuseReports @@ -62,6 +59,12 @@ spring-boot-starter-jersey + + + org.springframework.boot + spring-boot-starter-webflux + + com.fasterxml.jackson.jaxrs @@ -118,14 +121,6 @@ json-simple ${json-simple.version} - - org.apache.httpcomponents - httpclient - - - org.apache.httpcomponents - httpmime - org.apache.xmlgraphics fop @@ -153,8 +148,6 @@ provided - - org.springdoc @@ -181,7 +174,7 @@ org.eclipse.persistence org.eclipse.persistence.moxy - 2.7.14 + 4.0.2 javax.xml.bind diff --git a/src/main/java/fr/insee/pogues/config/ClientConfig.java b/src/main/java/fr/insee/pogues/config/ClientConfig.java deleted file mode 100644 index 395f1cb1..00000000 --- a/src/main/java/fr/insee/pogues/config/ClientConfig.java +++ /dev/null @@ -1,27 +0,0 @@ -package fr.insee.pogues.config; - -import org.apache.http.conn.ssl.NoopHostnameVerifier; -import org.apache.http.conn.ssl.SSLConnectionSocketFactory; -import org.apache.http.conn.ssl.TrustSelfSignedStrategy; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.impl.client.HttpClients; -import org.apache.http.ssl.SSLContexts; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import javax.net.ssl.SSLContext; -import java.security.KeyManagementException; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; - -@Configuration -public class ClientConfig { - - @Bean - public HttpClientBuilder httpClientBuilder() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException { - SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null, new TrustSelfSignedStrategy()).build(); - SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE); - return HttpClients.custom().setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE).setSSLSocketFactory(sslsf); - } - -} diff --git a/src/main/java/fr/insee/pogues/config/OpenApiConfiguration.java b/src/main/java/fr/insee/pogues/config/OpenApiConfiguration.java deleted file mode 100644 index 225d45bb..00000000 --- a/src/main/java/fr/insee/pogues/config/OpenApiConfiguration.java +++ /dev/null @@ -1,52 +0,0 @@ -package fr.insee.pogues.config; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import io.swagger.v3.oas.annotations.enums.SecuritySchemeType; -import io.swagger.v3.oas.models.OpenAPI; -import io.swagger.v3.oas.models.info.Info; -import io.swagger.v3.oas.annotations.security.SecurityScheme; -import io.swagger.v3.oas.models.servers.Server; - -@Configuration -@SecurityScheme( - name = "bearerAuth", - type = SecuritySchemeType.HTTP, - bearerFormat = "JWT", - scheme = "bearer" - ) -public class OpenApiConfiguration { - - @Value("${fr.insee.pogues.api.scheme}") - private String apiScheme; - - @Value("${fr.insee.pogues.api.host}") - private String apiHost; - - @Value("${fr.insee.pogues.api.name}") - private String apiName; - - @Value("${fr.insee.pogues.model.version}") - private String poguesModelVersion; - - @Value("${fr.insee.pogues.version}") - private String projectVersion; - - @Bean - public OpenAPI customOpenAPI() { - Server server = new Server(); - server.setUrl(String.format("%s://%s%s", apiScheme, apiHost, apiName)); - return new OpenAPI() - .addServersItem(server) - .info(new Info() - .title("Pogues API") - .description( - "Rest Endpoints and services used by Pogues" - + "

Pogues-Model version : " + poguesModelVersion + "

") - .version(projectVersion) - - ); - } -} diff --git a/src/main/java/fr/insee/pogues/config/PropertiesLog.java b/src/main/java/fr/insee/pogues/config/PropertiesLog.java deleted file mode 100644 index 4b81b8dd..00000000 --- a/src/main/java/fr/insee/pogues/config/PropertiesLog.java +++ /dev/null @@ -1,49 +0,0 @@ -package fr.insee.pogues.config; - -import java.util.Arrays; -import java.util.Objects; -import java.util.logging.Logger; - -import org.apache.commons.lang3.StringUtils; -import org.springframework.core.env.AbstractEnvironment; -import org.springframework.core.env.EnumerablePropertySource; -import org.springframework.core.env.Environment; -import org.springframework.stereotype.Component; - -@Component -public class PropertiesLog { - - private static final Logger log = Logger.getLogger(PropertiesLog.class.getName()); - private final Environment environment; - - public PropertiesLog(Environment environment) { - Objects.requireNonNull(environment); - this.environment=environment; - - log.info("==============================================================================================="); - log.info(" Properties values "); - log.info("==============================================================================================="); - - ((AbstractEnvironment) environment).getPropertySources().stream() - .map(propertySource -> { - if (propertySource instanceof EnumerablePropertySource){ - return ((EnumerablePropertySource)propertySource).getPropertyNames(); - }else{ - log.warning(propertySource+ " is not EnumerablePropertySource : listing impossible"); - return new String[] {}; - } - } - ) - .flatMap(Arrays::stream) - .distinct() - .forEach(key->log.info(key+" = "+printValueWithoutPassword(key))); - } - - private String printValueWithoutPassword(String key) { - if (StringUtils.containsAny(key, "password", "pwd", "keycloak.key", "jeton", "secret")) { - return "******"; - } - return environment.getProperty(key); - } - -} diff --git a/src/main/java/fr/insee/pogues/config/auth/security/DefaultSecurityContext.java b/src/main/java/fr/insee/pogues/config/auth/security/DefaultSecurityContext.java deleted file mode 100644 index a412cf84..00000000 --- a/src/main/java/fr/insee/pogues/config/auth/security/DefaultSecurityContext.java +++ /dev/null @@ -1,51 +0,0 @@ -package fr.insee.pogues.config.auth.security; - -import fr.insee.pogues.config.auth.UserProvider; -import fr.insee.pogues.config.auth.user.User; - -import static org.springframework.security.config.Customizer.withDefaults; - -import java.util.Arrays; - -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; -import org.springframework.web.cors.CorsConfiguration; -import org.springframework.web.cors.CorsConfigurationSource; -import org.springframework.web.cors.UrlBasedCorsConfigurationSource; - -@Configuration -@EnableWebSecurity -@ConditionalOnExpression("!'OIDC'.equals('${fr.insee.pogues.authentication}')") -public class DefaultSecurityContext extends WebSecurityConfigurerAdapter{ - - @Override - protected void configure(HttpSecurity http) throws Exception { - http.csrf().disable(); - http.cors(withDefaults()) - .authorizeRequests().anyRequest().permitAll(); - } - - @Bean - public UserProvider getUserProvider() { - return auth->new User(); - } - - @Bean - public CorsConfigurationSource corsConfigurationSource() { - CorsConfiguration configuration = new CorsConfiguration(); - configuration.setAllowedOrigins(Arrays.asList("*")); - configuration.setAllowedMethods(Arrays.asList("*")); - configuration.setAllowedHeaders(Arrays.asList("*")); - configuration.addExposedHeader("Content-Disposition"); - - UrlBasedCorsConfigurationSource source = new - UrlBasedCorsConfigurationSource(); - source.registerCorsConfiguration("/**", configuration); - return source; - } - -} diff --git a/src/main/java/fr/insee/pogues/config/auth/security/OpenIDConnectSecurityContext.java b/src/main/java/fr/insee/pogues/config/auth/security/OpenIDConnectSecurityContext.java deleted file mode 100644 index 537ab69a..00000000 --- a/src/main/java/fr/insee/pogues/config/auth/security/OpenIDConnectSecurityContext.java +++ /dev/null @@ -1,84 +0,0 @@ -package fr.insee.pogues.config.auth.security; - -import static org.springframework.security.config.Customizer.withDefaults; - -import java.util.Arrays; -import java.util.Optional; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpMethod; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.web.cors.CorsConfiguration; -import org.springframework.web.cors.CorsConfigurationSource; -import org.springframework.web.cors.UrlBasedCorsConfigurationSource; - -import fr.insee.pogues.config.auth.UserProvider; -import fr.insee.pogues.config.auth.user.User; - -@Configuration -@EnableWebSecurity -@ConditionalOnProperty(name = "fr.insee.pogues.authentication", havingValue = "OIDC") -public class OpenIDConnectSecurityContext extends WebSecurityConfigurerAdapter { - - static final Logger logger = LogManager.getLogger(OpenIDConnectSecurityContext.class); - - @Value("${fr.insee.pogues.force.ssl}") - boolean requireSSL; - - @Value("${jwt.stamp-claim}") - private String stampClaim; - - @Value("${jwt.username-claim}") - private String nameClaim; - - @Value("${fr.insee.pogues.cors.allowedOrigin}") - private Optional allowedOrigin; - - @Override - protected void configure(HttpSecurity http) throws Exception { - //TODO : variabiliser path /api... - http.sessionManagement().disable(); - http.cors(withDefaults()) - .authorizeRequests() - .antMatchers("/api/init", "/api/healthcheck").permitAll() - .antMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll() - .antMatchers("/api/persistence/questionnaire/json-lunatic/**").permitAll() - .antMatchers(HttpMethod.OPTIONS).permitAll() - .anyRequest().authenticated() - .and() - .oauth2ResourceServer() - .jwt(); - if (requireSSL) - http.antMatcher("/**").requiresChannel().anyRequest().requiresSecure(); - } - - @Bean - public UserProvider getUserProvider() { - return auth -> { - final Jwt jwt = (Jwt) auth.getPrincipal(); - return new User(jwt.getClaimAsString(stampClaim), jwt.getClaimAsString(nameClaim)); - }; - } - - @Bean - public CorsConfigurationSource corsConfigurationSource() { - CorsConfiguration configuration = new CorsConfiguration(); - configuration.setAllowedOrigins(Arrays.asList(allowedOrigin.get())); - configuration.setAllowedMethods(Arrays.asList("GET","POST","PUT","DELETE")); - configuration.setAllowedHeaders(Arrays.asList("*")); - configuration.addExposedHeader("Content-Disposition"); - UrlBasedCorsConfigurationSource source = new - UrlBasedCorsConfigurationSource(); - source.registerCorsConfiguration("/**", configuration); - return source; - } - -} diff --git a/src/main/java/fr/insee/pogues/configuration/AppConfiguration.java b/src/main/java/fr/insee/pogues/configuration/AppConfiguration.java new file mode 100644 index 00000000..8fa14465 --- /dev/null +++ b/src/main/java/fr/insee/pogues/configuration/AppConfiguration.java @@ -0,0 +1,40 @@ +package fr.insee.pogues.configuration; + +import fr.insee.pogues.configuration.rest.AuthenticationHelper; +import fr.insee.pogues.configuration.rest.WebClientTokenInterceptor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.transaction.annotation.EnableTransactionManagement; +import org.springframework.web.reactive.function.client.WebClient; + +@Configuration +@EnableConfigurationProperties +@ComponentScan(basePackages = { "fr.insee.pogues" }) +@EnableTransactionManagement +@EnableCaching +@Slf4j +public class AppConfiguration { + + @Autowired + private AuthenticationHelper authenticationHelper; + + @Bean + public WebClient webClient( + @Value("${feature.oidc.enabled}") boolean oidcEnabled, + WebClient.Builder builder) { + builder + .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE); + if(oidcEnabled) builder.filter(new WebClientTokenInterceptor(authenticationHelper)); + return builder.build(); + } + +} diff --git a/src/main/java/fr/insee/pogues/configuration/CorsConfig.java b/src/main/java/fr/insee/pogues/configuration/CorsConfig.java new file mode 100644 index 00000000..8da264a3 --- /dev/null +++ b/src/main/java/fr/insee/pogues/configuration/CorsConfig.java @@ -0,0 +1,29 @@ +package fr.insee.pogues.configuration; + +import fr.insee.pogues.configuration.properties.ApplicationProperties; +import lombok.AllArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; + +import java.util.List; + +/** Cors configuration */ +@Configuration +@AllArgsConstructor +public class CorsConfig { + @Bean + protected CorsConfigurationSource corsConfigurationSource(ApplicationProperties applicationProperties) { + org.springframework.web.cors.CorsConfiguration configuration = new org.springframework.web.cors.CorsConfiguration(); + configuration.setAllowedOriginPatterns(applicationProperties.corsOrigins()); + configuration.setAllowedMethods(List.of("GET", "PUT", "POST", "DELETE", "OPTIONS")); + configuration.setAllowedHeaders(List.of("Authorization", "Content-Type")); + configuration.addExposedHeader("Content-Disposition"); + configuration.setMaxAge(3600L); + configuration.setAllowCredentials(true); + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", configuration); + return source; + } +} diff --git a/src/main/java/fr/insee/pogues/configuration/PropertiesLogger.java b/src/main/java/fr/insee/pogues/configuration/PropertiesLogger.java new file mode 100644 index 00000000..dd7afada --- /dev/null +++ b/src/main/java/fr/insee/pogues/configuration/PropertiesLogger.java @@ -0,0 +1,59 @@ +package fr.insee.pogues.configuration; + +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.core.env.AbstractEnvironment; +import org.springframework.core.env.EnumerablePropertySource; +import org.springframework.core.env.Environment; + +import java.util.Arrays; +import java.util.Objects; +import java.util.Set; + +/** + * Listener Spring loads when Spring Boot is starting on + * ApplicationEnvironmentPreparedEvent event + * Display props in logs + * + */ +@Slf4j +public class PropertiesLogger implements ApplicationListener { + + private static final Set hiddenWords = Set.of("password", "pwd", "jeton", "token", "secret"); + + @Override + public void onApplicationEvent(@NonNull ApplicationEnvironmentPreparedEvent event) { + Environment environment = event.getEnvironment(); + + log.info("==============================================================================================="); + log.info(" Properties "); + log.info("==============================================================================================="); + + ((AbstractEnvironment) environment).getPropertySources().stream() + .map(propertySource -> { + if (propertySource instanceof EnumerablePropertySource) { + return ((EnumerablePropertySource) propertySource).getPropertyNames(); + } else { + log.warn(propertySource + " is not an EnumerablePropertySource : impossible to display"); + return new String[] {}; + } + }) + .flatMap(Arrays::stream) + .distinct() + .filter(Objects::nonNull) + .forEach(key -> log.info(key + " = " + resolveValueWithSecretAttribute(key, environment))); + log.info("============================================================================"); + + } + + private static Object resolveValueWithSecretAttribute(String key, Environment environment) { + if (hiddenWords.stream().anyMatch(key::contains)) { + return "******"; + } + return environment.getProperty(key); + + } + +} diff --git a/src/main/java/fr/insee/pogues/config/RemoteMetadata.java b/src/main/java/fr/insee/pogues/configuration/RemoteMetadata.java similarity index 94% rename from src/main/java/fr/insee/pogues/config/RemoteMetadata.java rename to src/main/java/fr/insee/pogues/configuration/RemoteMetadata.java index 576fad9f..294a6b6e 100644 --- a/src/main/java/fr/insee/pogues/config/RemoteMetadata.java +++ b/src/main/java/fr/insee/pogues/configuration/RemoteMetadata.java @@ -1,4 +1,4 @@ -package fr.insee.pogues.config; +package fr.insee.pogues.configuration; import org.springframework.boot.context.properties.ConfigurationProperties; diff --git a/src/main/java/fr/insee/pogues/config/StaticResourcesForFOPConfig.java b/src/main/java/fr/insee/pogues/configuration/StaticResourcesForFOPConfig.java similarity index 80% rename from src/main/java/fr/insee/pogues/config/StaticResourcesForFOPConfig.java rename to src/main/java/fr/insee/pogues/configuration/StaticResourcesForFOPConfig.java index 2f634a93..e2535c6f 100644 --- a/src/main/java/fr/insee/pogues/config/StaticResourcesForFOPConfig.java +++ b/src/main/java/fr/insee/pogues/configuration/StaticResourcesForFOPConfig.java @@ -1,4 +1,4 @@ -package fr.insee.pogues.config; +package fr.insee.pogues.configuration; import lombok.Getter; import lombok.NonNull; @@ -20,10 +20,16 @@ @Component public class StaticResourcesForFOPConfig { - final static Logger logger = LogManager.getLogger(StaticResourcesForFOPConfig.class); + final static Logger LOG = LogManager.getLogger(StaticResourcesForFOPConfig.class); + @Value("${fr.insee.pogues.pdf.temp.dir}") private Path staticResourcesTempDir; + + public StaticResourcesForFOPConfig(){ + LOG.info("Init, copy assest to tempDir"); + copyAssetResourcesToTempDir(); + } private final PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); private URI imgFolderUri=null; @@ -39,9 +45,9 @@ public URI getImgFolderUri() { return imgFolderUri; } - @PostConstruct + public void copyAssetResourcesToTempDir() { - logger.info("Copying static files from "+resolver.getResource(ResourceLoader.CLASSPATH_URL_PREFIX+"/pdf/")+" to "+staticResourcesTempDir); + LOG.info("Copying static files from "+resolver.getResource(ResourceLoader.CLASSPATH_URL_PREFIX+"/pdf/")+" to "+staticResourcesTempDir); copyFromClasspath(ResourceLoader.CLASSPATH_URL_PREFIX+"/pdf/img/*", "img"); copyFromClasspath(ResourceLoader.CLASSPATH_URL_PREFIX+"/pdf/fonts/*", "fonts"); @@ -49,7 +55,7 @@ public void copyAssetResourcesToTempDir() { } private void copyFromClasspath(String locationPattern, String destinationDirectory){ - logger.debug("Process "+locationPattern+ " to "+destinationDirectory); + LOG.debug("Process "+locationPattern+ " to "+destinationDirectory); Resource[] resources =null; try { resources = resolver.getResources(locationPattern); @@ -58,7 +64,7 @@ private void copyFromClasspath(String locationPattern, String destinationDirecto } var destination = staticResourcesTempDir.resolve(destinationDirectory); for (Resource resource : resources) { - logger.debug("Process "+resource); + LOG.debug("Process "+resource); try { FileUtils.copyInputStreamToFile(resource.getInputStream(), destination.resolve(resource.getFilename()).toFile()); } catch (IOException e) { diff --git a/src/main/java/fr/insee/pogues/configuration/auth/AuthConstants.java b/src/main/java/fr/insee/pogues/configuration/auth/AuthConstants.java new file mode 100644 index 00000000..ac7a4065 --- /dev/null +++ b/src/main/java/fr/insee/pogues/configuration/auth/AuthConstants.java @@ -0,0 +1,9 @@ +package fr.insee.pogues.configuration.auth; + +public class AuthConstants { + private AuthConstants() { + throw new IllegalStateException("Constants class"); + } + + public static final String ROLE_PREFIX = "ROLE_"; +} \ No newline at end of file diff --git a/src/main/java/fr/insee/pogues/configuration/auth/AuthorityRole.java b/src/main/java/fr/insee/pogues/configuration/auth/AuthorityRole.java new file mode 100644 index 00000000..061be288 --- /dev/null +++ b/src/main/java/fr/insee/pogues/configuration/auth/AuthorityRole.java @@ -0,0 +1,11 @@ +package fr.insee.pogues.configuration.auth; + +public class AuthorityRole { + private AuthorityRole() { + throw new IllegalArgumentException("Constant class"); + } + + public static final String HAS_ROLE_DESIGNER = "hasRole('DESIGNER')"; + public static final String HAS_ANY_ROLE = "hasAnyRole('DESIGNER', 'ADMIN')"; + public static final String HAS_ADMIN_PRIVILEGES = "hasAnyRole('ADMIN')"; +} diff --git a/src/main/java/fr/insee/pogues/configuration/auth/AuthorityRoleEnum.java b/src/main/java/fr/insee/pogues/configuration/auth/AuthorityRoleEnum.java new file mode 100644 index 00000000..16a40184 --- /dev/null +++ b/src/main/java/fr/insee/pogues/configuration/auth/AuthorityRoleEnum.java @@ -0,0 +1,6 @@ +package fr.insee.pogues.configuration.auth; + +public enum AuthorityRoleEnum { + ADMIN, + DESIGNER +} \ No newline at end of file diff --git a/src/main/java/fr/insee/pogues/configuration/auth/GrantedAuthorityConverter.java b/src/main/java/fr/insee/pogues/configuration/auth/GrantedAuthorityConverter.java new file mode 100644 index 00000000..7da55587 --- /dev/null +++ b/src/main/java/fr/insee/pogues/configuration/auth/GrantedAuthorityConverter.java @@ -0,0 +1,55 @@ +package fr.insee.pogues.configuration.auth; + +import fr.insee.pogues.configuration.properties.OidcProperties; +import fr.insee.pogues.configuration.properties.RoleProperties; +import lombok.AllArgsConstructor; +import org.springframework.core.convert.converter.Converter; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.oauth2.jwt.Jwt; + +import java.util.*; +import java.util.stream.Collectors; +@AllArgsConstructor +public class GrantedAuthorityConverter implements Converter> { + private final OidcProperties oidcProperties; + private final RoleProperties roleProperties; + + /** + * + * @param map: Map that represent JWT token + * @param keyPath : jsonPath to wanted value, ex: realm_access.roles + * @return the value of keyPath inside Map + * @param + */ + public T getDeepPropsOfMapForRoles(Map map, String keyPath){ + Map subMap = (Map) map; + String[] propertyPath = keyPath.toString().split("\\."); + for (int i = 0; i < propertyPath.length -1; i++) { + subMap = (Map) subMap.get(propertyPath[i]); + } + return (T) subMap.get(propertyPath[propertyPath.length -1]); + + } + + @SuppressWarnings("unchecked") + @Override + public Collection convert(Jwt jwt) { + Map claims = jwt.getClaims(); + + List roles = getDeepPropsOfMapForRoles(claims, oidcProperties.roleClaim()); + + return roles.stream() + .map(role -> { + if (role.equals(roleProperties.designer())) { + return new SimpleGrantedAuthority(AuthConstants.ROLE_PREFIX + AuthorityRoleEnum.DESIGNER); + } + if (role.equals(roleProperties.admin())) { + return new SimpleGrantedAuthority(AuthConstants.ROLE_PREFIX + AuthorityRoleEnum.ADMIN); + } + return null; + }) + .filter(Objects::nonNull) + .collect(Collectors.toCollection(ArrayList::new)); + } +} \ No newline at end of file diff --git a/src/main/java/fr/insee/pogues/configuration/auth/NoAuthSecurityConfiguration.java b/src/main/java/fr/insee/pogues/configuration/auth/NoAuthSecurityConfiguration.java new file mode 100644 index 00000000..439dc947 --- /dev/null +++ b/src/main/java/fr/insee/pogues/configuration/auth/NoAuthSecurityConfiguration.java @@ -0,0 +1,65 @@ +package fr.insee.pogues.configuration.auth; + + +import fr.insee.pogues.configuration.auth.user.User; +import fr.insee.pogues.configuration.properties.ApplicationProperties; +import lombok.AllArgsConstructor; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter; +import org.springframework.security.web.header.writers.XXssProtectionHeaderWriter; + +@ConditionalOnProperty(name = "feature.oidc.enabled", havingValue = "false") +@Configuration +@EnableWebSecurity +@AllArgsConstructor +public class NoAuthSecurityConfiguration { + private final PublicSecurityFilterChain publicSecurityFilterChainConfiguration; + + /** + * Configure spring security filter chain when no authentication + * + * @param http Http Security Object + * @return the spring security filter + * @throws Exception exception + */ + @Bean + @Order(2) + protected SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + return http + .securityMatcher("/**") + .csrf(AbstractHttpConfigurer::disable) + .cors(Customizer.withDefaults()) + .headers(headers -> headers + .xssProtection(xssConfig -> xssConfig.headerValue(XXssProtectionHeaderWriter.HeaderValue.DISABLED)) + .contentSecurityPolicy(cspConfig -> cspConfig + .policyDirectives("default-src 'none'") + ) + .referrerPolicy(referrerPolicy -> + referrerPolicy + .policy(ReferrerPolicyHeaderWriter.ReferrerPolicy.SAME_ORIGIN) + )) + .anonymous(anonymousConfig -> anonymousConfig + .authorities("ROLE_ADMIN")) + .authorizeHttpRequests(authorize -> authorize.anyRequest().permitAll()) + .build(); + } + + @Bean + @Order(1) + protected SecurityFilterChain filterPublicUrlsChain(HttpSecurity http, ApplicationProperties applicationProperties) throws Exception { + return publicSecurityFilterChainConfiguration.buildSecurityPublicFilterChain(http, applicationProperties.publicUrls()); + } + + @Bean + public UserProvider getUserProvider() { + return auth-> new User(); + } +} \ No newline at end of file diff --git a/src/main/java/fr/insee/pogues/configuration/auth/OidcSecurityConfiguration.java b/src/main/java/fr/insee/pogues/configuration/auth/OidcSecurityConfiguration.java new file mode 100644 index 00000000..8877b79e --- /dev/null +++ b/src/main/java/fr/insee/pogues/configuration/auth/OidcSecurityConfiguration.java @@ -0,0 +1,109 @@ +package fr.insee.pogues.configuration.auth; + +import fr.insee.pogues.configuration.auth.user.User; +import fr.insee.pogues.configuration.properties.ApplicationProperties; +import fr.insee.pogues.configuration.properties.OidcProperties; +import fr.insee.pogues.configuration.properties.RoleProperties; +import lombok.AllArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import org.springframework.core.convert.converter.Converter; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter; +import org.springframework.security.web.header.writers.XXssProtectionHeaderWriter; + +import java.util.Collection; + +/** + * Spring security configuration when using OIDC auth + */ +@Configuration +@EnableWebSecurity +@EnableMethodSecurity +@ConditionalOnProperty(name = "feature.oidc.enabled", havingValue = "true") +@AllArgsConstructor +public class OidcSecurityConfiguration { + private final PublicSecurityFilterChain publicSecurityFilterChainConfiguration; + + /** + * Configure spring security filter chain to handle OIDC authentication + * + * @param http Http Security Object + * @return the spring security filter + * @throws Exception exception + */ + + @Value("${jwt.stamp-claim}") + private String stampClaim; + + @Value("${jwt.username-claim}") + private String nameClaim; + + @Bean + @Order(2) + protected SecurityFilterChain filterChain(HttpSecurity http, + OidcProperties oidcProperties, RoleProperties roleProperties) throws Exception { + return http + .securityMatcher("/**") + .csrf(AbstractHttpConfigurer::disable) + .cors(Customizer.withDefaults()) + .headers(headers -> headers + .xssProtection(xssConfig -> xssConfig.headerValue(XXssProtectionHeaderWriter.HeaderValue.DISABLED)) + .contentSecurityPolicy(cspConfig -> cspConfig + .policyDirectives("default-src 'none'") + ) + .referrerPolicy(referrerPolicy -> + referrerPolicy + .policy(ReferrerPolicyHeaderWriter.ReferrerPolicy.SAME_ORIGIN) + )) + .authorizeHttpRequests(configurer -> configurer + .anyRequest() + .authenticated() + ) + .sessionManagement(sess -> sess.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter(oidcProperties, roleProperties)))) + .build(); + } + + @Bean + @Order(1) + protected SecurityFilterChain filterPublicUrlsChain(HttpSecurity http, ApplicationProperties applicationProperties, + OidcProperties oidcProperties) throws Exception { + String authorizedConnectionHost = oidcProperties.enabled() ? + " " + oidcProperties.authServerHost() : ""; + return publicSecurityFilterChainConfiguration.buildSecurityPublicFilterChain(http, applicationProperties.publicUrls(), authorizedConnectionHost); + } + + @Bean + protected JwtAuthenticationConverter jwtAuthenticationConverter(OidcProperties oidcProperties, RoleProperties roleProperties) { + JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter(); + jwtAuthenticationConverter.setPrincipalClaimName(oidcProperties.principalAttribute()); + jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter(oidcProperties, roleProperties)); + return jwtAuthenticationConverter; + } + + Converter> jwtGrantedAuthoritiesConverter(OidcProperties oidcProperties, RoleProperties roleProperties) { + return new GrantedAuthorityConverter(oidcProperties, roleProperties); + } + + @Bean + public UserProvider getUserProvider() { + return auth -> { + final Jwt jwt = (Jwt) auth.getPrincipal(); + return new User(jwt.getClaimAsString(stampClaim), jwt.getClaimAsString(nameClaim)); + }; + } + +} diff --git a/src/main/java/fr/insee/pogues/configuration/auth/PublicSecurityFilterChain.java b/src/main/java/fr/insee/pogues/configuration/auth/PublicSecurityFilterChain.java new file mode 100644 index 00000000..0acf9ee2 --- /dev/null +++ b/src/main/java/fr/insee/pogues/configuration/auth/PublicSecurityFilterChain.java @@ -0,0 +1,44 @@ +package fr.insee.pogues.configuration.auth; + +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter; +import org.springframework.security.web.header.writers.XXssProtectionHeaderWriter; + +@Configuration +public class PublicSecurityFilterChain { + SecurityFilterChain buildSecurityPublicFilterChain(HttpSecurity http, String[] publicUrls) throws Exception { + return buildSecurityPublicFilterChain(http, publicUrls, ""); + } + + SecurityFilterChain buildSecurityPublicFilterChain(HttpSecurity http, String[] publicUrls, String authorizedConnectionHost) throws Exception { + return http + .securityMatcher(publicUrls) + .cors(Customizer.withDefaults()) + .headers(headers -> headers + .xssProtection(xssConfig -> xssConfig.headerValue(XXssProtectionHeaderWriter.HeaderValue.DISABLED)) + .contentSecurityPolicy(cspConfig -> cspConfig + .policyDirectives("default-src 'none'; " + + "connect-src 'self'" + authorizedConnectionHost + "; " + + "img-src 'self' data:; " + + "style-src 'self'; " + + "script-src 'self' 'unsafe-inline'") + ) + .referrerPolicy(referrerPolicy -> + referrerPolicy + .policy(ReferrerPolicyHeaderWriter.ReferrerPolicy.SAME_ORIGIN) + )) + .authorizeHttpRequests(auth -> auth + .requestMatchers(HttpMethod.OPTIONS).permitAll() + .requestMatchers(publicUrls).permitAll() + .anyRequest() + .authenticated() + ) + .sessionManagement(sess -> sess.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/fr/insee/pogues/config/auth/UserProvider.java b/src/main/java/fr/insee/pogues/configuration/auth/UserProvider.java similarity index 62% rename from src/main/java/fr/insee/pogues/config/auth/UserProvider.java rename to src/main/java/fr/insee/pogues/configuration/auth/UserProvider.java index 561ba636..70b839bb 100644 --- a/src/main/java/fr/insee/pogues/config/auth/UserProvider.java +++ b/src/main/java/fr/insee/pogues/configuration/auth/UserProvider.java @@ -1,6 +1,6 @@ -package fr.insee.pogues.config.auth; +package fr.insee.pogues.configuration.auth; -import fr.insee.pogues.config.auth.user.User; +import fr.insee.pogues.configuration.auth.user.User; import org.springframework.security.core.Authentication; @FunctionalInterface diff --git a/src/main/java/fr/insee/pogues/config/auth/security/restrictions/StampsRestrictionsService.java b/src/main/java/fr/insee/pogues/configuration/auth/security/restrictions/StampsRestrictionsService.java similarity index 58% rename from src/main/java/fr/insee/pogues/config/auth/security/restrictions/StampsRestrictionsService.java rename to src/main/java/fr/insee/pogues/configuration/auth/security/restrictions/StampsRestrictionsService.java index 2d9731f1..0841edc5 100644 --- a/src/main/java/fr/insee/pogues/config/auth/security/restrictions/StampsRestrictionsService.java +++ b/src/main/java/fr/insee/pogues/configuration/auth/security/restrictions/StampsRestrictionsService.java @@ -1,6 +1,6 @@ -package fr.insee.pogues.config.auth.security.restrictions; +package fr.insee.pogues.configuration.auth.security.restrictions; -import fr.insee.pogues.config.auth.user.User; +import fr.insee.pogues.configuration.auth.user.User; public interface StampsRestrictionsService { diff --git a/src/main/java/fr/insee/pogues/config/auth/security/restrictions/StampsRestrictionsServiceImpl.java b/src/main/java/fr/insee/pogues/configuration/auth/security/restrictions/StampsRestrictionsServiceImpl.java similarity index 85% rename from src/main/java/fr/insee/pogues/config/auth/security/restrictions/StampsRestrictionsServiceImpl.java rename to src/main/java/fr/insee/pogues/configuration/auth/security/restrictions/StampsRestrictionsServiceImpl.java index b42c56cf..cc86774e 100644 --- a/src/main/java/fr/insee/pogues/config/auth/security/restrictions/StampsRestrictionsServiceImpl.java +++ b/src/main/java/fr/insee/pogues/configuration/auth/security/restrictions/StampsRestrictionsServiceImpl.java @@ -1,4 +1,4 @@ -package fr.insee.pogues.config.auth.security.restrictions; +package fr.insee.pogues.configuration.auth.security.restrictions; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -7,8 +7,8 @@ import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; -import fr.insee.pogues.config.auth.UserProvider; -import fr.insee.pogues.config.auth.user.User; +import fr.insee.pogues.configuration.auth.UserProvider; +import fr.insee.pogues.configuration.auth.user.User; @Service public class StampsRestrictionsServiceImpl implements StampsRestrictionsService{ diff --git a/src/main/java/fr/insee/pogues/config/auth/user/User.java b/src/main/java/fr/insee/pogues/configuration/auth/user/User.java similarity index 83% rename from src/main/java/fr/insee/pogues/config/auth/user/User.java rename to src/main/java/fr/insee/pogues/configuration/auth/user/User.java index 0d646270..6b21d123 100644 --- a/src/main/java/fr/insee/pogues/config/auth/user/User.java +++ b/src/main/java/fr/insee/pogues/configuration/auth/user/User.java @@ -1,4 +1,4 @@ -package fr.insee.pogues.config.auth.user; +package fr.insee.pogues.configuration.auth.user; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/src/main/java/fr/insee/pogues/configuration/properties/ApplicationProperties.java b/src/main/java/fr/insee/pogues/configuration/properties/ApplicationProperties.java new file mode 100644 index 00000000..881d652b --- /dev/null +++ b/src/main/java/fr/insee/pogues/configuration/properties/ApplicationProperties.java @@ -0,0 +1,48 @@ +package fr.insee.pogues.configuration.properties; + +import jakarta.validation.constraints.NotEmpty; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.validation.annotation.Validated; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +@Validated +@ConfigurationProperties(prefix = "application") +public record ApplicationProperties( + String host, + String title, + String description, + String[] publicUrls, + @NotEmpty(message = "cors origins must be specified") + List corsOrigins) { + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ApplicationProperties that = (ApplicationProperties) o; + return Objects.equals(host, that.host) + && Objects.equals(title, that.title) + && Objects.equals(description, that.description) + && Arrays.equals(publicUrls, that.publicUrls); + } + + @Override + public int hashCode() { + int result = Objects.hash(host, title, description); + result = 31 * result + Arrays.hashCode(publicUrls); + return result; + } + + @Override + public String toString() { + return "ApplicationProperties{" + + "host='" + host + '\'' + + ", title='" + title + '\'' + + ", description='" + description + '\'' + + ", publicUrls=" + Arrays.toString(publicUrls) + + '}'; + } +} \ No newline at end of file diff --git a/src/main/java/fr/insee/pogues/configuration/properties/OidcProperties.java b/src/main/java/fr/insee/pogues/configuration/properties/OidcProperties.java new file mode 100644 index 00000000..d940471a --- /dev/null +++ b/src/main/java/fr/insee/pogues/configuration/properties/OidcProperties.java @@ -0,0 +1,15 @@ +package fr.insee.pogues.configuration.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + + +@ConfigurationProperties(prefix = "feature.oidc") +public record OidcProperties( + boolean enabled, + String authServerHost, + String authServerUrl, + String realm, + String principalAttribute, + String roleClaim, + String clientId) { +} \ No newline at end of file diff --git a/src/main/java/fr/insee/pogues/configuration/properties/RoleProperties.java b/src/main/java/fr/insee/pogues/configuration/properties/RoleProperties.java new file mode 100644 index 00000000..047d0d7f --- /dev/null +++ b/src/main/java/fr/insee/pogues/configuration/properties/RoleProperties.java @@ -0,0 +1,10 @@ +package fr.insee.pogues.configuration.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "application.roles") +public record RoleProperties( + String designer, + String admin +) { +} \ No newline at end of file diff --git a/src/main/java/fr/insee/pogues/configuration/rest/AuthenticationHelper.java b/src/main/java/fr/insee/pogues/configuration/rest/AuthenticationHelper.java new file mode 100644 index 00000000..b83723b0 --- /dev/null +++ b/src/main/java/fr/insee/pogues/configuration/rest/AuthenticationHelper.java @@ -0,0 +1,19 @@ +package fr.insee.pogues.configuration.rest; + +import org.springframework.security.core.Authentication; + +public interface AuthenticationHelper { + /** + * Retrieve the auth token of the current user + * + * @return auth token + */ + String getUserToken(); + + /** + * Retrieve the authentication principal for current user + * + * @return {@link Authentication} the authentication user object + */ + Authentication getAuthenticationPrincipal(); +} \ No newline at end of file diff --git a/src/main/java/fr/insee/pogues/configuration/rest/AuthenticationTokenException.java b/src/main/java/fr/insee/pogues/configuration/rest/AuthenticationTokenException.java new file mode 100644 index 00000000..bdf8e68d --- /dev/null +++ b/src/main/java/fr/insee/pogues/configuration/rest/AuthenticationTokenException.java @@ -0,0 +1,7 @@ +package fr.insee.pogues.configuration.rest; + +public class AuthenticationTokenException extends RuntimeException { + public AuthenticationTokenException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/src/main/java/fr/insee/pogues/configuration/rest/AuthenticationUserHelper.java b/src/main/java/fr/insee/pogues/configuration/rest/AuthenticationUserHelper.java new file mode 100644 index 00000000..4eb7ff50 --- /dev/null +++ b/src/main/java/fr/insee/pogues/configuration/rest/AuthenticationUserHelper.java @@ -0,0 +1,26 @@ +package fr.insee.pogues.configuration.rest; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RequiredArgsConstructor +public class AuthenticationUserHelper implements AuthenticationHelper { + @Override + public String getUserToken() { + if(getAuthenticationPrincipal() instanceof JwtAuthenticationToken auth) { + return auth.getToken().getTokenValue(); + } + throw new AuthenticationTokenException("Cannot retrieve token for the user."); + } + + @Override + public Authentication getAuthenticationPrincipal() { + return SecurityContextHolder.getContext().getAuthentication(); + } +} \ No newline at end of file diff --git a/src/main/java/fr/insee/pogues/configuration/rest/WebClientTokenInterceptor.java b/src/main/java/fr/insee/pogues/configuration/rest/WebClientTokenInterceptor.java new file mode 100644 index 00000000..696e70bc --- /dev/null +++ b/src/main/java/fr/insee/pogues/configuration/rest/WebClientTokenInterceptor.java @@ -0,0 +1,25 @@ +package fr.insee.pogues.configuration.rest; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.reactive.function.client.ClientRequest; +import org.springframework.web.reactive.function.client.ClientResponse; +import org.springframework.web.reactive.function.client.ExchangeFilterFunction; +import org.springframework.web.reactive.function.client.ExchangeFunction; +import reactor.core.publisher.Mono; + +@RequiredArgsConstructor +@Slf4j +public class WebClientTokenInterceptor implements ExchangeFilterFunction { + + private final AuthenticationHelper authenticationHelper; + + @Override + public Mono filter(ClientRequest request, ExchangeFunction next) { + String jwt = authenticationHelper.getUserToken(); + ClientRequest newRequest = ClientRequest.from(request) + .headers(h -> h.setBearerAuth(jwt)) + .build(); + return next.exchange(newRequest); + } +} \ No newline at end of file diff --git a/src/main/java/fr/insee/pogues/configuration/swagger/SpringDocConfiguration.java b/src/main/java/fr/insee/pogues/configuration/swagger/SpringDocConfiguration.java new file mode 100644 index 00000000..98fdef30 --- /dev/null +++ b/src/main/java/fr/insee/pogues/configuration/swagger/SpringDocConfiguration.java @@ -0,0 +1,78 @@ +package fr.insee.pogues.configuration.swagger; + +import fr.insee.pogues.configuration.properties.OidcProperties; +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.security.*; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.info.BuildProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.MediaType; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; + +import java.util.ArrayList; +import java.util.Arrays; + +@Configuration +@ConditionalOnProperty(value="feature.swagger.enabled", havingValue = "true") +public class SpringDocConfiguration { + + @Value("${fr.insee.pogues.model.version}") + private String poguesModelVersion; + @Bean + @ConditionalOnProperty(name = "feature.oidc.enabled", havingValue = "false") + protected OpenAPI noAuthOpenAPI(BuildProperties buildProperties) { + return generateOpenAPI(buildProperties); + } + + @Bean + @ConditionalOnProperty(name = "feature.oidc.enabled", havingValue = "true") + protected OpenAPI oidcOpenAPI(OidcProperties oidcProperties, BuildProperties buildProperties) { + String authUrl = oidcProperties.authServerUrl() + "/realms/" + oidcProperties.realm() + "/protocol/openid-connect"; + String securitySchemeName = "oauth2"; + + return generateOpenAPI(buildProperties) + .addSecurityItem(new SecurityRequirement().addList(securitySchemeName, Arrays.asList("read", "write"))) + .components( + new Components() + .addSecuritySchemes(securitySchemeName, + new SecurityScheme() + .name(securitySchemeName) + .type(SecurityScheme.Type.OAUTH2) + .flows(getFlows(authUrl)) + ) + ); + + } + + public SpringDocConfiguration(MappingJackson2HttpMessageConverter converter) { + var supportedMediaTypes = new ArrayList<>(converter.getSupportedMediaTypes()); + supportedMediaTypes.add(new MediaType("application", "octet-stream")); + converter.setSupportedMediaTypes(supportedMediaTypes); + } + + private OpenAPI generateOpenAPI(BuildProperties buildProperties) { + return new OpenAPI().info( + new Info() + .title(buildProperties.getName()) + .description( + "Rest Endpoints and services used by Pogues" + + "

Pogues-Model version : " +poguesModelVersion + "

") + .version(buildProperties.getVersion()) + ); + } + + private OAuthFlows getFlows(String authUrl) { + OAuthFlows flows = new OAuthFlows(); + OAuthFlow flow = new OAuthFlow(); + Scopes scopes = new Scopes(); + flow.setAuthorizationUrl(authUrl + "/auth"); + flow.setTokenUrl(authUrl + "/token"); + flow.setRefreshUrl(authUrl + "/token"); + flow.setScopes(scopes); + return flows.authorizationCode(flow); + } +} \ No newline at end of file diff --git a/src/main/java/fr/insee/pogues/configuration/swagger/role/DisplayRolesOnSwaggerUI.java b/src/main/java/fr/insee/pogues/configuration/swagger/role/DisplayRolesOnSwaggerUI.java new file mode 100644 index 00000000..b3791272 --- /dev/null +++ b/src/main/java/fr/insee/pogues/configuration/swagger/role/DisplayRolesOnSwaggerUI.java @@ -0,0 +1,43 @@ +package fr.insee.pogues.configuration.swagger.role; + +import io.swagger.v3.oas.models.Operation; +import org.springdoc.core.customizers.OperationCustomizer; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; +import org.springframework.web.method.HandlerMethod; + +@Component +public class DisplayRolesOnSwaggerUI implements OperationCustomizer { + public static final String AUTHORIZED_ROLES = "Authorized roles: "; + + /** + * Display roles allowed to use an endpoint in the description field + * @param operation spring doc operation endpoint + * @param handlerMethod method handler used to retrieve annotations + * @return the operation with role description for UI + */ + @Override + public Operation customize(Operation operation, HandlerMethod handlerMethod) { + var preAuthorizeAnnotation = handlerMethod.getMethodAnnotation(PreAuthorize.class); + StringBuilder description = new StringBuilder(); + if(preAuthorizeAnnotation == null) { + return operation; + } + if(operation.getDescription() != null) { + description + .append(operation.getDescription()) + .append("\n"); + } + description.append(AUTHORIZED_ROLES); + String roles = preAuthorizeAnnotation.value(); + for(RoleUIMapper roleUIMapper : RoleUIMapper.values()) { + if(roles.contains(roleUIMapper.getRoleExpression())) { + description + .append(roleUIMapper.name()) + .append(" / "); + } + } + operation.setDescription(description.toString()); + return operation; + } +} \ No newline at end of file diff --git a/src/main/java/fr/insee/pogues/configuration/swagger/role/RoleUIMapper.java b/src/main/java/fr/insee/pogues/configuration/swagger/role/RoleUIMapper.java new file mode 100644 index 00000000..aa83bcff --- /dev/null +++ b/src/main/java/fr/insee/pogues/configuration/swagger/role/RoleUIMapper.java @@ -0,0 +1,19 @@ +package fr.insee.pogues.configuration.swagger.role; + +import fr.insee.pogues.configuration.auth.AuthorityRole; + +public enum RoleUIMapper { + ADMIN(AuthorityRole.HAS_ADMIN_PRIVILEGES), + AUTHENTICATED(AuthorityRole.HAS_ANY_ROLE), + INTERVIEWER(AuthorityRole.HAS_ROLE_DESIGNER); + + private final String roleExpression; + + RoleUIMapper(String roleExpression) { + this.roleExpression = roleExpression; + } + + public String getRoleExpression() { + return roleExpression; + } +} \ No newline at end of file diff --git a/src/main/java/fr/insee/pogues/metadata/client/MetadataClientImpl.java b/src/main/java/fr/insee/pogues/metadata/client/MetadataClientImpl.java index b44fa878..5682df64 100644 --- a/src/main/java/fr/insee/pogues/metadata/client/MetadataClientImpl.java +++ b/src/main/java/fr/insee/pogues/metadata/client/MetadataClientImpl.java @@ -1,24 +1,21 @@ package fr.insee.pogues.metadata.client; -import java.util.Arrays; -import java.util.List; - -import org.apache.http.entity.ContentType; +import fr.insee.pogues.configuration.RemoteMetadata; +import fr.insee.pogues.metadata.model.ColecticaItem; +import fr.insee.pogues.metadata.model.ColecticaItemRefList; +import fr.insee.pogues.metadata.model.Unit; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.web.client.RestTemplateBuilder; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpMethod; -import org.springframework.http.ResponseEntity; +import org.springframework.http.*; import org.springframework.stereotype.Service; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestTemplate; -import fr.insee.pogues.config.RemoteMetadata; -import fr.insee.pogues.metadata.model.ColecticaItem; -import fr.insee.pogues.metadata.model.ColecticaItemRefList; -import fr.insee.pogues.metadata.model.Unit; +import java.util.Arrays; +import java.util.List; +// TODO: change for webclient @Service public class MetadataClientImpl implements MetadataClient { @@ -42,7 +39,7 @@ public ColecticaItem getItem(String id) throws Exception { public List getItems(ColecticaItemRefList query) throws Exception { String url = String.format("%s/meta-data/items", remoteMetadata.getUrl()); MultiValueMap headers = new LinkedMultiValueMap<>(); - headers.add("Content-type", ContentType.APPLICATION_JSON.getMimeType()); + headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); HttpEntity request = new HttpEntity<>(query, headers); ResponseEntity response = restTemplate .exchange(url, HttpMethod.POST, request, ColecticaItem[].class); diff --git a/src/main/java/fr/insee/pogues/persistence/query/QuestionnairesServiceQueryPostgresqlImpl.java b/src/main/java/fr/insee/pogues/persistence/query/QuestionnairesServiceQueryPostgresqlImpl.java index eb1d9c90..8f4abc22 100644 --- a/src/main/java/fr/insee/pogues/persistence/query/QuestionnairesServiceQueryPostgresqlImpl.java +++ b/src/main/java/fr/insee/pogues/persistence/query/QuestionnairesServiceQueryPostgresqlImpl.java @@ -17,7 +17,7 @@ import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; -import fr.insee.pogues.config.auth.security.restrictions.StampsRestrictionsService; +import fr.insee.pogues.configuration.auth.security.restrictions.StampsRestrictionsService; import fr.insee.pogues.webservice.rest.PoguesException; /** diff --git a/src/main/java/fr/insee/pogues/search/repository/PoguesItemRepositoryImpl.java b/src/main/java/fr/insee/pogues/search/repository/PoguesItemRepositoryImpl.java index 89efbc6f..43c1e4c4 100644 --- a/src/main/java/fr/insee/pogues/search/repository/PoguesItemRepositoryImpl.java +++ b/src/main/java/fr/insee/pogues/search/repository/PoguesItemRepositoryImpl.java @@ -1,6 +1,6 @@ package fr.insee.pogues.search.repository; -import fr.insee.pogues.config.RemoteMetadata; +import fr.insee.pogues.configuration.RemoteMetadata; import fr.insee.pogues.search.model.DDIItem; import fr.insee.pogues.search.model.DataCollectionContext; import fr.insee.pogues.search.model.PoguesQuery; diff --git a/src/main/java/fr/insee/pogues/transforms/visualize/FOToPDFImpl.java b/src/main/java/fr/insee/pogues/transforms/visualize/FOToPDFImpl.java index 7b55a5a0..d65c71f5 100644 --- a/src/main/java/fr/insee/pogues/transforms/visualize/FOToPDFImpl.java +++ b/src/main/java/fr/insee/pogues/transforms/visualize/FOToPDFImpl.java @@ -1,6 +1,6 @@ package fr.insee.pogues.transforms.visualize; -import fr.insee.pogues.config.StaticResourcesForFOPConfig; +import fr.insee.pogues.configuration.StaticResourcesForFOPConfig; import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; import org.apache.fop.apps.Fop; diff --git a/src/main/java/fr/insee/pogues/utils/jersey/ObjectMapperProvider.java b/src/main/java/fr/insee/pogues/utils/jersey/ObjectMapperProvider.java deleted file mode 100644 index 0e53bd48..00000000 --- a/src/main/java/fr/insee/pogues/utils/jersey/ObjectMapperProvider.java +++ /dev/null @@ -1,34 +0,0 @@ -package fr.insee.pogues.utils.jersey; - - -import com.fasterxml.jackson.annotation.PropertyAccessor; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; - -import javax.ws.rs.ext.ContextResolver; -import javax.ws.rs.ext.Provider; - -import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.ANY; - -/** - * Created by acordier on 13/07/17. - */ -@Provider -public class ObjectMapperProvider implements ContextResolver { - - @Override - public ObjectMapper getContext(Class aClass) { - return createObjectMapper(); - } - - private ObjectMapper createObjectMapper() { - ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); - objectMapper.setVisibility(PropertyAccessor.FIELD, ANY); - - return objectMapper; - } - -} \ No newline at end of file diff --git a/src/main/java/fr/insee/pogues/utils/xsl/XSLTransformation.java b/src/main/java/fr/insee/pogues/utils/xsl/XSLTransformation.java deleted file mode 100644 index 0a88e302..00000000 --- a/src/main/java/fr/insee/pogues/utils/xsl/XSLTransformation.java +++ /dev/null @@ -1,39 +0,0 @@ -package fr.insee.pogues.utils.xsl; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import javax.xml.transform.Transformer; -import javax.xml.transform.stream.StreamResult; -import javax.xml.transform.stream.StreamSource; -import java.io.File; - -/** - * Main Saxon Service used to perform XSLT transformations - * - * @author I6VWID - * - */ -public class XSLTransformation { - - final static Logger logger = LogManager.getLogger(XSLTransformation.class); - - /** - * Main Saxon transformation method - * - * @param transformer - * : The defined transformer with his embedded parameters - * (defined in the other methods of this class) - * @param xmlInput - * : The input xml file where the XSLT will be applied - * @param xmlOutput - * : The output xml file after the transformation - * @throws Exception - * : Mainly if the input/output files path are incorrect - */ - public void xslTransform(Transformer transformer, String xmlInput, String xmlOutput) throws Exception { - logger.debug("Starting xsl transformation -Input : " + xmlInput + " -Output : " + xmlOutput); - transformer.transform(new StreamSource(new File(xmlInput)), new StreamResult(new File(xmlOutput))); - } - -} diff --git a/src/main/java/fr/insee/pogues/webservice/rest/PoguesPersistence.java b/src/main/java/fr/insee/pogues/webservice/rest/PoguesPersistence.java index a6998d36..de023b27 100644 --- a/src/main/java/fr/insee/pogues/webservice/rest/PoguesPersistence.java +++ b/src/main/java/fr/insee/pogues/webservice/rest/PoguesPersistence.java @@ -1,10 +1,9 @@ package fr.insee.pogues.webservice.rest; -import fr.insee.pogues.config.auth.UserProvider; -import fr.insee.pogues.config.auth.user.User; +import fr.insee.pogues.configuration.auth.UserProvider; +import fr.insee.pogues.configuration.auth.user.User; import fr.insee.pogues.persistence.service.QuestionnairesService; import fr.insee.pogues.persistence.service.VariablesService; -import fr.insee.pogues.transforms.visualize.PoguesJSONToPoguesJSONDeref; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -12,17 +11,10 @@ import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; -import java.util.*; - -import javax.ws.rs.Consumes; -import javax.ws.rs.Produces; -import javax.ws.rs.core.MediaType; - import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.json.simple.JSONArray; import org.json.simple.JSONObject; -import org.json.simple.parser.JSONParser; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; import org.springframework.http.HttpStatus;