Skip to content
Draft
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
6 changes: 4 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ dist/
nbdist/
nbactions.xml
nb-configuration.xml
META-INF/
datavault-broker/src/main/webapp/WEB-INF/glassfish-web.xml
datavault-webapp/src/main/webapp/WEB-INF/glassfish-web.xml
## Ignore all *.properties file in root folder, EXCEPT datavault.properties (the default)
Expand All @@ -43,4 +42,7 @@ datavault-webapp/pids
# ignore intellij run files
.run/
TEMPLATES/*
dv5/local-db/docker/backup.D.SPEED.sql
dv5/local-db/docker/backup.D.SPEED.sql
# this can set the java version for the Intellij IDE and will set java versions for terminals too if 'sdk config set sdkman_auto_env true'
.sdkmanrc
SWAGGER_OPENAPI/*.zip
9 changes: 9 additions & 0 deletions datavault-broker/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,15 @@
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-otel</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
import java.util.List;
import java.util.function.Function;

import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import org.datavaultplatform.broker.actuator.CurrentTimeEndpoint;
import org.datavaultplatform.broker.actuator.LocalFileStoreEndpoint;
import org.datavaultplatform.broker.actuator.MemoryInfoEndpoint;
import org.datavaultplatform.broker.actuator.SftpFileStoreEndpoint;
import org.datavaultplatform.broker.services.ArchiveStoreService;
import org.datavaultplatform.broker.services.FileStoreService;
import org.datavaultplatform.common.actuator.ActuatorHealthSecurityAdvice;
import org.datavaultplatform.common.actuator.ActuatorInfoSecurityAdvice;
import org.datavaultplatform.common.actuator.ActuatorSecurityAdvice;
import org.datavaultplatform.common.util.StorageClassNameResolver;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
Expand All @@ -21,6 +22,21 @@

public class ActuatorConfig {

@Bean
ActuatorInfoSecurityAdvice actuatorInfoSecurityAdvice() {
return new ActuatorInfoSecurityAdvice();
}

@Bean
ActuatorHealthSecurityAdvice actuatorHealthSecurityAdvice() {
return new ActuatorHealthSecurityAdvice();
}

@Bean
ActuatorSecurityAdvice actuatorSecurityAdvice() {
return new ActuatorSecurityAdvice();
}

@Bean
Clock clock() {
return Clock.systemDefaultZone();
Expand Down Expand Up @@ -56,11 +72,4 @@ public SftpFileStoreEndpoint sftpFileStoreEndpoint(@Autowired FileStoreService
public LocalFileStoreEndpoint localFileStoreEndpoint(@Autowired ArchiveStoreService archiveStoreService) {
return new LocalFileStoreEndpoint(archiveStoreService);
}

@Bean
public OpenAPI openAPI() {
return new OpenAPI().info(new Info().title("DataVault Broker")
.description("broker application")
.version("v0.0.1"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
Expand All @@ -19,6 +20,8 @@
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;

import static org.springframework.security.config.Customizer.withDefaults;

@ConditionalOnExpression("${broker.security.enabled:true}")
@Configuration
@Slf4j
Expand All @@ -45,26 +48,41 @@ public DaoAuthenticationProvider actuatorAuthenticationProvider(@Qualifier("actu
return provider;
}

@Bean
@Order(0)
@Profile("database")
public SecurityFilterChain traceApiFilterChain(HttpSecurity http, AuthenticationProvider actuatorAuthenticationProvider) throws Exception {
return http
.securityMatcher("/trace/**")
.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
.authenticationProvider(actuatorAuthenticationProvider)
.httpBasic(withDefaults())
.build();
}

@Bean
@Order(1)
public SecurityFilterChain actuatorSecurityFilterChain(HttpSecurity http,
@Qualifier("actuatorAuthenticationProvider") AuthenticationProvider authenticationProvider) throws Exception {
http.securityMatcher("/actuator/**","/v3/**","/swagger-ui/**")
.authenticationProvider( authenticationProvider )
http.securityMatcher("/actuator/**")
.authenticationProvider(authenticationProvider)
.csrf(AbstractHttpConfigurer::disable)
.httpBasic(Customizer.withDefaults())
.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests( authz -> {
authz.requestMatchers(
"/v3/**",
"/swagger-ui/**",
"/actuator/info",
"/actuator/health",
"/actuator/metrics",
"/actuator/mappings",
"/actuator/memoryinfo").permitAll();
authz.anyRequest().fullyAuthenticated();
});
.authorizeHttpRequests(authz -> authz
// 1. Allow these specific endpoints without login
.requestMatchers(
"/actuator",
"/actuator/info",
"/actuator/health"
).permitAll()

// 2. Require authentication for everything else covered by the securityMatcher
// (This includes Swagger, V3 docs, and the rest of the actuator endpoints)
.anyRequest().authenticated()
);

return http.build();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,16 @@
package org.datavaultplatform.broker.config;

import static org.datavaultplatform.common.util.Constants.HEADER_USER_ID;

import java.io.IOException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.datavaultplatform.broker.authentication.RestAuthenticationFailureHandler;
import org.datavaultplatform.broker.authentication.RestAuthenticationFilter;
import org.datavaultplatform.broker.authentication.RestAuthenticationProvider;
import org.datavaultplatform.broker.authentication.RestAuthenticationSuccessHandler;
import org.datavaultplatform.broker.authentication.RestWebAuthenticationDetailsSource;
import org.datavaultplatform.broker.authentication.*;
import org.datavaultplatform.broker.services.AdminService;
import org.datavaultplatform.broker.services.ClientsService;
import org.datavaultplatform.broker.services.RolesAndPermissionsService;
import org.datavaultplatform.broker.services.UsersService;
import org.datavaultplatform.common.config.SecurityMethod;
import org.datavaultplatform.common.util.Constants;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
Expand All @@ -28,6 +22,7 @@
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
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;
Expand All @@ -45,6 +40,12 @@
import org.springframework.web.filter.CommonsRequestLoggingFilter;
import org.springframework.web.filter.GenericFilterBean;

import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;

import static org.datavaultplatform.common.util.Constants.HEADER_USER_ID;

@SuppressWarnings("DefaultAnnotationParam")
@ConditionalOnExpression("${broker.security.enabled:true}")
@Configuration
Expand All @@ -60,7 +61,6 @@ public class SecurityConfig {
WebSecurityCustomizer webSecurityCustomizer() {
return web -> {
web.debug(securityDebug);
web.ignoring().requestMatchers("/retrieve/**");
};
}

Expand All @@ -78,24 +78,66 @@ public SecurityFilterChain securityFilterChain(
.addFilterAt(restFilter(authenticationManager), AbstractPreAuthenticatedProcessingFilter.class)
.sessionManagement(cust -> cust.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.exceptionHandling(ex -> ex.authenticationEntryPoint(http403EntryPoint()))
.authorizeHttpRequests(authz -> authz
.requestMatchers("/admin/users/**").hasAuthority("ROLE_ADMIN")
.requestMatchers("/admin/archivestores/**").hasAuthority("ROLE_ADMIN_ARCHIVESTORES")
.requestMatchers("/admin/deposits/**").hasAuthority("ROLE_ADMIN_DEPOSITS")
.requestMatchers("/admin/retrieves/**").hasAuthority("ROLE_ADMIN_RETRIEVES")
.requestMatchers("/admin/vaults/**").hasAuthority("ROLE_ADMIN_VAULTS")
.requestMatchers("/admin/pendingVaults/**").hasAuthority("ROLE_ADMIN_PENDING_VAULTS")
.requestMatchers("/admin/events/**").hasAuthority("ROLE_ADMIN_EVENTS")
.requestMatchers("/admin/billing/**").hasAuthority("ROLE_ADMIN_BILLING")
/* TODO : DavidHay : no controller mapped to /admin/reviews ! */
.requestMatchers("/admin/reviews/**").hasAuthority("ROLE_ADMIN_REVIEWS")
.requestMatchers("/admin/paused/deposit/toggle/**").hasAuthority("ROLE_ADMIN")
.requestMatchers("/admin/paused/retrieve/toggle/**").hasAuthority("ROLE_ADMIN")
.anyRequest().authenticated());
.authorizeHttpRequests(getAuthZCustomizer());

return http.build();
}

public static final Map<String,String> SECURITY_PATH_MAP = new LinkedHashMap<>();

static {
SECURITY_PATH_MAP.put("/admin/users/**", "hasAuthority('ROLE_ADMIN')");
SECURITY_PATH_MAP.put("/admin/archivestores/**", "hasAuthority('ROLE_ADMIN_ARCHIVESTORES')");
SECURITY_PATH_MAP.put("/admin/deposits/**", "hasAuthority('ROLE_ADMIN_DEPOSITS')");
SECURITY_PATH_MAP.put("/admin/retrieves/**", "hasAuthority('ROLE_ADMIN_RETRIEVES')");
SECURITY_PATH_MAP.put("/admin/vaults/**", "hasAuthority('ROLE_ADMIN_VAULTS')");
SECURITY_PATH_MAP.put("/admin/pendingVaults/**", "hasAuthority('ROLE_ADMIN_PENDING_VAULTS')");
SECURITY_PATH_MAP.put("/admin/events/**", "hasAuthority('ROLE_ADMIN_EVENTS')");
SECURITY_PATH_MAP.put("/admin/billing/**", "hasAuthority('ROLE_ADMIN_BILLING')");
/* TODO : DavidHay : no controller mapped to /admin/reviews ! */
SECURITY_PATH_MAP.put("/admin/reviews/**", "hasAuthority('ROLE_ADMIN_REVIEWS')");
SECURITY_PATH_MAP.put("/admin/paused/deposit/toggle/**", "hasAuthority('ROLE_ADMIN')");
SECURITY_PATH_MAP.put("/admin/paused/retrieve/toggle/**", "hasAuthority('ROLE_ADMIN')");
}

public Customizer<org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry> getAuthZCustomizer() {
return authz -> {

for(Map.Entry<String, String> entry : SECURITY_PATH_MAP.entrySet()) {
var matchers = authz.requestMatchers(entry.getKey());
SecurityMethod sm = SecurityMethod.from(entry.getValue());
if (sm.isPermitAll()) {
matchers.permitAll();

} else if (sm.isHasRole()) {
matchers.hasRole(sm.arg());

} else if (sm.isHasAuthority()) {
matchers.hasAuthority(sm.arg());

} else {
throw new RuntimeException("Unknown security method: " + sm.method());
}
}
authz.anyRequest().authenticated();

// authz
// .requestMatchers("/admin/users/**").hasAuthority("ROLE_ADMIN")
// .requestMatchers("/admin/archivestores/**").hasAuthority("ROLE_ADMIN_ARCHIVESTORES")
// .requestMatchers("/admin/deposits/**").hasAuthority("ROLE_ADMIN_DEPOSITS")
// .requestMatchers("/admin/retrieves/**").hasAuthority("ROLE_ADMIN_RETRIEVES")
// .requestMatchers("/admin/vaults/**").hasAuthority("ROLE_ADMIN_VAULTS")
// .requestMatchers("/admin/pendingVaults/**").hasAuthority("ROLE_ADMIN_PENDING_VAULTS")
// .requestMatchers("/admin/events/**").hasAuthority("ROLE_ADMIN_EVENTS")
// .requestMatchers("/admin/billing/**").hasAuthority("ROLE_ADMIN_BILLING")
// /* TODO : DavidHay : no controller mapped to /admin/reviews ! */
// .requestMatchers("/admin/reviews/**").hasAuthority("ROLE_ADMIN_REVIEWS")
// .requestMatchers("/admin/paused/deposit/toggle/**").hasAuthority("ROLE_ADMIN")
// .requestMatchers("/admin/paused/retrieve/toggle/**").hasAuthority("ROLE_ADMIN")
// .anyRequest().authenticated();
};
}

/**
<bean id="restFilter" class="org.datavaultplatform.broker.authentication.RestAuthenticationFilter">
<property name="principalRequestHeader" value="X-UserID" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.datavaultplatform.broker.config;

import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator;
import io.opentelemetry.context.propagation.ContextPropagators;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@Import(UserMdcFilter.class)
public class TracingConfig {

@Bean
ContextPropagators otelContextPropagators() {
return ContextPropagators.create(W3CTraceContextPropagator.getInstance());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package org.datavaultplatform.broker.config;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.datavaultplatform.common.util.MdcUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.core.annotation.Order;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

@Component
@ConditionalOnClass(Authentication.class)
@Order(101) // Ensure it runs after Spring Security Filter (default 100)
public class UserMdcFilter extends OncePerRequestFilter {

@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {

Authentication auth = SecurityContextHolder.getContext().getAuthentication();

String username = null;
if (auth != null && auth.isAuthenticated()) {
username = auth.getName();
}
username = MdcUtils.getMdcUserName(username);
MdcUtils.addUserNameToMdc(username);

try {
filterChain.doFilter(request, response);
} finally {
MdcUtils.removeMdcUserName();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package org.datavaultplatform.broker.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
@Import(TracingConfig.class)
public class WebConfig implements WebMvcConfigurer {

@Override
Expand Down
Loading