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
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ repositories {
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'com.google.guava:guava:33.4.8-jre'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.6'
implementation 'gg.jte:jte:3.2.1'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import jakarta.validation.constraints.Positive;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.huebert.iotfsdb.IotfsdbProperties;
import org.huebert.iotfsdb.properties.IotfsdbProperties;
import org.huebert.iotfsdb.schema.PartitionPeriod;
import org.huebert.iotfsdb.schema.SeriesDefinition;
import org.huebert.iotfsdb.schema.SeriesFile;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.huebert.iotfsdb;
package org.huebert.iotfsdb.properties;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
Expand All @@ -21,4 +21,6 @@ public class IotfsdbProperties {

private boolean ui = true;

private SecurityProperties security = new SecurityProperties();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.huebert.iotfsdb.properties;

import lombok.Data;

import java.util.ArrayList;
import java.util.List;

@Data
public class SecurityProperties {

private boolean enabled = false;

private List<UserProperties> users = new ArrayList<>();

}
18 changes: 18 additions & 0 deletions src/main/java/org/huebert/iotfsdb/properties/UserProperties.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.huebert.iotfsdb.properties;

import lombok.Data;
import org.huebert.iotfsdb.security.UserRole;

import java.util.HashSet;
import java.util.Set;

@Data
public class UserProperties {

private String username;

private String password;

private Set<UserRole> roles = new HashSet<>();

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.huebert.iotfsdb.schema.SeriesFile;
import org.huebert.iotfsdb.service.SeriesService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
Expand All @@ -28,6 +29,7 @@
@RestController
@RequestMapping("/v2/series")
@ConditionalOnProperty(prefix = "iotfsdb", value = "read-only", havingValue = "false")
@PreAuthorize("hasRole('API_WRITE')") //Remove ConditionalOnProperty and just add property check here
public class MutatingSeriesController {

private final SeriesService seriesService;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import org.huebert.iotfsdb.service.ParallelUtil;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
Expand All @@ -32,6 +33,7 @@
@RestController
@RequestMapping("/v2/data")
@ConditionalOnProperty(prefix = "iotfsdb", value = "read-only", havingValue = "false")
@PreAuthorize("hasRole('API_WRITE')")
public class MutatingSeriesDataController {

private final InsertService insertService;
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/org/huebert/iotfsdb/rest/SeriesController.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.huebert.iotfsdb.schema.SeriesFile;
import org.huebert.iotfsdb.service.SeriesService;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
Expand All @@ -27,6 +28,7 @@
@Slf4j
@RestController
@RequestMapping("/v2/series")
@PreAuthorize("hasRole('API_READ') or hasRole('API_WRITE')")
public class SeriesController {

private final SeriesService seriesService;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import org.huebert.iotfsdb.service.TimeConverter;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
Expand All @@ -28,6 +29,7 @@
@Slf4j
@RestController
@RequestMapping("/v2/data")
@PreAuthorize("hasRole('API_READ') or hasRole('API_WRITE')")
public class SeriesDataController {

private final ExportService exportService;
Expand Down
54 changes: 54 additions & 0 deletions src/main/java/org/huebert/iotfsdb/security/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package org.huebert.iotfsdb.security;

import org.huebert.iotfsdb.properties.IotfsdbProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableMethodSecurity
public class SecurityConfig {

private final IotfsdbProperties properties;

public SecurityConfig(IotfsdbProperties properties) {
this.properties = properties;
}

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
Customizer<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry> authorizer;
if (properties.getSecurity().isEnabled()) {
authorizer = a -> a.anyRequest().authenticated();
} else {
authorizer = a -> a.anyRequest().permitAll();
}
return http.csrf(AbstractHttpConfigurer::disable)

Check failure

Code scanning / CodeQL

Disabled Spring CSRF protection High

CSRF vulnerability due to protection being disabled.

Copilot Autofix

AI 10 months ago

To fix the issue, CSRF protection should be enabled by default. Instead of disabling CSRF protection, the configuration should allow for proper CSRF token validation. If there are specific endpoints that do not require CSRF protection (e.g., APIs used by non-browser clients), those endpoints can be explicitly excluded from CSRF protection using Spring's csrf().ignoringRequestMatchers() method.

The changes will involve:

  1. Removing the http.csrf(AbstractHttpConfigurer::disable) line.
  2. Configuring CSRF protection properly, ensuring that it is enabled for browser-accessible endpoints.
  3. Optionally, excluding specific endpoints from CSRF protection if necessary.

Suggested changeset 1
src/main/java/org/huebert/iotfsdb/security/SecurityConfig.java

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/main/java/org/huebert/iotfsdb/security/SecurityConfig.java b/src/main/java/org/huebert/iotfsdb/security/SecurityConfig.java
--- a/src/main/java/org/huebert/iotfsdb/security/SecurityConfig.java
+++ b/src/main/java/org/huebert/iotfsdb/security/SecurityConfig.java
@@ -34,3 +34,3 @@
         }
-        return http.csrf(AbstractHttpConfigurer::disable)
+        return http.csrf(csrf -> csrf.ignoringRequestMatchers("/api/**")) // Enable CSRF protection, exclude API endpoints
                 .formLogin(Customizer.withDefaults())
EOF
@@ -34,3 +34,3 @@
}
return http.csrf(AbstractHttpConfigurer::disable)
return http.csrf(csrf -> csrf.ignoringRequestMatchers("/api/**")) // Enable CSRF protection, exclude API endpoints
.formLogin(Customizer.withDefaults())
Copilot is powered by AI and may make mistakes. Always verify output.
.formLogin(Customizer.withDefaults())
.authorizeHttpRequests(authorizer)
.httpBasic(Customizer.withDefaults())
.build();
}

@Bean
public UserDetailsService userDetailsService() {
UserDetails[] userDetails = properties.getSecurity().getUsers().stream()
.map(up -> User.builder()
.username(up.getUsername())
.password(up.getPassword())
.roles(up.getRoles().stream().map(Enum::name).toArray(String[]::new))
.build())
.toArray(UserDetails[]::new);
return new InMemoryUserDetailsManager(userDetails);
}

}
13 changes: 13 additions & 0 deletions src/main/java/org/huebert/iotfsdb/security/UserRole.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.huebert.iotfsdb.security;

public enum UserRole {
API_READ,
API_WRITE,
UI_READ,
UI_WRITE;

public String getRoleName() {
return "ROLE_" + name();
}

}
2 changes: 1 addition & 1 deletion src/main/java/org/huebert/iotfsdb/service/DataService.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import lombok.extern.slf4j.Slf4j;
import org.huebert.iotfsdb.IotfsdbProperties;
import org.huebert.iotfsdb.properties.IotfsdbProperties;
import org.huebert.iotfsdb.partition.PartitionAdapter;
import org.huebert.iotfsdb.persistence.PartitionByteBuffer;
import org.huebert.iotfsdb.persistence.PersistenceAdapter;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import com.google.common.collect.Range;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import org.huebert.iotfsdb.IotfsdbProperties;
import org.huebert.iotfsdb.properties.IotfsdbProperties;
import org.huebert.iotfsdb.schema.FindDataRequest;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.extern.slf4j.Slf4j;
import org.huebert.iotfsdb.IotfsdbProperties;
import org.huebert.iotfsdb.properties.IotfsdbProperties;
import org.huebert.iotfsdb.partition.BytePartition;
import org.huebert.iotfsdb.partition.CurvedMappedPartition;
import org.huebert.iotfsdb.partition.DoublePartition;
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/org/huebert/iotfsdb/ui/DataUiController.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.huebert.iotfsdb.ui.service.PlotData;
import org.huebert.iotfsdb.ui.service.SearchParser;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
Expand All @@ -32,6 +33,7 @@
@Controller
@RequestMapping("/ui/data")
@ConditionalOnProperty(prefix = "iotfsdb", value = "ui", havingValue = "true")
@PreAuthorize("hasRole('UI_READ') or hasRole('UI_WRITE')")
public class DataUiController {

private final QueryService queryService;
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/org/huebert/iotfsdb/ui/IndexUiController.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.huebert.iotfsdb.ui;

import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
Expand All @@ -10,6 +11,7 @@
@Controller
@RequestMapping("/ui")
@ConditionalOnProperty(prefix = "iotfsdb", value = "ui", havingValue = "true")
@PreAuthorize("hasRole('UI_READ') or hasRole('UI_WRITE')")
public class IndexUiController {

@GetMapping
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.huebert.iotfsdb.ui.service.ObjectEncoder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.DeleteMapping;
Expand All @@ -33,6 +34,7 @@
@Controller
@RequestMapping("/ui/series")
@ConditionalOnExpression("${iotfsdb.ui:true} and not ${iotfsdb.read-only:false}")
@PreAuthorize("hasRole('UI_WRITE')")
public class MutatingSeriesUiController {

private final SeriesService seriesService;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.huebert.iotfsdb.service.ImportService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
Expand All @@ -24,6 +25,7 @@
@Controller
@RequestMapping("/ui/transfer")
@ConditionalOnExpression("${iotfsdb.ui:true} and not ${iotfsdb.read-only:false}")
@PreAuthorize("hasRole('UI_WRITE')")
public class MutatingTransferUiController {

private final ImportService importService;
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/org/huebert/iotfsdb/ui/SeriesUiController.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
Expand All @@ -29,6 +30,7 @@
@Controller
@RequestMapping("/ui/series")
@ConditionalOnProperty(prefix = "iotfsdb", value = "ui", havingValue = "true")
@PreAuthorize("hasRole('UI_READ') or hasRole('UI_WRITE')")
public class SeriesUiController {

private final SeriesService seriesService;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.huebert.iotfsdb.ui.service.ExportUiService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
Expand All @@ -15,6 +16,7 @@
@Controller
@RequestMapping("/ui/transfer")
@ConditionalOnProperty(prefix = "iotfsdb", value = "ui", havingValue = "true")
@PreAuthorize("hasRole('UI_READ') or hasRole('UI_WRITE')")
public class TransferUiController {

private final ExportUiService exportService;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@

import lombok.Builder;
import lombok.Data;
import org.huebert.iotfsdb.security.UserRole;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.info.BuildProperties;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;

@Service
Expand All @@ -22,8 +25,10 @@ public BasePageService(BuildProperties buildProperties) {
}

public BasePage getBasePage() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
boolean hasUiWrite = authentication.getAuthorities().stream().anyMatch(a -> a.getAuthority().equals(UserRole.UI_WRITE.getRoleName()));
return BasePage.builder()
.readOnly(readOnly)
.readOnly(readOnly || !hasUiWrite)
.version(buildProperties.getVersion())
.springdocEnabled(springdocEnabled)
.build();
Expand Down
11 changes: 11 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,17 @@ iotfsdb:
max-query-size: 1000
partition-cache: "expireAfterAccess=5m,maximumSize=10000,softValues"
ui: true
security:
enabled: true
users:
- username: "iotfsdb"
password: "{noop}iotfsdb"
# password: "$2a$10$7nSkpsLWbxNtFEZ8JA9wX.t7TMFjATurl36cufGp6PkiMEj33Vxsi"
roles:
- API_READ
- API_WRITE
- UI_READ
- UI_WRITE

gg:
jte:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package org.huebert.iotfsdb.persistence;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.huebert.iotfsdb.IotfsdbProperties;
import org.huebert.iotfsdb.properties.IotfsdbProperties;
import org.huebert.iotfsdb.schema.NumberType;
import org.huebert.iotfsdb.schema.PartitionPeriod;
import org.huebert.iotfsdb.schema.SeriesDefinition;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package org.huebert.iotfsdb.service;

import org.huebert.iotfsdb.IotfsdbProperties;
import org.huebert.iotfsdb.properties.IotfsdbProperties;
import org.huebert.iotfsdb.partition.PartitionAdapter;
import org.huebert.iotfsdb.persistence.PartitionByteBuffer;
import org.huebert.iotfsdb.persistence.PersistenceAdapter;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package org.huebert.iotfsdb.service;

import com.google.common.collect.Range;
import org.huebert.iotfsdb.IotfsdbProperties;
import org.huebert.iotfsdb.properties.IotfsdbProperties;
import org.huebert.iotfsdb.schema.FindDataRequest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import com.google.common.collect.Range;
import com.google.common.collect.RangeMap;
import org.huebert.iotfsdb.IotfsdbProperties;
import org.huebert.iotfsdb.properties.IotfsdbProperties;
import org.huebert.iotfsdb.partition.BytePartition;
import org.huebert.iotfsdb.partition.CurvedMappedPartition;
import org.huebert.iotfsdb.partition.DoublePartition;
Expand Down
Loading
Loading