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 class="bulk-operation-caption">dataset</div>
<div class="bulk-operation-data">
<radio-option name="export-dataset" value="NODE" v-model="exportDataset">nodes</radio-option>
<radio-option name="export-dataset" value="COUNTRY_MATRIX" v-model="exportDataset">kilometer rates
<radio-option v-if="canModify('nodes')" name="export-dataset" value="NODE" v-model="exportDataset">nodes
</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 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 class="bulk-operation-caption">validity period</div>
<div class="bulk-operation-data">
<div v-if="canModify('rates')" class="bulk-operation-caption">validity period</div>
<div v-if="canModify('rates')" class="bulk-operation-data">
<div class="period-select-container">
<dropdown :options="periods"
emptyText="No property set available"
@ -48,13 +55,20 @@
<div class="bulk-operation-header">Import</div>
<div class="bulk-operation-caption">dataset</div>
<div class="bulk-operation-data">
<radio-option name="import-dataset" value="NODE" v-model="importDataset">nodes</radio-option>
<radio-option name="import-dataset" value="COUNTRY_MATRIX" v-model="importDataset">kilometer rates
<radio-option v-if="canModify('nodes')" name="import-dataset" value="NODE" v-model="importDataset">nodes
</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 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 class="bulk-operation-caption">file</div>
@ -79,8 +93,11 @@
<div class="bulk-operation-box-status-container">
<div class="bulk-operation-status">
<div class="bulk-operation-header">History</div>
<div v-if="this.bulkOperationStore.getBulkOperations.length === 0" class="empty-container">No recent bulk operations</div>
<bulk-operation v-else v-for="bulk in this.bulkOperationStore.getBulkOperations" :key="bulk.id" :operation="bulk" @download="fetchFile"></bulk-operation>
<div v-if="this.bulkOperationStore.getBulkOperations.length === 0" class="empty-container">No recent bulk
operations
</div>
<bulk-operation v-else v-for="bulk in this.bulkOperationStore.getBulkOperations" :key="bulk.id"
:operation="bulk" @download="fetchFile"></bulk-operation>
</div>
</div>
@ -101,6 +118,7 @@ import {useValidityPeriodStore} from "@/store/validityPeriod.js";
import {useBulkOperationStore} from "@/store/bulkOperation.js";
import BulkOperation from "@/components/layout/bulkoperation/BulkOperation.vue";
import logger from "@/logger.js";
import {useActiveUserStore} from "@/store/activeuser.js";
export default {
name: "BulkOperations",
@ -125,13 +143,13 @@ export default {
},
watch: {
async isSelected(newVal) {
if(newVal === true)
if (newVal === true && this.activeUserStore.allowRates)
await this.validityPeriodStore.loadPeriods();
await this.bulkOperationStore.manageStatus();
}
},
computed: {
...mapStores(useValidityPeriodStore, useBulkOperationStore),
...mapStores(useValidityPeriodStore, useBulkOperationStore, useActiveUserStore),
showValidityPeriod() {
return this.exportType === "download" && (this.exportDataset === "COUNTRY_MATRIX" || this.exportDataset === "CONTAINER_RATE");
},
@ -167,8 +185,20 @@ export default {
}
},
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) {
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'}`
},
@ -195,7 +225,7 @@ export default {
this.fileBlob = await this.readFileAsBlob(file);
// 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;
logger.info(`File loaded into memory: ${file.name} (${(file.size / 1024).toFixed(2)} KB)`);
@ -220,7 +250,7 @@ export default {
reader.onload = (e) => {
// 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);
};

View file

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

View file

@ -18,12 +18,12 @@ export const useActiveUserStore = defineStore('activeUser', {
allowConfiguration(state) {
if (state.user === null)
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) {
if (state.user === null)
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) {
if (state.user === null)
@ -45,6 +45,11 @@ export const useActiveUserStore = defineStore('activeUser', {
return false;
return state.user.groups?.includes("freight");
},
isMaterial(state) {
if (state.user === null)
return false;
return state.user.groups?.includes("material");
},
allowRates(state) {
if (state.user === null)
return false;

View file

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

View file

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

View file

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

View file

@ -37,20 +37,20 @@ public class BulkOperationController {
}
@GetMapping({"/status/", "/status"})
@PreAuthorize("hasAnyRole('SUPER', 'FREIGHT', 'PACKAGING')")
@PreAuthorize("hasAnyRole('SUPER', 'FREIGHT', 'PACKAGING', 'MATERIAL')")
public ResponseEntity<List<BulkOperationDTO>> getBulkStatus() {
return ResponseEntity.ok(bulkOperationService.getStatus());
}
@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) {
bulkOperationService.processFileImport(type, file);
return ResponseEntity.ok().build();
}
@GetMapping({"/templates/{type}", "/templates/{type}/"})
@PreAuthorize("hasAnyRole('SUPER', 'FREIGHT', 'PACKAGING')")
@PreAuthorize("hasAnyRole('SUPER', 'FREIGHT', 'PACKAGING', 'MATERIAL')")
public ResponseEntity<InputStreamResource> generateTemplate(@PathVariable BulkFileType type) {
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Disposition", "attachment; filename=lcc_template_" + type.name().toLowerCase() + ".xlsx");
@ -64,7 +64,7 @@ public class BulkOperationController {
@GetMapping({"/download/{type}", "/download/{type}/"})
@PreAuthorize("hasAnyRole('SUPER', 'FREIGHT', 'PACKAGING')")
@PreAuthorize("hasAnyRole('SUPER', 'FREIGHT', 'PACKAGING', 'MATERIAL')")
public ResponseEntity<Void> scheduleDownload(@PathVariable BulkFileType type) {
bulkOperationService.processFileExport(type);
return ResponseEntity.ok().build();
@ -72,7 +72,7 @@ public class BulkOperationController {
@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) {
bulkOperationService.processFileExport(type, validityPeriodId);
return ResponseEntity.ok().build();
@ -80,7 +80,7 @@ public class BulkOperationController {
}
@GetMapping({"/file/{processId}", "/file/{processId}/"})
@PreAuthorize("hasAnyRole('SUPER', 'FREIGHT', 'PACKAGING')")
@PreAuthorize("hasAnyRole('SUPER', 'FREIGHT', 'PACKAGING', 'MATERIAL')")
public ResponseEntity<InputStreamResource> download(@PathVariable("processId") Integer 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).
*/
@GetMapping("/")
@PreAuthorize("hasAnyRole('SUPER', 'CALCULATION', 'BASIC', 'FREIGHT', 'PACKAGING')")
@PreAuthorize("hasAnyRole('SUPER', 'CALCULATION', 'BASIC', 'FREIGHT', 'PACKAGING', 'MATERIAL')")
public ResponseEntity<List<MaterialDTO>> listMaterials(
@RequestParam(defaultValue = "true") String excludeDeprecated,
@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.
*/
@GetMapping("/{id}")
@PreAuthorize("hasAnyRole('SUPER', 'CALCULATION', 'BASIC', 'FREIGHT', 'PACKAGING')")
@PreAuthorize("hasAnyRole('SUPER', 'CALCULATION', 'BASIC', 'FREIGHT', 'PACKAGING', 'MATERIAL')")
public ResponseEntity<MaterialDetailDTO> getMaterialDetails(@PathVariable Integer id) {
return ResponseEntity.ok(materialService.getMaterial(id));
}

View file

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

View file

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

View file

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

View file

@ -122,7 +122,7 @@ public class SysErrorRepository {
}
// 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);
// 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,
e.calculation_job_id, e.bulk_operation_id, e.type, e.created_at
FROM sys_error e
""" + whereClause.toString() + """
""" + whereClause + """
ORDER BY e.created_at DESC
LIMIT :limit OFFSET :offset
""";

View file

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

View file

@ -135,6 +135,28 @@ public class DestinationRepository {
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
public List<Destination> getByPremiseIdsAndNodeId(List<Integer> premiseId, Integer nodeId, Integer userId) {
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> {
@Override

View file

@ -288,13 +288,7 @@ public class ContainerRateRepository {
}
private static class ContainerRateMapper implements RowMapper<ContainerRate> {
private final boolean fetchCountryIds;
public ContainerRateMapper(boolean fetchCountryIds) {
this.fetchCountryIds = fetchCountryIds;
}
private record ContainerRateMapper(boolean fetchCountryIds) implements RowMapper<ContainerRate> {
public ContainerRateMapper() {
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.transformer.premise.DestinationTransformer;
import de.avatic.lcc.service.users.AuthorizationService;
import de.avatic.lcc.util.exception.base.ForbiddenException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -34,15 +33,12 @@ public class DestinationService {
private final RouteSectionRepository routeSectionRepository;
private final RouteNodeRepository routeNodeRepository;
private final RoutingService routingService;
;
private final NodeRepository nodeRepository;
private final PremiseRepository premiseRepository;
private final UserNodeRepository userNodeRepository;
private final PropertyRepository propertyRepository;
private final PropertyService propertyService;
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.destinationTransformer = destinationTransformer;
this.routeRepository = routeRepository;
@ -52,16 +48,16 @@ public class DestinationService {
this.nodeRepository = nodeRepository;
this.premiseRepository = premiseRepository;
this.userNodeRepository = userNodeRepository;
this.propertyRepository = propertyRepository;
this.propertyService = propertyService;
this.authorizationService = authorizationService;
}
@Transactional
public Map<Integer, List<DestinationDTO>> setDestination(DestinationSetDTO dto) {
//TODO fix user authorization
var userId = 1;
//dto.getPremiseId().forEach(id -> destinationRepository.checkOwner(id, userId));
var admin = authorizationService.isSuper();
Integer userId = authorizationService.getUserId();
if (!admin)
destinationRepository.checkOwner(dto.getPremiseId(), userId);
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) {
Node destinationNode = nodeRepository.getById(destinationNodeId).orElseThrow();
@ -141,17 +136,22 @@ public class DestinationService {
}
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());
}
@Transactional
public void updateDestination(Integer id, DestinationUpdateDTO destinationUpdateDTO) {
//todo check authorization
var admin = authorizationService.isSuper();
Integer userId = authorizationService.getUserId();
destinationRepository.checkOwner(id, userId);
if (!admin)
destinationRepository.checkOwner(id, userId);
var selectedRouteId = destinationUpdateDTO.getRouteSelectedId();
@ -159,7 +159,6 @@ public class DestinationService {
routeRepository.updateSelectedByDestinationId(id, selectedRouteId);
}
destinationRepository.update(id,
destinationUpdateDTO.getAnnualAmount(),
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> userNodes = new HashMap<>();
for(var premise : premisses) {
for(var destinationId : destinationIds) {
for (var premise : premisses) {
for (var destinationId : destinationIds) {
boolean isUserSupplierNode = (premise.getSupplierNodeId() == null);
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());
}
if(!isUserSupplierNode && !nodes.containsKey(premise.getSupplierNodeId())) {
if (!isUserSupplierNode && !nodes.containsKey(premise.getSupplierNodeId())) {
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());
}
@ -205,7 +204,6 @@ public class DestinationService {
@Transactional
public void saveRoute(List<RouteInformation> routeObjs, Integer destinationId) {
for (var routeObj : routeObjs) {
boolean first = true;
Integer fromNodeId = null;
@ -238,44 +236,12 @@ public class DestinationService {
}
}
private record RouteIds(Integer fromNodeId, Integer toNodeId, boolean isUserSupplierNode) {}
@Transactional
public void findRouteAndSave(Integer destinationId, Node destination, Node supplier, boolean isUserSupplierNode) {
var routeObjs = routingService.findRoutes(destination, supplier, isUserSupplierNode);
for (var routeObj : routeObjs) {
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;
}
}
saveRoute(routeObjs, destinationId);
}
@Transactional
@ -289,14 +255,14 @@ public class DestinationService {
destinations.forEach(destination -> deleteDestinationById(destination.getId(), deleteRoutesOnly));
}
@Transactional
public void deleteDestinationById(Integer id, boolean deleteRoutesOnly) {
//todo check authorization
var admin = authorizationService.isSuper();
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);
for (var route : routes) {
@ -310,11 +276,6 @@ public class DestinationService {
if (!deleteRoutesOnly)
destinationRepository.deleteById(id);
return;
}
throw new ForbiddenException("Not authorized to delete destination with id " + id);
}
@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)
public List<PremiseDetailDTO> getPremises(List<Integer> premiseIds) {
var admin = authorizationService.isSuper();
var userId = authorizationService.getUserId();
if (!admin)
premiseRepository.checkOwner(premiseIds, userId);
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) {
var admin = authorizationService.isSuper();
var userId = authorizationService.getUserId();
if (!admin)
premiseRepository.checkOwner(premises, userId);
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));
@ -183,11 +188,12 @@ public class PremisesService {
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();
if (!admin)
premiseRepository.checkOwner(packagingDTO.getPremiseIds(), userId);
var dimensions = packagingDTO.getDimensions() == null ? null : dimensionTransformer.toDimensionEntity(packagingDTO.getDimensions());
@ -197,8 +203,10 @@ public class PremisesService {
}
public void updateMaterial(MaterialUpdateDTO materialUpdateDTO) {
//TODO check values. and return errors if needed
var admin = authorizationService.isSuper();
var userId = authorizationService.getUserId();
if (!admin)
premiseRepository.checkOwner(materialUpdateDTO.getPremiseIds(), userId);
var tariffRate = materialUpdateDTO.getTariffRate() == null ? null : BigDecimal.valueOf(materialUpdateDTO.getTariffRate().doubleValue());
@ -207,8 +215,10 @@ public class PremisesService {
}
public void updatePrice(PriceUpdateDTO priceUpdateDTO) {
//TODO check values. and return errors if needed
var admin = authorizationService.isSuper();
var userId = authorizationService.getUserId();
if (!admin)
premiseRepository.checkOwner(priceUpdateDTO.getPremiseIds(), userId);
var price = priceUpdateDTO.getPrice() == null ? null : BigDecimal.valueOf(priceUpdateDTO.getPrice().doubleValue());
@ -219,9 +229,13 @@ public class PremisesService {
@Transactional
public void delete(List<Integer> premiseIds) {
var admin = authorizationService.isSuper();
var userId = authorizationService.getUserId();
if (!admin)
premiseRepository.checkOwner(premiseIds, userId);
// only delete drafts.
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) {
var admin = authorizationService.isSuper();
var userId = authorizationService.getUserId();
if (!admin)
premiseRepository.checkOwner(premiseIds, userId);
// only archive completed.
var premisses = premiseRepository.getPremisesById(premiseIds);
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;
}
@Transactional
public List<Integer> duplicate(List<Integer> premissIds) {
var admin = authorizationService.isSuper();
var userId = authorizationService.getUserId();
if (!admin)
premiseRepository.checkOwner(premissIds, userId);
var newIds = new ArrayList<Integer>();
premissIds.forEach(id -> {

View file

@ -172,7 +172,7 @@ public class PropertyValidationService {
public void evaluate(String value) {
if (operator == CompareOperator.ENUM) {
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 {
try {

View file

@ -53,8 +53,8 @@ public class UserNodeService {
node.setName(dto.getName());
node.setAddress(dto.getAddress());
node.setGeoLng(BigDecimal.valueOf(dto.getLocation().getLongitude()));
node.setGeoLat(BigDecimal.valueOf(dto.getLocation().getLatitude()));
node.setGeoLng(BigDecimal.valueOf(dto.getLocation().longitude()));
node.setGeoLat(BigDecimal.valueOf(dto.getLocation().latitude()));
node.setDestination(false);
node.setSource(true);
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.BulkOperation;
import de.avatic.lcc.repositories.NodeRepository;
import de.avatic.lcc.service.api.BatchGeoApiService;
import de.avatic.lcc.service.bulk.bulkImport.*;
import de.avatic.lcc.service.excelMapper.*;
import de.avatic.lcc.service.transformer.generic.NodeTransformer;
import de.avatic.lcc.util.exception.internalerror.ExcelValidationError;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
@ -26,8 +24,7 @@ public class BulkImportService {
private final MaterialExcelMapper materialExcelMapper;
private final PackagingExcelMapper packagingExcelMapper;
private final NodeExcelMapper nodeExcelMapper;
private final NodeRepository nodeRepository;
private final NodeTransformer nodeTransformer;
private final NodeBulkImportService nodeBulkImportService;
private final PackagingBulkImportService packagingBulkImportService;
private final MaterialBulkImportService materialBulkImportService;
@ -35,14 +32,12 @@ public class BulkImportService {
private final ContainerRateImportService containerRateImportService;
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.containerRateExcelMapper = containerRateExcelMapper;
this.materialExcelMapper = materialExcelMapper;
this.packagingExcelMapper = packagingExcelMapper;
this.nodeExcelMapper = nodeExcelMapper;
this.nodeRepository = nodeRepository;
this.nodeTransformer = nodeTransformer;
this.nodeBulkImportService = nodeBulkImportService;
this.packagingBulkImportService = packagingBulkImportService;
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.users.AuthorizationService;
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.internalerror.ExcelValidationError;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
@ -39,6 +41,8 @@ public class BulkOperationService {
public void processFileImport(BulkFileType fileType, MultipartFile file) {
var userId = authorizationService.getUserId();
checkAuthorized(fileType);
String contentType = file.getContentType();
if (!"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet".equals(contentType) &&
@ -62,10 +66,23 @@ public class BulkOperationService {
} 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) {
var userId = authorizationService.getUserId();

View file

@ -49,7 +49,7 @@ public class TemplateExportService {
public InputStreamSource generateTemplate(BulkFileType bulkFileType) {
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
Workbook workbook = new XSSFWorkbook();) {
Workbook workbook = new XSSFWorkbook()) {
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
}
@ -289,15 +289,7 @@ public class ContainerCalculationService {
}
@Renderer(text = "getFullDebugInfo()")
private static class Block {
private final PackagingDimension dimension;
private final BlockType type;
public Block(PackagingDimension dimension, BlockType type) {
this.dimension = dimension;
this.type = type;
}
private record Block(PackagingDimension dimension, BlockType type) {
public int calculateBlockCount(int containerColumnLength) {
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.repositories.NodeRepository;
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.rates.ContainerRateRepository;
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 java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalUnit;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
@Service
public class PreCalculationCheckService {
@ -48,8 +52,9 @@ public class PreCalculationCheckService {
private final ContainerRateRepository containerRateRepository;
private final ValidityPeriodRepository validityPeriodRepository;
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.customApiService = customApiService;
this.destinationRepository = destinationRepository;
@ -64,6 +69,7 @@ public class PreCalculationCheckService {
this.containerRateRepository = containerRateRepository;
this.validityPeriodRepository = validityPeriodRepository;
this.propertySetRepository = propertySetRepository;
this.propertyRepository = propertyRepository;
}
public void doPrecheck(Integer premiseId, Integer setId, Integer periodId) {
@ -125,17 +131,30 @@ public class PreCalculationCheckService {
private void periodCheck(ValidityPeriod period, PropertySet set) {
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)
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())
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())
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);
}
;
private WeightedTotalCosts getWeightedTotalCosts(Map<Integer, List<CalculationJobRouteSection>> sectionsMap) {
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.stereotype.Service;
import java.util.Arrays;
@Service
public class AuthorizationService {
@ -35,9 +37,22 @@ public class AuthorizationService {
}
public boolean isSuper() {
return hasRole("SUPER");
}
public boolean hasAnyRole(String... roles) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
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");

View file

@ -49,8 +49,7 @@ public class UserService {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
//todo make a service. and simulate user rights in dev profile.
if (authentication != null && authentication.getPrincipal() instanceof LccOidcUser) {
LccOidcUser oidcUser = (LccOidcUser) authentication.getPrincipal();
if (authentication != null && authentication.getPrincipal() instanceof LccOidcUser oidcUser) {
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)
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)