diff --git a/calls.http b/calls.http
index 200ae773..75c442f7 100644
--- a/calls.http
+++ b/calls.http
@@ -10,6 +10,7 @@ Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
&client_id=odh-mobility-datacollector-development
&client_secret=7bd46f8f-c296-416d-a13d-dc81e68d0830
+&scope=openid
### Get access token for the writer (TEST DB)
# @name login
@@ -33,7 +34,7 @@ grant_type=password
#@host=https://mobility.share.opendatahub.com
@host=http://localhost:8999
-@host=https://mobility.share.opendatahub.testingmachine.eu
+#@host=https://mobility.share.opendatahub.testingmachine.eu
@authtoken = {{login.response.body.access_token}}
diff --git a/writer/pom.xml b/writer/pom.xml
index 50ddc464..424dd9fe 100644
--- a/writer/pom.xml
+++ b/writer/pom.xml
@@ -46,6 +46,10 @@ SPDX-License-Identifier: CC0-1.0
org.springframework.boot
spring-boot-starter-security
+
+ org.springframework.security
+ spring-security-config
+
org.springframework.boot
spring-boot-starter-oauth2-client
@@ -68,10 +72,6 @@ SPDX-License-Identifier: CC0-1.0
spring-boot-starter-test
test
-
- org.springframework.security
- spring-security-config
-
@@ -111,7 +111,6 @@ SPDX-License-Identifier: CC0-1.0
runtime
-
diff --git a/writer/src/main/java/it/bz/idm/bdp/writer/config/WebSecurity.java b/writer/src/main/java/it/bz/idm/bdp/writer/config/WebSecurity.java
index 6e535eb1..151fb2ab 100644
--- a/writer/src/main/java/it/bz/idm/bdp/writer/config/WebSecurity.java
+++ b/writer/src/main/java/it/bz/idm/bdp/writer/config/WebSecurity.java
@@ -4,13 +4,22 @@
package it.bz.idm.bdp.writer.config;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.stream.Collectors;
+
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.Customizer;
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.SimpleGrantedAuthority;
+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.authentication.session.NullAuthenticatedSessionStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
@@ -33,17 +42,48 @@ public AuthenticationManager authenticationManager(HttpSecurity http) throws Exc
return http.getSharedObject(AuthenticationManagerBuilder.class).build();
}
+ // For some reason, spring does not read the role claim from the jwt.
+ // Since we're basing our authorization on roles, we have to extend the spring security jwt converter to get that functionality.
+ // see https://stackoverflow.com/questions/65518172/spring-security-cant-extract-roles-from-jwt for reference
+ //
+ // Note that this is pretty specific to our use case and only maps roles. If we ever need scope or other claims,
+ // implement that here (and make it a separate class implementing the Converter interface)
+ @SuppressWarnings("unchecked")
+ private Converter jwtConverter(){
+ JwtAuthenticationConverter jwtConverter = new JwtAuthenticationConverter();
+ jwtConverter.setJwtGrantedAuthoritiesConverter(jwt -> {
+ // see org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter.java
+ // This lambda functions as a replacement / reimplementation for that class
+ Collection roles = new ArrayList<>();
+ Object roleClaim = jwt.getClaim("roles");
+ if (roleClaim instanceof Collection){
+ roles.addAll((Collection) roleClaim);
+ }
+
+ return roles
+ .stream()
+ .map(role -> new SimpleGrantedAuthority(role))
+ .collect(Collectors.toList());
+ });
+ return jwtConverter;
+ }
+
@Bean
public SecurityFilterChain oauthFilter(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
+ // health check always accessible
.requestMatchers("/actuator/**")
.permitAll()
+ // Authorize based on role claim ROLE_ADMIN
.requestMatchers("/json/**")
.hasRole("ADMIN")
- .anyRequest().permitAll());
- http.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));
+ // permitAll is ported over from legacy code, not sure if it doesn't make more sense to deny all other requests
+ .anyRequest().permitAll())
+ .oauth2Client(Customizer.withDefaults());
+ // Register the oauth server, and our custom jwt converter
+ http.oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtConverter())));
return http.build();
}
}
diff --git a/writer/src/main/resources/META-INF/persistence.xml b/writer/src/main/resources/META-INF/persistence.xml
index e4988316..3e9ede6b 100644
--- a/writer/src/main/resources/META-INF/persistence.xml
+++ b/writer/src/main/resources/META-INF/persistence.xml
@@ -13,7 +13,7 @@ SPDX-License-Identifier: CC0-1.0
-
+
diff --git a/writer/src/main/resources/application.properties b/writer/src/main/resources/application.properties
index 4b1e3f62..8a06d34f 100644
--- a/writer/src/main/resources/application.properties
+++ b/writer/src/main/resources/application.properties
@@ -13,19 +13,11 @@ management.endpoint.health.enabled=true
### Security
security.cors.allowedOrigins=${SECURITY_ALLOWED_ORIGINS:*}
-#keycloak.auth-server-url=${KEYCLOAK_URL:https://auth.opendatahub.testingmachine.eu/auth}
-#keycloak.ssl-required=${KEYCLOAK_SSL_REQUIRED:none}
-#keycloak.realm=${KEYCLOAK_REALM:noi}
-#keycloak.resource=${KEYCLOAK_CLIENT_ID:odh-mobility-writer-development}
-#keycloak.use-resource-role-mappings=true
-#keycloak.public-client=false
-#keycloak.bearer-only=true
-#keycloak.securityConstraints[0].authRoles[0]=ROLE_ADMIN
-#keycloak.securityConstraints[0].securityCollections[0].patterns[0]=/json/*
-
spring.security.oauth2.client.provider.keycloak.issuer-uri=${KEYCLOAK_URL:https://auth.opendatahub.testingmachine.eu/auth}/realms/${KEYCLOAK_REALM:noi}
-spring.security.oauth2.client.provider.keycloak.user-name-attribute=preferred_username
+#spring.security.oauth2.client.provider.keycloak.user-name-attribute=preferred_username
spring.security.oauth2.resourceserver.jwt.issuer-uri=${KEYCLOAK_URL:https://auth.opendatahub.testingmachine.eu/auth}/realms/${KEYCLOAK_REALM:noi}
+
+spring.security.oauth2.client.registration.keycloak.provider=keycloak
spring.security.oauth2.client.registration.keycloak.client-id=${KEYCLOAK_CLIENT_ID:odh-mobility-writer-development}
spring.security.oauth2.client.registration.keycloak.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.keycloak.scope=openid
@@ -45,6 +37,7 @@ hibernate.hikari.maximumPoolSize=${HIBERNATE_MAX_POOL_SIZE:2}
spring.datasource.url=jdbc:postgresql://${POSTGRES_SERVER:localhost}:${POSTGRES_PORT:5555}/${POSTGRES_DB:bdp}?currentSchema=${POSTGRES_SCHEMA:intimev2},public
spring.datasource.username=${POSTGRES_USERNAME:bdp}
spring.datasource.password=${POSTGRES_PASSWORD:password}
+spring.datasource.hikari.maximum-pool-size=${HIBERNATE_MAX_POOL_SIZE:2}
spring.jpa.hibernate.ddl-auto=none
spring.jpa.hibernate.naming.implicit-strategy=it.bz.idm.bdp.dal.util.SchemaGeneratorImplicitNamingStrategy
spring.jpa.properties.hibernate.format_sql=true
diff --git a/writer/src/test/resources/META-INF/persistence.xml b/writer/src/test/resources/META-INF/persistence.xml
index 74b0f928..02941007 100644
--- a/writer/src/test/resources/META-INF/persistence.xml
+++ b/writer/src/test/resources/META-INF/persistence.xml
@@ -27,7 +27,7 @@ SPDX-License-Identifier: CC0-1.0
it.bz.idm.bdp.dal.MeasurementStringHistory
-
+