Enhanced owner validation and property checks in services:

- **Backend**:
  - Check ownership before execution in DestinationService and PremisesService
  - Added Valid period check in pre flight check
  - Fixed allowed headers in cors config
  - added user groups to migration
This commit is contained in:
Jan 2025-10-30 13:39:59 +01:00
parent 302967e645
commit a7ea4d97d2
30 changed files with 425 additions and 315 deletions

View file

@ -14,17 +14,24 @@
</div> </div>
<div class="bulk-operation-caption">dataset</div> <div class="bulk-operation-caption">dataset</div>
<div class="bulk-operation-data"> <div class="bulk-operation-data">
<radio-option name="export-dataset" value="NODE" v-model="exportDataset">nodes</radio-option> <radio-option v-if="canModify('nodes')" name="export-dataset" value="NODE" v-model="exportDataset">nodes
<radio-option name="export-dataset" value="COUNTRY_MATRIX" v-model="exportDataset">kilometer rates
</radio-option> </radio-option>
<radio-option name="export-dataset" value="CONTAINER_RATE" v-model="exportDataset">container rates <radio-option v-if="canModify('rates')" name="export-dataset" value="COUNTRY_MATRIX"
v-model="exportDataset">kilometer rates
</radio-option>
<radio-option v-if="canModify('rates')" name="export-dataset" value="CONTAINER_RATE"
v-model="exportDataset">container rates
</radio-option>
<radio-option v-if="canModify('materials')" name="export-dataset" value="MATERIAL" v-model="exportDataset">
materials
</radio-option>
<radio-option v-if="canModify('packaging')" name="export-dataset" value="PACKAGING" v-model="exportDataset">
packaging
</radio-option> </radio-option>
<radio-option name="export-dataset" value="MATERIAL" v-model="exportDataset">materials</radio-option>
<radio-option name="export-dataset" value="PACKAGING" v-model="exportDataset">packaging</radio-option>
</div> </div>
<div class="bulk-operation-caption">validity period</div> <div v-if="canModify('rates')" class="bulk-operation-caption">validity period</div>
<div class="bulk-operation-data"> <div v-if="canModify('rates')" class="bulk-operation-data">
<div class="period-select-container"> <div class="period-select-container">
<dropdown :options="periods" <dropdown :options="periods"
emptyText="No property set available" emptyText="No property set available"
@ -48,13 +55,20 @@
<div class="bulk-operation-header">Import</div> <div class="bulk-operation-header">Import</div>
<div class="bulk-operation-caption">dataset</div> <div class="bulk-operation-caption">dataset</div>
<div class="bulk-operation-data"> <div class="bulk-operation-data">
<radio-option name="import-dataset" value="NODE" v-model="importDataset">nodes</radio-option> <radio-option v-if="canModify('nodes')" name="import-dataset" value="NODE" v-model="importDataset">nodes
<radio-option name="import-dataset" value="COUNTRY_MATRIX" v-model="importDataset">kilometer rates
</radio-option> </radio-option>
<radio-option name="import-dataset" value="CONTAINER_RATE" v-model="importDataset">container rates <radio-option v-if="canModify('rates')" name="import-dataset" value="COUNTRY_MATRIX"
v-model="importDataset">kilometer rates
</radio-option>
<radio-option v-if="canModify('rates')" name="import-dataset" value="CONTAINER_RATE"
v-model="importDataset">container rates
</radio-option>
<radio-option v-if="canModify('materials')" name="import-dataset" value="MATERIAL" v-model="importDataset">
materials
</radio-option>
<radio-option v-if="canModify('packaging')" name="import-dataset" value="PACKAGING" v-model="importDataset">
packaging
</radio-option> </radio-option>
<radio-option name="import-dataset" value="MATERIAL" v-model="importDataset">materials</radio-option>
<radio-option name="import-dataset" value="PACKAGING" v-model="importDataset">packaging</radio-option>
</div> </div>
<div class="bulk-operation-caption">file</div> <div class="bulk-operation-caption">file</div>
@ -79,8 +93,11 @@
<div class="bulk-operation-box-status-container"> <div class="bulk-operation-box-status-container">
<div class="bulk-operation-status"> <div class="bulk-operation-status">
<div class="bulk-operation-header">History</div> <div class="bulk-operation-header">History</div>
<div v-if="this.bulkOperationStore.getBulkOperations.length === 0" class="empty-container">No recent bulk operations</div> <div v-if="this.bulkOperationStore.getBulkOperations.length === 0" class="empty-container">No recent bulk
<bulk-operation v-else v-for="bulk in this.bulkOperationStore.getBulkOperations" :key="bulk.id" :operation="bulk" @download="fetchFile"></bulk-operation> operations
</div>
<bulk-operation v-else v-for="bulk in this.bulkOperationStore.getBulkOperations" :key="bulk.id"
:operation="bulk" @download="fetchFile"></bulk-operation>
</div> </div>
</div> </div>
@ -101,6 +118,7 @@ import {useValidityPeriodStore} from "@/store/validityPeriod.js";
import {useBulkOperationStore} from "@/store/bulkOperation.js"; import {useBulkOperationStore} from "@/store/bulkOperation.js";
import BulkOperation from "@/components/layout/bulkoperation/BulkOperation.vue"; import BulkOperation from "@/components/layout/bulkoperation/BulkOperation.vue";
import logger from "@/logger.js"; import logger from "@/logger.js";
import {useActiveUserStore} from "@/store/activeuser.js";
export default { export default {
name: "BulkOperations", name: "BulkOperations",
@ -125,13 +143,13 @@ export default {
}, },
watch: { watch: {
async isSelected(newVal) { async isSelected(newVal) {
if(newVal === true) if (newVal === true && this.activeUserStore.allowRates)
await this.validityPeriodStore.loadPeriods(); await this.validityPeriodStore.loadPeriods();
await this.bulkOperationStore.manageStatus(); await this.bulkOperationStore.manageStatus();
} }
}, },
computed: { computed: {
...mapStores(useValidityPeriodStore, useBulkOperationStore), ...mapStores(useValidityPeriodStore, useBulkOperationStore, useActiveUserStore),
showValidityPeriod() { showValidityPeriod() {
return this.exportType === "download" && (this.exportDataset === "COUNTRY_MATRIX" || this.exportDataset === "CONTAINER_RATE"); return this.exportType === "download" && (this.exportDataset === "COUNTRY_MATRIX" || this.exportDataset === "CONTAINER_RATE");
}, },
@ -167,8 +185,20 @@ export default {
} }
}, },
methods: { methods: {
canModify(role) {
switch (role) {
case "nodes":
return this.activeUserStore.isSuper;
case "rates":
return this.activeUserStore.isSuper || this.activeUserStore.isFreight;
case "materials":
return this.activeUserStore.isSuper || this.activeUserStore.isMaterial;
case "packaging":
return this.activeUserStore.isSuper || this.activeUserStore.isPackaging;
}
},
buildDate(date) { buildDate(date) {
if(date === null) return "not set"; if (date === null) return "not set";
return `${date[0]}-${date[1].toString().padStart(2, '0')}-${date[2].toString().padStart(2, '0')} ${date[3]?.toString().padStart(2, '0') ?? '00'}:${date[4]?.toString().padStart(2, '0') ?? '00'}:${date[5]?.toString().padStart(2, '0') ?? '00'}` return `${date[0]}-${date[1].toString().padStart(2, '0')}-${date[2].toString().padStart(2, '0')} ${date[3]?.toString().padStart(2, '0') ?? '00'}:${date[4]?.toString().padStart(2, '0') ?? '00'}:${date[5]?.toString().padStart(2, '0') ?? '00'}`
}, },
@ -195,7 +225,7 @@ export default {
this.fileBlob = await this.readFileAsBlob(file); this.fileBlob = await this.readFileAsBlob(file);
// File-Objekt mit dem Blob erstellen, das den originalen Namen und Typ behält // File-Objekt mit dem Blob erstellen, das den originalen Namen und Typ behält
this.selectedFile = new File([this.fileBlob], file.name, { type: file.type }); this.selectedFile = new File([this.fileBlob], file.name, {type: file.type});
this.selectedFileName = file.name; this.selectedFileName = file.name;
logger.info(`File loaded into memory: ${file.name} (${(file.size / 1024).toFixed(2)} KB)`); logger.info(`File loaded into memory: ${file.name} (${(file.size / 1024).toFixed(2)} KB)`);
@ -220,7 +250,7 @@ export default {
reader.onload = (e) => { reader.onload = (e) => {
// ArrayBuffer in Blob konvertieren // ArrayBuffer in Blob konvertieren
const blob = new Blob([e.target.result], { type: file.type }); const blob = new Blob([e.target.result], {type: file.type});
resolve(blob); resolve(blob);
}; };

View file

@ -77,19 +77,22 @@ export default {
tabsConfig() { tabsConfig() {
const tabs = []; const tabs = [];
if(this.activeUserStore.isSuper) { if (this.activeUserStore.isSuper) {
tabs.push(this.propertiesTab); tabs.push(this.propertiesTab);
tabs.push(this.systemLogTab); tabs.push(this.systemLogTab);
} }
if(this.activeUserStore.isService) { if (this.activeUserStore.isService) {
tabs.push(this.appsTab); tabs.push(this.appsTab);
} }
if (this.activeUserStore.isSuper || this.activeUserStore.isMaterial) {
tabs.push(this.materialsTab); tabs.push(this.materialsTab);
}
tabs.push(this.nodesTab); tabs.push(this.nodesTab);
if(this.activeUserStore.allowRates) if (this.activeUserStore.allowRates)
tabs.push(this.ratesTab); tabs.push(this.ratesTab);
tabs.push(this.bulkOperationsTab); tabs.push(this.bulkOperationsTab);

View file

@ -18,12 +18,12 @@ export const useActiveUserStore = defineStore('activeUser', {
allowConfiguration(state) { allowConfiguration(state) {
if (state.user === null) if (state.user === null)
return false; return false;
return state.user.groups?.includes("super") || state.user.groups?.includes("freight") || state.user.groups?.includes("packaging") || state.user.groups?.includes("service"); return state.user.groups?.includes("super") || state.user.groups?.includes("freight") || state.user.groups?.includes("packaging") || state.user.groups?.includes("service") || state.user.groups?.includes("material");
}, },
allowReporting(state) { allowReporting(state) {
if (state.user === null) if (state.user === null)
return false; return false;
return state.user.groups?.includes("super") || state.user.groups?.includes("freight") || state.user.groups?.includes("packaging") || state.user.groups?.includes("basic") || state.user.groups?.includes("calculation"); return state.user.groups?.includes("super") || state.user.groups?.includes("freight") || state.user.groups?.includes("packaging") || state.user.groups?.includes("material") || state.user.groups?.includes("basic") || state.user.groups?.includes("calculation");
}, },
isSuper(state) { isSuper(state) {
if (state.user === null) if (state.user === null)
@ -45,6 +45,11 @@ export const useActiveUserStore = defineStore('activeUser', {
return false; return false;
return state.user.groups?.includes("freight"); return state.user.groups?.includes("freight");
}, },
isMaterial(state) {
if (state.user === null)
return false;
return state.user.groups?.includes("material");
},
allowRates(state) { allowRates(state) {
if (state.user === null) if (state.user === null)
return false; return false;

View file

@ -2,6 +2,7 @@ import {defineStore} from 'pinia'
import {config} from '@/config' import {config} from '@/config'
import {useErrorStore} from "@/store/error.js"; import {useErrorStore} from "@/store/error.js";
import performRequest from "@/backend.js"; import performRequest from "@/backend.js";
import logger from "@/logger.js";
export const useMaterialStore = defineStore('material', { export const useMaterialStore = defineStore('material', {
state() { state() {

View file

@ -14,63 +14,63 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.Arrays; import java.util.Arrays;
@Configuration //@Configuration
@EnableWebMvc //@EnableWebMvc
@Order(Ordered.HIGHEST_PRECEDENCE) //@Order(Ordered.HIGHEST_PRECEDENCE)
public class CorsConfig implements WebMvcConfigurer { public class CorsConfig implements WebMvcConfigurer {
private final Environment environment; // private final Environment environment;
//
@Value("${lcc.allowed_cors}") // @Value("${lcc.allowed_cors}")
private String allowedCors; // private String allowedCors;
//
public CorsConfig(Environment environment) { // public CorsConfig(Environment environment) {
this.environment = environment; // this.environment = environment;
} // }
//
@Override // @Override
public void addCorsMappings(@NotNull CorsRegistry registry) { // public void addCorsMappings(@NotNull CorsRegistry registry) {
String[] activeProfiles = environment.getActiveProfiles(); // String[] activeProfiles = environment.getActiveProfiles();
//
System.out.println("Active profiles: " + Arrays.toString(activeProfiles)); // System.out.println("Active profiles: " + Arrays.toString(activeProfiles));
System.out.println("Allowed CORS: " + allowedCors); // System.out.println("Allowed CORS: " + allowedCors);
//
if (Arrays.asList(activeProfiles).contains("dev")) { // if (Arrays.asList(activeProfiles).contains("dev")) {
//
System.out.println("Applying DEV CORS configuration"); // System.out.println("Applying DEV CORS configuration");
//
// Development CORS configuration // // Development CORS configuration
registry.addMapping("/api/**") // registry.addMapping("/api/**")
.allowedOriginPatterns("http://localhost:*") // .allowedOriginPatterns("http://localhost:*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*") // .allowedHeaders("*")
.exposedHeaders("X-Total-Count", "X-Page-Count", "X-Current-Page") // .exposedHeaders("X-Total-Count", "X-Page-Count", "X-Current-Page")
.allowCredentials(true); // .allowCredentials(true);
//
// OAuth endpoints // // OAuth endpoints
registry.addMapping("/oauth/**") // registry.addMapping("/oauth/**")
.allowedOriginPatterns("http://localhost:*") // .allowedOriginPatterns("http://localhost:*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*") // .allowedHeaders("*")
.allowCredentials(true); // .allowCredentials(true);
} else { // } else {
//
System.out.println("Applying PROD CORS configuration"); // System.out.println("Applying PROD CORS configuration");
//
// Production CORS configuration // // Production CORS configuration
registry.addMapping("/api/**") // registry.addMapping("/api/**")
.allowedOrigins(allowedCors) // .allowedOrigins(allowedCors)
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*") // .allowedHeaders("*")
.exposedHeaders("X-Total-Count", "X-Page-Count", "X-Current-Page") // .exposedHeaders("X-Total-Count", "X-Page-Count", "X-Current-Page")
.allowCredentials(true); // .allowCredentials(true);
//
// OAuth endpoints // // OAuth endpoints
registry.addMapping("/oauth/**") // registry.addMapping("/oauth/**")
.allowedOriginPatterns("*") // .allowedOriginPatterns("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*") // .allowedHeaders("*")
.allowCredentials(true); // .allowCredentials(true);
} // }
} // }
} }

View file

@ -104,10 +104,13 @@ public class SecurityConfig {
CorsConfiguration configuration = new CorsConfiguration(); CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOriginPatterns(List.of("http://localhost:*")); configuration.setAllowedOriginPatterns(List.of("http://localhost:*"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS")); configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(Arrays.asList("*")); configuration.setAllowedHeaders(List.of("*"));
configuration.setAllowCredentials(true); configuration.setAllowCredentials(true);
configuration.setMaxAge(3600L); configuration.setMaxAge(3600L);
configuration.setExposedHeaders(Arrays.asList("X-Total-Count", "X-Page-Count", "X-Current-Page"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration); source.registerCorsConfiguration("/**", configuration);
return source; return source;
@ -131,6 +134,9 @@ public class SecurityConfig {
configuration.setAllowCredentials(true); configuration.setAllowCredentials(true);
configuration.setMaxAge(3600L); configuration.setMaxAge(3600L);
configuration.setExposedHeaders(Arrays.asList("X-Total-Count", "X-Page-Count", "X-Current-Page"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration); source.registerCorsConfiguration("/**", configuration);
return source; return source;

View file

@ -37,20 +37,20 @@ public class BulkOperationController {
} }
@GetMapping({"/status/", "/status"}) @GetMapping({"/status/", "/status"})
@PreAuthorize("hasAnyRole('SUPER', 'FREIGHT', 'PACKAGING')") @PreAuthorize("hasAnyRole('SUPER', 'FREIGHT', 'PACKAGING', 'MATERIAL')")
public ResponseEntity<List<BulkOperationDTO>> getBulkStatus() { public ResponseEntity<List<BulkOperationDTO>> getBulkStatus() {
return ResponseEntity.ok(bulkOperationService.getStatus()); return ResponseEntity.ok(bulkOperationService.getStatus());
} }
@PostMapping({"/upload/{type}", "/upload/{type}/"}) @PostMapping({"/upload/{type}", "/upload/{type}/"})
@PreAuthorize("hasAnyRole('SUPER', 'FREIGHT', 'PACKAGING')") @PreAuthorize("hasAnyRole('SUPER', 'FREIGHT', 'PACKAGING', 'MATERIAL')")
public ResponseEntity<Void> uploadFile(@PathVariable BulkFileType type, @BodyParam("file") MultipartFile file) { public ResponseEntity<Void> uploadFile(@PathVariable BulkFileType type, @BodyParam("file") MultipartFile file) {
bulkOperationService.processFileImport(type, file); bulkOperationService.processFileImport(type, file);
return ResponseEntity.ok().build(); return ResponseEntity.ok().build();
} }
@GetMapping({"/templates/{type}", "/templates/{type}/"}) @GetMapping({"/templates/{type}", "/templates/{type}/"})
@PreAuthorize("hasAnyRole('SUPER', 'FREIGHT', 'PACKAGING')") @PreAuthorize("hasAnyRole('SUPER', 'FREIGHT', 'PACKAGING', 'MATERIAL')")
public ResponseEntity<InputStreamResource> generateTemplate(@PathVariable BulkFileType type) { public ResponseEntity<InputStreamResource> generateTemplate(@PathVariable BulkFileType type) {
HttpHeaders headers = new HttpHeaders(); HttpHeaders headers = new HttpHeaders();
headers.add("Content-Disposition", "attachment; filename=lcc_template_" + type.name().toLowerCase() + ".xlsx"); headers.add("Content-Disposition", "attachment; filename=lcc_template_" + type.name().toLowerCase() + ".xlsx");
@ -64,7 +64,7 @@ public class BulkOperationController {
@GetMapping({"/download/{type}", "/download/{type}/"}) @GetMapping({"/download/{type}", "/download/{type}/"})
@PreAuthorize("hasAnyRole('SUPER', 'FREIGHT', 'PACKAGING')") @PreAuthorize("hasAnyRole('SUPER', 'FREIGHT', 'PACKAGING', 'MATERIAL')")
public ResponseEntity<Void> scheduleDownload(@PathVariable BulkFileType type) { public ResponseEntity<Void> scheduleDownload(@PathVariable BulkFileType type) {
bulkOperationService.processFileExport(type); bulkOperationService.processFileExport(type);
return ResponseEntity.ok().build(); return ResponseEntity.ok().build();
@ -72,7 +72,7 @@ public class BulkOperationController {
@GetMapping({"/download/{type}/{validity_period_id}", "/download/{type}/{validity_period_id}/"}) @GetMapping({"/download/{type}/{validity_period_id}", "/download/{type}/{validity_period_id}/"})
@PreAuthorize("hasAnyRole('SUPER', 'FREIGHT', 'PACKAGING')") @PreAuthorize("hasAnyRole('SUPER', 'FREIGHT', 'PACKAGING', 'MATERIAL')")
public ResponseEntity<Void> scheduleDownload(@PathVariable BulkFileType type, @PathVariable("validity_period_id") Integer validityPeriodId) { public ResponseEntity<Void> scheduleDownload(@PathVariable BulkFileType type, @PathVariable("validity_period_id") Integer validityPeriodId) {
bulkOperationService.processFileExport(type, validityPeriodId); bulkOperationService.processFileExport(type, validityPeriodId);
return ResponseEntity.ok().build(); return ResponseEntity.ok().build();
@ -80,7 +80,7 @@ public class BulkOperationController {
} }
@GetMapping({"/file/{processId}", "/file/{processId}/"}) @GetMapping({"/file/{processId}", "/file/{processId}/"})
@PreAuthorize("hasAnyRole('SUPER', 'FREIGHT', 'PACKAGING')") @PreAuthorize("hasAnyRole('SUPER', 'FREIGHT', 'PACKAGING', 'MATERIAL')")
public ResponseEntity<InputStreamResource> download(@PathVariable("processId") Integer id) { public ResponseEntity<InputStreamResource> download(@PathVariable("processId") Integer id) {
var op = bulkOperationService.getBulkOperation(id); var op = bulkOperationService.getBulkOperation(id);

View file

@ -37,7 +37,7 @@ public class MaterialController {
* X-Total-Count (total elements), X-Page-Count (total pages), and X-Current-Page (current page). * X-Total-Count (total elements), X-Page-Count (total pages), and X-Current-Page (current page).
*/ */
@GetMapping("/") @GetMapping("/")
@PreAuthorize("hasAnyRole('SUPER', 'CALCULATION', 'BASIC', 'FREIGHT', 'PACKAGING')") @PreAuthorize("hasAnyRole('SUPER', 'CALCULATION', 'BASIC', 'FREIGHT', 'PACKAGING', 'MATERIAL')")
public ResponseEntity<List<MaterialDTO>> listMaterials( public ResponseEntity<List<MaterialDTO>> listMaterials(
@RequestParam(defaultValue = "true") String excludeDeprecated, @RequestParam(defaultValue = "true") String excludeDeprecated,
@RequestParam(defaultValue = "20") @Min(1) int limit, @RequestParam(defaultValue = "20") @Min(1) int limit,
@ -61,7 +61,7 @@ public class MaterialController {
* @throws RuntimeException if the material with the given ID is not found. * @throws RuntimeException if the material with the given ID is not found.
*/ */
@GetMapping("/{id}") @GetMapping("/{id}")
@PreAuthorize("hasAnyRole('SUPER', 'CALCULATION', 'BASIC', 'FREIGHT', 'PACKAGING')") @PreAuthorize("hasAnyRole('SUPER', 'CALCULATION', 'BASIC', 'FREIGHT', 'PACKAGING', 'MATERIAL')")
public ResponseEntity<MaterialDetailDTO> getMaterialDetails(@PathVariable Integer id) { public ResponseEntity<MaterialDetailDTO> getMaterialDetails(@PathVariable Integer id) {
return ResponseEntity.ok(materialService.getMaterial(id)); return ResponseEntity.ok(materialService.getMaterial(id));
} }

View file

@ -1,26 +1,15 @@
package de.avatic.lcc.dto.generic; package de.avatic.lcc.dto.generic;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.util.Objects;
/** /**
* Represents a geographical location with latitude and longitude. * Represents a geographical location with latitude and longitude.
* This immutable DTO (Data Transfer Object) is used to transfer location data across system layers. * This immutable DTO (Data Transfer Object) is used to transfer location data across system layers.
*/ *
public class LocationDTO { * @param latitude The latitude of the location.
/**
* The latitude of the location.
* Positive values indicate north and negative values indicate south. * Positive values indicate north and negative values indicate south.
*/ * @param longitude The longitude of the location.
private final double latitude;
/**
* The longitude of the location.
* Positive values indicate east and negative values indicate west. * Positive values indicate east and negative values indicate west.
*/ */
private final double longitude; public record LocationDTO(double latitude, double longitude) {
/** /**
* Constructs a new {@code LocationDTO} with the specified latitude and longitude. * Constructs a new {@code LocationDTO} with the specified latitude and longitude.
@ -28,9 +17,7 @@ public class LocationDTO {
* @param latitude the latitude of the location, where north is positive and south is negative * @param latitude the latitude of the location, where north is positive and south is negative
* @param longitude the longitude of the location, where east is positive and west is negative * @param longitude the longitude of the location, where east is positive and west is negative
*/ */
public LocationDTO(double latitude, double longitude) { public LocationDTO {
this.latitude = latitude;
this.longitude = longitude;
} }
/** /**
@ -46,7 +33,8 @@ public class LocationDTO {
* *
* @return the latitude, where positive values indicate north and negative values indicate south * @return the latitude, where positive values indicate north and negative values indicate south
*/ */
public double getLatitude() { @Override
public double latitude() {
return latitude; return latitude;
} }
@ -55,7 +43,8 @@ public class LocationDTO {
* *
* @return the longitude, where positive values indicate east and negative values indicate west * @return the longitude, where positive values indicate east and negative values indicate west
*/ */
public double getLongitude() { @Override
public double longitude() {
return longitude; return longitude;
} }
@ -68,11 +57,6 @@ public class LocationDTO {
Double.compare(that.longitude, longitude) == 0; Double.compare(that.longitude, longitude) == 0;
} }
@Override
public int hashCode() {
return Objects.hash(latitude, longitude);
}
@Override @Override
public String toString() { public String toString() {
return "LocationDTO{" + return "LocationDTO{" +

View file

@ -160,18 +160,12 @@ public class BulkOperationRepository {
); );
} }
private static class BulkOperationRowMapper implements RowMapper<BulkOperation> { private record BulkOperationRowMapper(boolean skipFile) implements RowMapper<BulkOperation> {
private final boolean skipFile;
BulkOperationRowMapper() { BulkOperationRowMapper() {
this(false); this(false);
} }
BulkOperationRowMapper(boolean skipFile) {
this.skipFile = skipFile;
}
@Override @Override
public BulkOperation mapRow(ResultSet rs, int rowNum) throws SQLException { public BulkOperation mapRow(ResultSet rs, int rowNum) throws SQLException {
BulkOperation operation = new BulkOperation(); BulkOperation operation = new BulkOperation();

View file

@ -30,15 +30,18 @@ import java.util.Map;
@Repository @Repository
public class DumpRepository { public class DumpRepository {
@Autowired private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
@Autowired private final JdbcTemplate jdbcTemplate;
private JdbcTemplate jdbcTemplate; private final PremiseRepository premiseRepository;
@Autowired private final PremiseTransformer premiseTransformer;
private PremiseRepository premiseRepository;
@Autowired public DumpRepository(NamedParameterJdbcTemplate namedParameterJdbcTemplate, JdbcTemplate jdbcTemplate, PremiseRepository premiseRepository, PremiseTransformer premiseTransformer) {
private PremiseTransformer premiseTransformer; this.namedParameterJdbcTemplate = namedParameterJdbcTemplate;
this.jdbcTemplate = jdbcTemplate;
this.premiseRepository = premiseRepository;
this.premiseTransformer = premiseTransformer;
}
@Transactional(readOnly = true) @Transactional(readOnly = true)
public CalculationJobDumpDTO getDump(Integer id) { public CalculationJobDumpDTO getDump(Integer id) {

View file

@ -122,7 +122,7 @@ public class SysErrorRepository {
} }
// Count total elements // Count total elements
String countSql = "SELECT COUNT(*) FROM sys_error e" + whereClause.toString(); String countSql = "SELECT COUNT(*) FROM sys_error e" + whereClause;
Integer totalElements = namedParameterJdbcTemplate.queryForObject(countSql, parameters, Integer.class); Integer totalElements = namedParameterJdbcTemplate.queryForObject(countSql, parameters, Integer.class);
// Build main query with pagination // Build main query with pagination
@ -130,7 +130,7 @@ public class SysErrorRepository {
SELECT e.id, e.user_id, e.title, e.code, e.message, e.pinia, SELECT e.id, e.user_id, e.title, e.code, e.message, e.pinia,
e.calculation_job_id, e.bulk_operation_id, e.type, e.created_at e.calculation_job_id, e.bulk_operation_id, e.type, e.created_at
FROM sys_error e FROM sys_error e
""" + whereClause.toString() + """ """ + whereClause + """
ORDER BY e.created_at DESC ORDER BY e.created_at DESC
LIMIT :limit OFFSET :offset LIMIT :limit OFFSET :offset
"""; """;

View file

@ -14,7 +14,7 @@ import java.util.function.Function;
*/ */
public class SearchQueryResult<T> { public class SearchQueryResult<T> {
private List<T> result; private final List<T> result;
private final Integer page, totalPages, totalElements, elementsPerPage; private final Integer page, totalPages, totalElements, elementsPerPage;

View file

@ -135,6 +135,28 @@ public class DestinationRepository {
return Optional.of(userId.getFirst()); return Optional.of(userId.getFirst());
} }
@Transactional
public Map<Integer, Integer> getOwnerIdsByIds(List<Integer> ids) {
if (ids == null || ids.isEmpty()) {
return Collections.emptyMap();
}
String placeholders = String.join(",", Collections.nCopies(ids.size(), "?"));
String query = String.format("""
SELECT pd.id AS pd_id, p.user_id AS user_id
FROM premise_destination pd
JOIN premise p ON pd.premise_id = p.id
WHERE pd.id IN (%s)""", placeholders);
return jdbcTemplate.query(query, rs -> {
Map<Integer, Integer> result = new HashMap<>();
while (rs.next()) {
result.put(rs.getInt("pd_id"), rs.getInt("user_id"));
}
return result;
}, ids.toArray());
}
@Transactional @Transactional
public List<Destination> getByPremiseIdsAndNodeId(List<Integer> premiseId, Integer nodeId, Integer userId) { public List<Destination> getByPremiseIdsAndNodeId(List<Integer> premiseId, Integer nodeId, Integer userId) {
String placeholder = String.join(",", Collections.nCopies(premiseId.size(), "?")); String placeholder = String.join(",", Collections.nCopies(premiseId.size(), "?"));
@ -226,6 +248,29 @@ public class DestinationRepository {
} }
} }
@Transactional
public void checkOwner(List<Integer> ids, Integer userId) {
if (ids == null || ids.isEmpty()) {
return;
}
Map<Integer, Integer> ownerMap = getOwnerIdsByIds(ids);
List<String> violations = new ArrayList<>();
for (Integer id : ids) {
Integer ownerId = ownerMap.get(id);
if (ownerId == null || !ownerId.equals(userId)) {
violations.add("id " + id + " (owner: " + ownerId + " user: " + userId + ")");
}
}
if (!violations.isEmpty()) {
throw new ForbiddenException("Access violation. Accessing destinations: " +
String.join(", ", violations));
}
}
private static class DestinationMapper implements RowMapper<Destination> { private static class DestinationMapper implements RowMapper<Destination> {
@Override @Override

View file

@ -288,13 +288,7 @@ public class ContainerRateRepository {
} }
private static class ContainerRateMapper implements RowMapper<ContainerRate> { private record ContainerRateMapper(boolean fetchCountryIds) implements RowMapper<ContainerRate> {
private final boolean fetchCountryIds;
public ContainerRateMapper(boolean fetchCountryIds) {
this.fetchCountryIds = fetchCountryIds;
}
public ContainerRateMapper() { public ContainerRateMapper() {
this(false); this(false);

View file

@ -15,7 +15,6 @@ import de.avatic.lcc.repositories.users.UserNodeRepository;
import de.avatic.lcc.service.calculation.RoutingService; import de.avatic.lcc.service.calculation.RoutingService;
import de.avatic.lcc.service.transformer.premise.DestinationTransformer; import de.avatic.lcc.service.transformer.premise.DestinationTransformer;
import de.avatic.lcc.service.users.AuthorizationService; import de.avatic.lcc.service.users.AuthorizationService;
import de.avatic.lcc.util.exception.base.ForbiddenException;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -34,15 +33,12 @@ public class DestinationService {
private final RouteSectionRepository routeSectionRepository; private final RouteSectionRepository routeSectionRepository;
private final RouteNodeRepository routeNodeRepository; private final RouteNodeRepository routeNodeRepository;
private final RoutingService routingService; private final RoutingService routingService;
;
private final NodeRepository nodeRepository; private final NodeRepository nodeRepository;
private final PremiseRepository premiseRepository; private final PremiseRepository premiseRepository;
private final UserNodeRepository userNodeRepository; private final UserNodeRepository userNodeRepository;
private final PropertyRepository propertyRepository;
private final PropertyService propertyService;
private final AuthorizationService authorizationService; private final AuthorizationService authorizationService;
public DestinationService(DestinationRepository destinationRepository, DestinationTransformer destinationTransformer, RouteRepository routeRepository, RouteSectionRepository routeSectionRepository, RouteNodeRepository routeNodeRepository, RoutingService routingService, NodeRepository nodeRepository, PremiseRepository premiseRepository, UserNodeRepository userNodeRepository, PropertyRepository propertyRepository, PropertyService propertyService, AuthorizationService authorizationService) { public DestinationService(DestinationRepository destinationRepository, DestinationTransformer destinationTransformer, RouteRepository routeRepository, RouteSectionRepository routeSectionRepository, RouteNodeRepository routeNodeRepository, RoutingService routingService, NodeRepository nodeRepository, PremiseRepository premiseRepository, UserNodeRepository userNodeRepository, AuthorizationService authorizationService) {
this.destinationRepository = destinationRepository; this.destinationRepository = destinationRepository;
this.destinationTransformer = destinationTransformer; this.destinationTransformer = destinationTransformer;
this.routeRepository = routeRepository; this.routeRepository = routeRepository;
@ -52,16 +48,16 @@ public class DestinationService {
this.nodeRepository = nodeRepository; this.nodeRepository = nodeRepository;
this.premiseRepository = premiseRepository; this.premiseRepository = premiseRepository;
this.userNodeRepository = userNodeRepository; this.userNodeRepository = userNodeRepository;
this.propertyRepository = propertyRepository;
this.propertyService = propertyService;
this.authorizationService = authorizationService; this.authorizationService = authorizationService;
} }
@Transactional @Transactional
public Map<Integer, List<DestinationDTO>> setDestination(DestinationSetDTO dto) { public Map<Integer, List<DestinationDTO>> setDestination(DestinationSetDTO dto) {
//TODO fix user authorization var admin = authorizationService.isSuper();
var userId = 1; Integer userId = authorizationService.getUserId();
//dto.getPremiseId().forEach(id -> destinationRepository.checkOwner(id, userId));
if (!admin)
destinationRepository.checkOwner(dto.getPremiseId(), userId);
deleteAllDestinationsByPremiseId(dto.getPremiseId(), false); deleteAllDestinationsByPremiseId(dto.getPremiseId(), false);
@ -80,7 +76,6 @@ public class DestinationService {
} }
private List<Destination> createDestination(List<Premise> premisesToProcess, Integer destinationNodeId, Integer annualAmount, Number repackingCost, Number disposalCost, Number handlingCost, Map<RouteIds, List<RouteInformation>> routes) { private List<Destination> createDestination(List<Premise> premisesToProcess, Integer destinationNodeId, Integer annualAmount, Number repackingCost, Number disposalCost, Number handlingCost, Map<RouteIds, List<RouteInformation>> routes) {
Node destinationNode = nodeRepository.getById(destinationNodeId).orElseThrow(); Node destinationNode = nodeRepository.getById(destinationNodeId).orElseThrow();
@ -141,17 +136,22 @@ public class DestinationService {
} }
public DestinationDTO getDestination(Integer id) { public DestinationDTO getDestination(Integer id) {
//todo check authorization var admin = authorizationService.isSuper();
var userId = authorizationService.getUserId();
if (!admin)
destinationRepository.checkOwner(id, userId);
return destinationTransformer.toDestinationDTO(destinationRepository.getById(id).orElseThrow()); return destinationTransformer.toDestinationDTO(destinationRepository.getById(id).orElseThrow());
} }
@Transactional @Transactional
public void updateDestination(Integer id, DestinationUpdateDTO destinationUpdateDTO) { public void updateDestination(Integer id, DestinationUpdateDTO destinationUpdateDTO) {
//todo check authorization var admin = authorizationService.isSuper();
Integer userId = authorizationService.getUserId(); Integer userId = authorizationService.getUserId();
destinationRepository.checkOwner(id, userId);
if (!admin)
destinationRepository.checkOwner(id, userId);
var selectedRouteId = destinationUpdateDTO.getRouteSelectedId(); var selectedRouteId = destinationUpdateDTO.getRouteSelectedId();
@ -159,7 +159,6 @@ public class DestinationService {
routeRepository.updateSelectedByDestinationId(id, selectedRouteId); routeRepository.updateSelectedByDestinationId(id, selectedRouteId);
} }
destinationRepository.update(id, destinationRepository.update(id,
destinationUpdateDTO.getAnnualAmount(), destinationUpdateDTO.getAnnualAmount(),
destinationUpdateDTO.getRepackingCost() == null ? null : BigDecimal.valueOf(destinationUpdateDTO.getRepackingCost().doubleValue()), destinationUpdateDTO.getRepackingCost() == null ? null : BigDecimal.valueOf(destinationUpdateDTO.getRepackingCost().doubleValue()),
@ -177,21 +176,21 @@ public class DestinationService {
Map<Integer, Node> nodes = new HashMap<>(); Map<Integer, Node> nodes = new HashMap<>();
Map<Integer, Node> userNodes = new HashMap<>(); Map<Integer, Node> userNodes = new HashMap<>();
for(var premise : premisses) { for (var premise : premisses) {
for(var destinationId : destinationIds) { for (var destinationId : destinationIds) {
boolean isUserSupplierNode = (premise.getSupplierNodeId() == null); boolean isUserSupplierNode = (premise.getSupplierNodeId() == null);
var ids = new RouteIds(isUserSupplierNode ? premise.getUserSupplierNodeId() : premise.getSupplierNodeId(), destinationId, isUserSupplierNode); var ids = new RouteIds(isUserSupplierNode ? premise.getUserSupplierNodeId() : premise.getSupplierNodeId(), destinationId, isUserSupplierNode);
if(routes.containsKey(ids)) continue; if (routes.containsKey(ids)) continue;
if(!nodes.containsKey(destinationId)) { if (!nodes.containsKey(destinationId)) {
nodes.put(destinationId, nodeRepository.getById(destinationId).orElseThrow()); nodes.put(destinationId, nodeRepository.getById(destinationId).orElseThrow());
} }
if(!isUserSupplierNode && !nodes.containsKey(premise.getSupplierNodeId())) { if (!isUserSupplierNode && !nodes.containsKey(premise.getSupplierNodeId())) {
nodes.put(premise.getSupplierNodeId(), nodeRepository.getById(premise.getSupplierNodeId()).orElseThrow()); nodes.put(premise.getSupplierNodeId(), nodeRepository.getById(premise.getSupplierNodeId()).orElseThrow());
} }
if(isUserSupplierNode && !userNodes.containsKey(premise.getUserSupplierNodeId())) { if (isUserSupplierNode && !userNodes.containsKey(premise.getUserSupplierNodeId())) {
userNodes.put(premise.getUserSupplierNodeId(), userNodeRepository.getById(premise.getUserSupplierNodeId()).orElseThrow()); userNodes.put(premise.getUserSupplierNodeId(), userNodeRepository.getById(premise.getUserSupplierNodeId()).orElseThrow());
} }
@ -205,7 +204,6 @@ public class DestinationService {
@Transactional @Transactional
public void saveRoute(List<RouteInformation> routeObjs, Integer destinationId) { public void saveRoute(List<RouteInformation> routeObjs, Integer destinationId) {
for (var routeObj : routeObjs) { for (var routeObj : routeObjs) {
boolean first = true; boolean first = true;
Integer fromNodeId = null; Integer fromNodeId = null;
@ -238,44 +236,12 @@ public class DestinationService {
} }
} }
private record RouteIds(Integer fromNodeId, Integer toNodeId, boolean isUserSupplierNode) {}
@Transactional @Transactional
public void findRouteAndSave(Integer destinationId, Node destination, Node supplier, boolean isUserSupplierNode) { public void findRouteAndSave(Integer destinationId, Node destination, Node supplier, boolean isUserSupplierNode) {
var routeObjs = routingService.findRoutes(destination, supplier, isUserSupplierNode); var routeObjs = routingService.findRoutes(destination, supplier, isUserSupplierNode);
for (var routeObj : routeObjs) { saveRoute(routeObjs, destinationId);
boolean first = true;
Integer fromNodeId = null;
var premiseRoute = routeObj.getRoute();
premiseRoute.setDestinationId(destinationId);
int routeId = routeRepository.insert(premiseRoute);
for (RouteSectionInformation section : routeObj.getSections()) {
if (first) {
fromNodeId = routeNodeRepository.insert(section.getFromNode());
first = false;
}
var toNode = section.getToNode();
Integer toNodeId = routeNodeRepository.insert(toNode);
var premiseRouteSection = section.getSection();
premiseRouteSection.setRouteId(routeId);
premiseRouteSection.setFromRouteNodeId(fromNodeId);
premiseRouteSection.setToRouteNodeId(toNodeId);
routeSectionRepository.insert(premiseRouteSection);
fromNodeId = toNodeId;
}
}
} }
@Transactional @Transactional
@ -289,14 +255,14 @@ public class DestinationService {
destinations.forEach(destination -> deleteDestinationById(destination.getId(), deleteRoutesOnly)); destinations.forEach(destination -> deleteDestinationById(destination.getId(), deleteRoutesOnly));
} }
@Transactional @Transactional
public void deleteDestinationById(Integer id, boolean deleteRoutesOnly) { public void deleteDestinationById(Integer id, boolean deleteRoutesOnly) {
//todo check authorization var admin = authorizationService.isSuper();
Integer userId = authorizationService.getUserId(); Integer userId = authorizationService.getUserId();
Optional<Integer> ownerId = destinationRepository.getOwnerIdById(id);
if (ownerId.isPresent() && ownerId.get().equals(userId)) { if (!admin)
destinationRepository.checkOwner(id, userId);
List<Route> routes = routeRepository.getByDestinationId(id); List<Route> routes = routeRepository.getByDestinationId(id);
for (var route : routes) { for (var route : routes) {
@ -310,11 +276,6 @@ public class DestinationService {
if (!deleteRoutesOnly) if (!deleteRoutesOnly)
destinationRepository.deleteById(id); destinationRepository.deleteById(id);
return;
}
throw new ForbiddenException("Not authorized to delete destination with id " + id);
} }
@Transactional @Transactional
@ -349,5 +310,8 @@ public class DestinationService {
} }
} }
private record RouteIds(Integer fromNodeId, Integer toNodeId, boolean isUserSupplierNode) {
}
} }

View file

@ -90,7 +90,10 @@ public class PremisesService {
@Transactional(readOnly = true) @Transactional(readOnly = true)
public List<PremiseDetailDTO> getPremises(List<Integer> premiseIds) { public List<PremiseDetailDTO> getPremises(List<Integer> premiseIds) {
var admin = authorizationService.isSuper();
var userId = authorizationService.getUserId(); var userId = authorizationService.getUserId();
if (!admin)
premiseRepository.checkOwner(premiseIds, userId); premiseRepository.checkOwner(premiseIds, userId);
return premiseRepository.getPremisesById(premiseIds).stream().filter(p -> p.getState().equals(PremiseState.DRAFT)).map(premiseTransformer::toPremiseDetailDTO).toList(); return premiseRepository.getPremisesById(premiseIds).stream().filter(p -> p.getState().equals(PremiseState.DRAFT)).map(premiseTransformer::toPremiseDetailDTO).toList();
@ -98,12 +101,14 @@ public class PremisesService {
public void startCalculation(List<Integer> premises) { public void startCalculation(List<Integer> premises) {
var admin = authorizationService.isSuper();
var userId = authorizationService.getUserId(); var userId = authorizationService.getUserId();
if (!admin)
premiseRepository.checkOwner(premises, userId); premiseRepository.checkOwner(premises, userId);
var validSetId = propertySetRepository.getValidSetId(); var validSetId = propertySetRepository.getValidSetId();
var validPeriodId = validityPeriodRepository.getValidPeriodId().orElseThrow(() -> new InternalErrorException("no valid period found that is VALID")); var validPeriodId = validityPeriodRepository.getValidPeriodId().orElseThrow(() -> new InternalErrorException("no set of transport rates found that is VALID"));
premises.forEach(premiseId -> preCalculationCheckService.doPrecheck(premiseId, validSetId, validPeriodId)); premises.forEach(premiseId -> preCalculationCheckService.doPrecheck(premiseId, validSetId, validPeriodId));
@ -183,11 +188,12 @@ public class PremisesService {
return calculationStatusService.getCalculationStatus(processId); return calculationStatusService.getCalculationStatus(processId);
} }
@Transactional
public void updatePackaging(PackagingUpdateDTO packagingDTO) {
//TODO check values. and return errors if needed public void updatePackaging(PackagingUpdateDTO packagingDTO) {
var admin = authorizationService.isSuper();
var userId = authorizationService.getUserId(); var userId = authorizationService.getUserId();
if (!admin)
premiseRepository.checkOwner(packagingDTO.getPremiseIds(), userId); premiseRepository.checkOwner(packagingDTO.getPremiseIds(), userId);
var dimensions = packagingDTO.getDimensions() == null ? null : dimensionTransformer.toDimensionEntity(packagingDTO.getDimensions()); var dimensions = packagingDTO.getDimensions() == null ? null : dimensionTransformer.toDimensionEntity(packagingDTO.getDimensions());
@ -197,8 +203,10 @@ public class PremisesService {
} }
public void updateMaterial(MaterialUpdateDTO materialUpdateDTO) { public void updateMaterial(MaterialUpdateDTO materialUpdateDTO) {
//TODO check values. and return errors if needed var admin = authorizationService.isSuper();
var userId = authorizationService.getUserId(); var userId = authorizationService.getUserId();
if (!admin)
premiseRepository.checkOwner(materialUpdateDTO.getPremiseIds(), userId); premiseRepository.checkOwner(materialUpdateDTO.getPremiseIds(), userId);
var tariffRate = materialUpdateDTO.getTariffRate() == null ? null : BigDecimal.valueOf(materialUpdateDTO.getTariffRate().doubleValue()); var tariffRate = materialUpdateDTO.getTariffRate() == null ? null : BigDecimal.valueOf(materialUpdateDTO.getTariffRate().doubleValue());
@ -207,8 +215,10 @@ public class PremisesService {
} }
public void updatePrice(PriceUpdateDTO priceUpdateDTO) { public void updatePrice(PriceUpdateDTO priceUpdateDTO) {
//TODO check values. and return errors if needed var admin = authorizationService.isSuper();
var userId = authorizationService.getUserId(); var userId = authorizationService.getUserId();
if (!admin)
premiseRepository.checkOwner(priceUpdateDTO.getPremiseIds(), userId); premiseRepository.checkOwner(priceUpdateDTO.getPremiseIds(), userId);
var price = priceUpdateDTO.getPrice() == null ? null : BigDecimal.valueOf(priceUpdateDTO.getPrice().doubleValue()); var price = priceUpdateDTO.getPrice() == null ? null : BigDecimal.valueOf(priceUpdateDTO.getPrice().doubleValue());
@ -219,9 +229,13 @@ public class PremisesService {
@Transactional @Transactional
public void delete(List<Integer> premiseIds) { public void delete(List<Integer> premiseIds) {
var admin = authorizationService.isSuper();
var userId = authorizationService.getUserId(); var userId = authorizationService.getUserId();
if (!admin)
premiseRepository.checkOwner(premiseIds, userId); premiseRepository.checkOwner(premiseIds, userId);
// only delete drafts. // only delete drafts.
var toBeDeleted = premiseRepository.getPremisesById(premiseIds).stream().filter(p -> p.getState().equals(PremiseState.DRAFT)).map(Premise::getId).toList(); var toBeDeleted = premiseRepository.getPremisesById(premiseIds).stream().filter(p -> p.getState().equals(PremiseState.DRAFT)).map(Premise::getId).toList();
@ -232,10 +246,15 @@ public class PremisesService {
} }
@Transactional
public void archive(List<Integer> premiseIds) { public void archive(List<Integer> premiseIds) {
var admin = authorizationService.isSuper();
var userId = authorizationService.getUserId(); var userId = authorizationService.getUserId();
if (!admin)
premiseRepository.checkOwner(premiseIds, userId); premiseRepository.checkOwner(premiseIds, userId);
// only archive completed. // only archive completed.
var premisses = premiseRepository.getPremisesById(premiseIds); var premisses = premiseRepository.getPremisesById(premiseIds);
premiseRepository.setStatus(premisses.stream().filter(p -> p.getState().equals(PremiseState.COMPLETED)).map(Premise::getId).toList(), PremiseState.ARCHIVED); premiseRepository.setStatus(premisses.stream().filter(p -> p.getState().equals(PremiseState.COMPLETED)).map(Premise::getId).toList(), PremiseState.ARCHIVED);
@ -262,9 +281,14 @@ public class PremisesService {
return resolveDTO; return resolveDTO;
} }
@Transactional
public List<Integer> duplicate(List<Integer> premissIds) { public List<Integer> duplicate(List<Integer> premissIds) {
var admin = authorizationService.isSuper();
var userId = authorizationService.getUserId(); var userId = authorizationService.getUserId();
if (!admin)
premiseRepository.checkOwner(premissIds, userId); premiseRepository.checkOwner(premissIds, userId);
var newIds = new ArrayList<Integer>(); var newIds = new ArrayList<Integer>();
premissIds.forEach(id -> { premissIds.forEach(id -> {

View file

@ -172,7 +172,7 @@ public class PropertyValidationService {
public void evaluate(String value) { public void evaluate(String value) {
if (operator == CompareOperator.ENUM) { if (operator == CompareOperator.ENUM) {
if (!((List<String>) this.value).contains(value)) { if (!((List<String>) this.value).contains(value)) {
throw new PropertyValidationException(propertyId, operator.getIdentifier(), ((List<String>) this.value).toString(), value); throw new PropertyValidationException(propertyId, operator.getIdentifier(), this.value.toString(), value);
} }
} else { } else {
try { try {

View file

@ -53,8 +53,8 @@ public class UserNodeService {
node.setName(dto.getName()); node.setName(dto.getName());
node.setAddress(dto.getAddress()); node.setAddress(dto.getAddress());
node.setGeoLng(BigDecimal.valueOf(dto.getLocation().getLongitude())); node.setGeoLng(BigDecimal.valueOf(dto.getLocation().longitude()));
node.setGeoLat(BigDecimal.valueOf(dto.getLocation().getLatitude())); node.setGeoLat(BigDecimal.valueOf(dto.getLocation().latitude()));
node.setDestination(false); node.setDestination(false);
node.setSource(true); node.setSource(true);
node.setIntermediate(false); node.setIntermediate(false);

View file

@ -2,11 +2,9 @@ package de.avatic.lcc.service.bulk;
import de.avatic.lcc.model.bulk.BulkFileTypes; import de.avatic.lcc.model.bulk.BulkFileTypes;
import de.avatic.lcc.model.bulk.BulkOperation; import de.avatic.lcc.model.bulk.BulkOperation;
import de.avatic.lcc.repositories.NodeRepository;
import de.avatic.lcc.service.api.BatchGeoApiService; import de.avatic.lcc.service.api.BatchGeoApiService;
import de.avatic.lcc.service.bulk.bulkImport.*; import de.avatic.lcc.service.bulk.bulkImport.*;
import de.avatic.lcc.service.excelMapper.*; import de.avatic.lcc.service.excelMapper.*;
import de.avatic.lcc.service.transformer.generic.NodeTransformer;
import de.avatic.lcc.util.exception.internalerror.ExcelValidationError; import de.avatic.lcc.util.exception.internalerror.ExcelValidationError;
import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.usermodel.Workbook;
@ -26,8 +24,7 @@ public class BulkImportService {
private final MaterialExcelMapper materialExcelMapper; private final MaterialExcelMapper materialExcelMapper;
private final PackagingExcelMapper packagingExcelMapper; private final PackagingExcelMapper packagingExcelMapper;
private final NodeExcelMapper nodeExcelMapper; private final NodeExcelMapper nodeExcelMapper;
private final NodeRepository nodeRepository;
private final NodeTransformer nodeTransformer;
private final NodeBulkImportService nodeBulkImportService; private final NodeBulkImportService nodeBulkImportService;
private final PackagingBulkImportService packagingBulkImportService; private final PackagingBulkImportService packagingBulkImportService;
private final MaterialBulkImportService materialBulkImportService; private final MaterialBulkImportService materialBulkImportService;
@ -35,14 +32,12 @@ public class BulkImportService {
private final ContainerRateImportService containerRateImportService; private final ContainerRateImportService containerRateImportService;
private final BatchGeoApiService batchGeoApiService; private final BatchGeoApiService batchGeoApiService;
public BulkImportService(MatrixRateExcelMapper matrixRateExcelMapper, ContainerRateExcelMapper containerRateExcelMapper, MaterialExcelMapper materialExcelMapper, PackagingExcelMapper packagingExcelMapper, NodeExcelMapper nodeExcelMapper, NodeRepository nodeRepository, NodeTransformer nodeTransformer, NodeBulkImportService nodeBulkImportService, PackagingBulkImportService packagingBulkImportService, MaterialBulkImportService materialBulkImportService, MatrixRateImportService matrixRateImportService, ContainerRateImportService containerRateImportService, BatchGeoApiService batchGeoApiService) { public BulkImportService(MatrixRateExcelMapper matrixRateExcelMapper, ContainerRateExcelMapper containerRateExcelMapper, MaterialExcelMapper materialExcelMapper, PackagingExcelMapper packagingExcelMapper, NodeExcelMapper nodeExcelMapper, NodeBulkImportService nodeBulkImportService, PackagingBulkImportService packagingBulkImportService, MaterialBulkImportService materialBulkImportService, MatrixRateImportService matrixRateImportService, ContainerRateImportService containerRateImportService, BatchGeoApiService batchGeoApiService) {
this.matrixRateExcelMapper = matrixRateExcelMapper; this.matrixRateExcelMapper = matrixRateExcelMapper;
this.containerRateExcelMapper = containerRateExcelMapper; this.containerRateExcelMapper = containerRateExcelMapper;
this.materialExcelMapper = materialExcelMapper; this.materialExcelMapper = materialExcelMapper;
this.packagingExcelMapper = packagingExcelMapper; this.packagingExcelMapper = packagingExcelMapper;
this.nodeExcelMapper = nodeExcelMapper; this.nodeExcelMapper = nodeExcelMapper;
this.nodeRepository = nodeRepository;
this.nodeTransformer = nodeTransformer;
this.nodeBulkImportService = nodeBulkImportService; this.nodeBulkImportService = nodeBulkImportService;
this.packagingBulkImportService = packagingBulkImportService; this.packagingBulkImportService = packagingBulkImportService;
this.materialBulkImportService = materialBulkImportService; this.materialBulkImportService = materialBulkImportService;

View file

@ -10,7 +10,9 @@ import de.avatic.lcc.repositories.rates.ValidityPeriodRepository;
import de.avatic.lcc.service.transformer.bulk.BulkOperationTransformer; import de.avatic.lcc.service.transformer.bulk.BulkOperationTransformer;
import de.avatic.lcc.service.users.AuthorizationService; import de.avatic.lcc.service.users.AuthorizationService;
import de.avatic.lcc.util.exception.badrequest.FileFormatNotSupportedException; import de.avatic.lcc.util.exception.badrequest.FileFormatNotSupportedException;
import de.avatic.lcc.util.exception.base.ForbiddenException;
import de.avatic.lcc.util.exception.base.InternalErrorException; import de.avatic.lcc.util.exception.base.InternalErrorException;
import de.avatic.lcc.util.exception.internalerror.ExcelValidationError;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
@ -39,6 +41,8 @@ public class BulkOperationService {
public void processFileImport(BulkFileType fileType, MultipartFile file) { public void processFileImport(BulkFileType fileType, MultipartFile file) {
var userId = authorizationService.getUserId(); var userId = authorizationService.getUserId();
checkAuthorized(fileType);
String contentType = file.getContentType(); String contentType = file.getContentType();
if (!"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet".equals(contentType) && if (!"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet".equals(contentType) &&
@ -62,10 +66,23 @@ public class BulkOperationService {
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); //TODO throw a nice exception throw new ExcelValidationError("Unable to read uploaded file.");
} }
} }
private void checkAuthorized(BulkFileType fileType) {
if( switch (fileType) {
case CONTAINER_RATE, COUNTRY_MATRIX -> authorizationService.hasAnyRole("SUPER", "FREIGHT");
case MATERIAL -> authorizationService.hasAnyRole("SUPER", "MATERIAL");
case PACKAGING -> authorizationService.hasAnyRole("SUPER", "PACKAGING");
case NODE -> authorizationService.hasAnyRole("SUPER");
}) {
throw new ForbiddenException("You are not authorized to perform this operation");
}
}
public void processFileExport(BulkFileType type, Integer validityPeriodId) { public void processFileExport(BulkFileType type, Integer validityPeriodId) {
var userId = authorizationService.getUserId(); var userId = authorizationService.getUserId();

View file

@ -49,7 +49,7 @@ public class TemplateExportService {
public InputStreamSource generateTemplate(BulkFileType bulkFileType) { public InputStreamSource generateTemplate(BulkFileType bulkFileType) {
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
Workbook workbook = new XSSFWorkbook();) { Workbook workbook = new XSSFWorkbook()) {
Sheet sheet = workbook.createSheet(BulkFileTypes.valueOf(bulkFileType.name()).getSheetName()); Sheet sheet = workbook.createSheet(BulkFileTypes.valueOf(bulkFileType.name()).getSheetName());

View file

@ -229,7 +229,7 @@ public class ContainerCalculationService {
} }
private static enum SolutionType { private enum SolutionType {
HORIZONTAL, VERTICAL HORIZONTAL, VERTICAL
} }
@ -289,15 +289,7 @@ public class ContainerCalculationService {
} }
@Renderer(text = "getFullDebugInfo()") @Renderer(text = "getFullDebugInfo()")
private static class Block { private record Block(PackagingDimension dimension, BlockType type) {
private final PackagingDimension dimension;
private final BlockType type;
public Block(PackagingDimension dimension, BlockType type) {
this.dimension = dimension;
this.type = type;
}
public int calculateBlockCount(int containerColumnLength) { public int calculateBlockCount(int containerColumnLength) {
return containerColumnLength / type.getBlockColumnLength(dimension); return containerColumnLength / type.getBlockColumnLength(dimension);

View file

@ -14,6 +14,7 @@ import de.avatic.lcc.model.db.rates.ValidityPeriodState;
import de.avatic.lcc.model.db.utils.WeightUnit; import de.avatic.lcc.model.db.utils.WeightUnit;
import de.avatic.lcc.repositories.NodeRepository; import de.avatic.lcc.repositories.NodeRepository;
import de.avatic.lcc.repositories.premise.*; import de.avatic.lcc.repositories.premise.*;
import de.avatic.lcc.repositories.properties.PropertyRepository;
import de.avatic.lcc.repositories.properties.PropertySetRepository; import de.avatic.lcc.repositories.properties.PropertySetRepository;
import de.avatic.lcc.repositories.rates.ContainerRateRepository; import de.avatic.lcc.repositories.rates.ContainerRateRepository;
import de.avatic.lcc.repositories.rates.MatrixRateRepository; import de.avatic.lcc.repositories.rates.MatrixRateRepository;
@ -25,9 +26,12 @@ import de.avatic.lcc.util.exception.internalerror.PremiseValidationError;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalUnit;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.TimeUnit;
@Service @Service
public class PreCalculationCheckService { public class PreCalculationCheckService {
@ -48,8 +52,9 @@ public class PreCalculationCheckService {
private final ContainerRateRepository containerRateRepository; private final ContainerRateRepository containerRateRepository;
private final ValidityPeriodRepository validityPeriodRepository; private final ValidityPeriodRepository validityPeriodRepository;
private final PropertySetRepository propertySetRepository; private final PropertySetRepository propertySetRepository;
private final PropertyRepository propertyRepository;
public PreCalculationCheckService(PremiseRepository premiseRepository, CustomApiService customApiService, DestinationRepository destinationRepository, RouteRepository routeRepository, NodeRepository nodeRepository, DimensionTransformer dimensionTransformer, PropertyService propertyService, RouteSectionRepository routeSectionRepository, RouteNodeRepository routeNodeRepository, MatrixRateRepository matrixRateRepository, ContainerRateRepository containerRateRepository, ValidityPeriodRepository validityPeriodRepository, PropertySetRepository propertySetRepository) { public PreCalculationCheckService(PremiseRepository premiseRepository, CustomApiService customApiService, DestinationRepository destinationRepository, RouteRepository routeRepository, NodeRepository nodeRepository, DimensionTransformer dimensionTransformer, PropertyService propertyService, RouteSectionRepository routeSectionRepository, RouteNodeRepository routeNodeRepository, MatrixRateRepository matrixRateRepository, ContainerRateRepository containerRateRepository, ValidityPeriodRepository validityPeriodRepository, PropertySetRepository propertySetRepository, PropertyRepository propertyRepository) {
this.premiseRepository = premiseRepository; this.premiseRepository = premiseRepository;
this.customApiService = customApiService; this.customApiService = customApiService;
this.destinationRepository = destinationRepository; this.destinationRepository = destinationRepository;
@ -64,6 +69,7 @@ public class PreCalculationCheckService {
this.containerRateRepository = containerRateRepository; this.containerRateRepository = containerRateRepository;
this.validityPeriodRepository = validityPeriodRepository; this.validityPeriodRepository = validityPeriodRepository;
this.propertySetRepository = propertySetRepository; this.propertySetRepository = propertySetRepository;
this.propertyRepository = propertyRepository;
} }
public void doPrecheck(Integer premiseId, Integer setId, Integer periodId) { public void doPrecheck(Integer premiseId, Integer setId, Integer periodId) {
@ -125,17 +131,30 @@ public class PreCalculationCheckService {
private void periodCheck(ValidityPeriod period, PropertySet set) { private void periodCheck(ValidityPeriod period, PropertySet set) {
if(set == null) if(set == null)
throw new PremiseValidationError("There are no system properties for the given date."); throw new PremiseValidationError("There are no system properties for the given date. Please contact your administrator.");
if(period == null) if(period == null)
throw new PremiseValidationError("There are no rates for the given date."); throw new PremiseValidationError("There are no rates for the given date. Please contact your administrator.");
if(ValidityPeriodState.VALID != period.getState() && ValidityPeriodState.EXPIRED != period.getState()) if(ValidityPeriodState.VALID != period.getState() && ValidityPeriodState.EXPIRED != period.getState())
throw new PremiseValidationError("There are no valid rates for the given date."); throw new PremiseValidationError("There are no valid rates for the given date. Please contact your administrator.");
if(ValidityPeriodState.VALID != set.getState() && ValidityPeriodState.EXPIRED != period.getState()) if(ValidityPeriodState.VALID != set.getState() && ValidityPeriodState.EXPIRED != period.getState())
throw new PremiseValidationError("There are no valid system properties for the given date."); throw new PremiseValidationError("There are no valid system properties for the given date. Please contact your administrator.");
//TODO: sicherstellen, dass die valid days für den zeitpunkt galten zu dem die valid period galt (wenn rückwirkend gerechnet wird)
var validDays = propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.VALID_DAYS, set.getId());
var renewals = period.getRenewals();
if(validDays.isEmpty())
throw new PremiseValidationError("There are no valid days property. Please contact your administrator");
var validDaysInt = Integer.parseInt(validDays.get().getCurrentValue());
if(!period.getStartDate().plusDays((long) validDaysInt * renewals).isAfter(LocalDateTime.now()))
throw new PremiseValidationError("There are no valid rates for the given date. Please contact your administrator.");
} }

View file

@ -130,8 +130,6 @@ public class ReportTransformer {
return new TimePeriod(startDate, endDate); return new TimePeriod(startDate, endDate);
} }
;
private WeightedTotalCosts getWeightedTotalCosts(Map<Integer, List<CalculationJobRouteSection>> sectionsMap) { private WeightedTotalCosts getWeightedTotalCosts(Map<Integer, List<CalculationJobRouteSection>> sectionsMap) {
BigDecimal totalPreRunCost = BigDecimal.ZERO; BigDecimal totalPreRunCost = BigDecimal.ZERO;

View file

@ -9,6 +9,8 @@ import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.Arrays;
@Service @Service
public class AuthorizationService { public class AuthorizationService {
@ -35,9 +37,22 @@ public class AuthorizationService {
} }
public boolean isSuper() { public boolean isSuper() {
return hasRole("SUPER");
}
public boolean hasAnyRole(String... roles) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication(); Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth.getPrincipal() instanceof LccOidcUser user) { if (auth.getPrincipal() instanceof LccOidcUser user) {
return user.getAuthorities().stream().anyMatch(authority -> authority.getAuthority().equals("ROLE_SUPER")); return user.getAuthorities().stream().anyMatch(authority -> Arrays.asList(roles).contains(authority.getAuthority().replace("ROLE_", "")));
}
throw new ForbiddenException("No user found");
}
public boolean hasRole(String role) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth.getPrincipal() instanceof LccOidcUser user) {
return user.getAuthorities().stream().anyMatch(authority -> authority.getAuthority().equals("ROLE_" + role.toUpperCase()));
} }
throw new ForbiddenException("No user found"); throw new ForbiddenException("No user found");

View file

@ -49,8 +49,7 @@ public class UserService {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
//todo make a service. and simulate user rights in dev profile. //todo make a service. and simulate user rights in dev profile.
if (authentication != null && authentication.getPrincipal() instanceof LccOidcUser) { if (authentication != null && authentication.getPrincipal() instanceof LccOidcUser oidcUser) {
LccOidcUser oidcUser = (LccOidcUser) authentication.getPrincipal();
return oidcUser.getAuthorities().stream().anyMatch(authority -> authority.getAuthority().equals("ROLE_SUPER")); return oidcUser.getAuthorities().stream().anyMatch(authority -> authority.getAuthority().equals("ROLE_SUPER"));
} }

View file

@ -0,0 +1,20 @@
INSERT INTO sys_group(group_name, group_description)
VALUES ('none', 'no rights');
INSERT INTO sys_group(group_name, group_description)
VALUES ('basic', 'Login, generate reports');
INSERT INTO sys_group(group_name, group_description)
VALUES ('calculation', 'Login, generate reports, do calculations');
INSERT INTO sys_group(group_name, group_description)
VALUES ('freight', 'Login, generate reports, edit freight rates');
INSERT INTO sys_group(group_name, group_description)
VALUES ('packaging', 'Login, generate reports, edit packaging data');
INSERT INTO sys_group(group_name, group_description)
VALUES ('material', 'Login, generate reports, edit material data');
INSERT INTO sys_group(group_name, group_description)
VALUES ('super',
'Login, generate reports, do calculations, edit freight rates, edit packaging data');
INSERT INTO sys_group(group_name, group_description)
VALUES ('service', 'Register API Tokens');
INSERT INTO sys_group(group_name, group_description)
VALUES ('right-management',
'Add/Remove users, groups, etc.');

View file

@ -18,6 +18,8 @@ VALUES ('freight', 'Login, generate reports, edit freight rates');
INSERT INTO sys_group(group_name, group_description) INSERT INTO sys_group(group_name, group_description)
VALUES ('packaging', 'Login, generate reports, edit packaging data'); VALUES ('packaging', 'Login, generate reports, edit packaging data');
INSERT INTO sys_group(group_name, group_description) INSERT INTO sys_group(group_name, group_description)
VALUES ('material', 'Login, generate reports, edit material data');
INSERT INTO sys_group(group_name, group_description)
VALUES ('super', VALUES ('super',
'Login, generate reports, do calculations, edit freight rates, edit packaging data'); 'Login, generate reports, do calculations, edit freight rates, edit packaging data');
INSERT INTO sys_group(group_name, group_description) INSERT INTO sys_group(group_name, group_description)