diff --git a/src/frontend/src/App.vue b/src/frontend/src/App.vue index 122f690..38cc276 100644 --- a/src/frontend/src/App.vue +++ b/src/frontend/src/App.vue @@ -16,7 +16,6 @@ import ErrorNotification from "@/components/UI/ErrorNotifcation.vue"; export default { components: {ErrorNotification, TheHeader}, - } diff --git a/src/frontend/src/components/layout/TheHeader.vue b/src/frontend/src/components/layout/TheHeader.vue index 02000f1..6fdb243 100644 --- a/src/frontend/src/components/layout/TheHeader.vue +++ b/src/frontend/src/components/layout/TheHeader.vue @@ -3,9 +3,9 @@ @@ -15,10 +15,27 @@ import {PhCube, PhHeart, PhHorse} from "@phosphor-icons/vue"; import NavigationElement from "@/components/UI/NavigationElement.vue"; import Config from "@/pages/Config.vue"; +import {useActiveUserStore} from "@/store/activeuser.js"; +import {mapStores} from "pinia"; export default { name: "TheHeader", - components: {Parameterization: Config, NavigationElement, PhCube, PhHeart, PhHorse} + components: {Parameterization: Config, NavigationElement, PhCube, PhHeart, PhHorse}, + computed: { + ...mapStores(useActiveUserStore), + showCalculation() { + return this.activeUserStore.allowCalculation; + }, + showReporting() { + return this.activeUserStore.allowReporting; + }, + showConfiguration() { + return this.activeUserStore.allowConfiguration; + } + }, + async created() { + await this.activeUserStore.loadIfRequired(); + } } diff --git a/src/frontend/src/pages/Config.vue b/src/frontend/src/pages/Config.vue index 873f561..8c58801 100644 --- a/src/frontend/src/pages/Config.vue +++ b/src/frontend/src/pages/Config.vue @@ -25,6 +25,8 @@ import Rates from "@/components/layout/config/Rates.vue"; import Nodes from "@/components/layout/config/Nodes.vue"; import Materials from "@/components/layout/config/Materials.vue"; import ErrorLog from "@/pages/ErrorLog.vue"; +import {mapStores} from "pinia"; +import {useActiveUserStore} from "@/store/activeuser.js"; export default { name: "Config", @@ -32,50 +34,68 @@ export default { data() { return { currentTab: null, - tabsConfig: [ - { - title: 'Properties', - component: markRaw(Properties), - props: { isSelected: false}, - }, - { - title: 'System log', - component: markRaw(ErrorLog), - props: { isSelected: false}, - }, - { - title: 'Materials', - component: markRaw(Materials), - props: { isSelected: false}, - }, - { - title: 'Nodes', - component: markRaw(Nodes), - props: { isSelected: false}, - }, - { - title: 'Rates', - component: markRaw(Rates), - props: { isSelected: false}, - }, - { - title: 'Bulk operations', - component: markRaw(BulkOperations), - props: { isSelected: false}, - } - ] + propertiesTab: { + title: 'Properties', + component: markRaw(Properties), + props: {isSelected: false}, + }, + systemLogTab: { + title: 'System log', + component: markRaw(ErrorLog), + props: {isSelected: false}, + }, + materialsTab: { + title: 'Materials', + component: markRaw(Materials), + props: {isSelected: false}, + }, + nodesTab: { + title: 'Nodes', + component: markRaw(Nodes), + props: {isSelected: false}, + }, + ratesTab: { + title: 'Rates', + component: markRaw(Rates), + props: {isSelected: false}, + }, + bulkOperationsTab: { + title: 'Bulk operations', + component: markRaw(BulkOperations), + props: {isSelected: false}, + } } + }, + computed: { + ...mapStores(useActiveUserStore), + tabsConfig() { + const tabs = []; + + if(this.activeUserStore.isSuper) { + tabs.push(this.propertiesTab); + tabs.push(this.systemLogTab); + } + + tabs.push(this.materialsTab); + tabs.push(this.nodesTab); + + if(this.activeUserStore.allowRates) + tabs.push(this.ratesTab); + + tabs.push(this.bulkOperationsTab); + + + return tabs; + + } + }, + created() { + }, methods: { handleTabChange(eventData) { - - console.log("handleTabChange") - - const { index, tab } = eventData; - console.log(`Tab ${index} activated:`, tab.title); - - this.tabsConfig.forEach(t => t.props.isSelected = t.title === tab.title); - + const {index, tab} = eventData; + this.tabsConfig.forEach(t => t.props.isSelected = t.title === tab.title); } } } diff --git a/src/frontend/src/pages/NotAuthorized.vue b/src/frontend/src/pages/NotAuthorized.vue new file mode 100644 index 0000000..58fb8ba --- /dev/null +++ b/src/frontend/src/pages/NotAuthorized.vue @@ -0,0 +1,36 @@ + + + + + + \ No newline at end of file diff --git a/src/frontend/src/router.js b/src/frontend/src/router.js index 2d001ac..11d5612 100644 --- a/src/frontend/src/router.js +++ b/src/frontend/src/router.js @@ -8,53 +8,118 @@ import CalculationAssistant from "@/pages/CalculationAssistant.vue"; import ErrorLog from "@/pages/ErrorLog.vue"; import CalculationDump from "@/components/layout/dev/CalculationDump.vue"; import DevPage from "@/pages/DevPage.vue"; +import {useActiveUserStore} from "@/store/activeuser.js"; +import NotAuthorized from "@/pages/NotAuthorized.vue"; const router = createRouter({ history: createWebHistory(), routes: [ + { + path: '/not-authorized', + component: NotAuthorized, + name: 'not-authorized', + }, { path: '/', redirect: '/calculations', name: 'calculation-list', - }, { path: '/assistant', component: CalculationAssistant, name: 'assistant', + beforeEnter: async (to, from) => { + + const userStore = useActiveUserStore(); + await userStore.loadIfRequired(); + + if (userStore.allowCalculation) { + return true; + } + return {name: 'not-authorized'}; + }, }, { path: '/calculations', component: Calculations, name: 'home', + beforeEnter: async (to, from) => { + const userStore = useActiveUserStore(); + await userStore.loadIfRequired(); + + if (userStore.allowCalculation) { + return true; + } + return {name: 'not-authorized'}; + }, }, { path: '/edit/:id', component: CalculationSingleEdit, name: 'edit', + beforeEnter: async (to, from) => { + const userStore = useActiveUserStore(); + await userStore.loadIfRequired(); + + if (userStore.allowCalculation) { + return true; + } + return {name: 'not-authorized'}; + }, }, { path: '/bulk/:ids', component: CalculationMassEdit, name: 'bulk', + beforeEnter: async (to, from) => { + const userStore = useActiveUserStore(); + await userStore.loadIfRequired(); + + if (userStore.allowCalculation) { + return true; + } + return {name: 'not-authorized'}; + }, }, { path: '/bulk/:ids/edit/:id', component: CalculationSingleEdit, name: 'bulk-single-edit', + beforeEnter: async (to, from) => { + const userStore = useActiveUserStore(); + await userStore.loadIfRequired(); + + if (userStore.allowCalculation) { + return true; + } + return {name: 'not-authorized'}; + }, }, { path: '/reports', component: Reporting, + beforeEnter: async (to, from) => { + const userStore = useActiveUserStore(); + await userStore.loadIfRequired(); + + if (userStore.allowReporting) { + return true; + } + return {name: 'not-authorized'}; + }, }, { path: '/config', - component: Config - }, - { - path: '/error', - component: ErrorLog + component: Config, + beforeEnter: async (to, from) => { + const userStore = useActiveUserStore(); + await userStore.loadIfRequired(); + if (userStore.allowConfiguration) { + return true; + } + return {name: 'not-authorized'}; + }, }, { path: '/dev/dump/:id', diff --git a/src/frontend/src/store/activeuser.js b/src/frontend/src/store/activeuser.js new file mode 100644 index 0000000..ad26342 --- /dev/null +++ b/src/frontend/src/store/activeuser.js @@ -0,0 +1,59 @@ +import {defineStore} from 'pinia' +import {config} from '@/config' +import performRequest from "@/backend.js"; + + +export const useActiveUserStore = defineStore('activeUser', { + state: () => { + return { + user: null, + } + }, + getters: { + allowCalculation(state) { + if (state.user === null) + return false; + return state.user.groups?.includes("super") || state.user.groups?.includes("calculation"); + }, + allowConfiguration(state) { + if (state.user === null) + return false; + return state.user.groups?.includes("super") || state.user.groups?.includes("freight") || state.user.groups?.includes("packaging"); + }, + allowReporting(state) { + return state.user !== null; + + }, + isSuper(state) { + if (state.user === null) + return false; + return state.user.groups?.includes("super"); + }, + isPackaging(state) { + if (state.user === null) + return false; + return state.user.groups?.includes("packaging"); + }, + isFreight(state) { + if (state.user === null) + return false; + return state.user.groups?.includes("freight"); + }, + allowRates(state) { + if (state.user === null) + return false; + return state.user.groups?.includes("super") || state.user.groups?.includes("freight"); + } + + }, + actions: { + async loadIfRequired() { + if (this.user === null) + await this.load(); + }, + async load() { + const resp = await performRequest(this, "GET", `${config.backendUrl}/active-user`, null, true); + this.user = resp.data; + } + } +}); \ No newline at end of file diff --git a/src/main/java/de/avatic/lcc/config/DevUserEmulationFilter.java b/src/main/java/de/avatic/lcc/config/DevUserEmulationFilter.java index efd0688..46f6060 100644 --- a/src/main/java/de/avatic/lcc/config/DevUserEmulationFilter.java +++ b/src/main/java/de/avatic/lcc/config/DevUserEmulationFilter.java @@ -47,12 +47,12 @@ public class DevUserEmulationFilter extends OncePerRequestFilter { Integer emulatedUserId = (Integer) session.getAttribute(DEV_USER_ID_SESSION_KEY); - if(emulatedUserId != null) { +// if(emulatedUserId != null) { User user = userRepository.getById(emulatedUserId == null ? 1 : emulatedUserId); if (user != null) { setEmulatedUser(user); } - } +// } filterChain.doFilter(request, response); } diff --git a/src/main/java/de/avatic/lcc/config/SecurityConfig.java b/src/main/java/de/avatic/lcc/config/SecurityConfig.java index 67e65bb..8d8aa13 100644 --- a/src/main/java/de/avatic/lcc/config/SecurityConfig.java +++ b/src/main/java/de/avatic/lcc/config/SecurityConfig.java @@ -11,6 +11,7 @@ import org.jetbrains.annotations.NotNull; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; @@ -33,6 +34,7 @@ import java.util.Set; import java.util.function.Supplier; @Configuration +@EnableMethodSecurity public class SecurityConfig { @Bean diff --git a/src/main/java/de/avatic/lcc/controller/GlobalExceptionHandler.java b/src/main/java/de/avatic/lcc/controller/GlobalExceptionHandler.java index dab14de..627e94b 100644 --- a/src/main/java/de/avatic/lcc/controller/GlobalExceptionHandler.java +++ b/src/main/java/de/avatic/lcc/controller/GlobalExceptionHandler.java @@ -81,8 +81,6 @@ public class GlobalExceptionHandler { return new ResponseEntity<>(new ErrorResponseDTO(error), HttpStatus.BAD_REQUEST); } - - @ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(MethodArgumentTypeMismatchException.class) public ResponseEntity handleConstraintViolation(MethodArgumentTypeMismatchException exception) { // diff --git a/src/main/java/de/avatic/lcc/controller/bulk/BulkOperationController.java b/src/main/java/de/avatic/lcc/controller/bulk/BulkOperationController.java index e457ea1..2a6d774 100644 --- a/src/main/java/de/avatic/lcc/controller/bulk/BulkOperationController.java +++ b/src/main/java/de/avatic/lcc/controller/bulk/BulkOperationController.java @@ -26,7 +26,6 @@ import java.util.List; */ @RestController @RequestMapping("/api/bulk") -@RolesAllowed({"ROLE_SUPER", "ROLE_FREIGHT", "ROLE_PACKAGING"}) public class BulkOperationController { private final BulkOperationService bulkOperationService; @@ -38,17 +37,20 @@ public class BulkOperationController { } @GetMapping({"/status/", "/status"}) + @PreAuthorize("hasAnyRole('SUPER', 'FREIGHT', 'PACKAGING')") public ResponseEntity> getBulkStatus() { return ResponseEntity.ok(bulkOperationService.getStatus()); } @PostMapping({"/upload/{type}", "/upload/{type}/"}) + @PreAuthorize("hasAnyRole('SUPER', 'FREIGHT', 'PACKAGING')") public ResponseEntity 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')") public ResponseEntity generateTemplate(@PathVariable BulkFileType type) { HttpHeaders headers = new HttpHeaders(); headers.add("Content-Disposition", "attachment; filename=lcc_template_" + type.name().toLowerCase() + ".xlsx"); @@ -62,6 +64,7 @@ public class BulkOperationController { @GetMapping({"/download/{type}", "/download/{type}/"}) + @PreAuthorize("hasAnyRole('SUPER', 'FREIGHT', 'PACKAGING')") public ResponseEntity scheduleDownload(@PathVariable BulkFileType type) { bulkOperationService.processFileExport(type); return ResponseEntity.ok().build(); @@ -69,6 +72,7 @@ public class BulkOperationController { @GetMapping({"/download/{type}/{validity_period_id}", "/download/{type}/{validity_period_id}/"}) + @PreAuthorize("hasAnyRole('SUPER', 'FREIGHT', 'PACKAGING')") public ResponseEntity scheduleDownload(@PathVariable BulkFileType type, @PathVariable("validity_period_id") Integer validityPeriodId) { bulkOperationService.processFileExport(type, validityPeriodId); return ResponseEntity.ok().build(); @@ -76,6 +80,7 @@ public class BulkOperationController { } @GetMapping({"/file/{processId}", "/file/{processId}/"}) + @PreAuthorize("hasAnyRole('SUPER', 'FREIGHT', 'PACKAGING')") public ResponseEntity download(@PathVariable("processId") Integer id) { var op = bulkOperationService.getBulkOperation(id); diff --git a/src/main/java/de/avatic/lcc/controller/calculation/PremiseController.java b/src/main/java/de/avatic/lcc/controller/calculation/PremiseController.java index b243678..7e26510 100644 --- a/src/main/java/de/avatic/lcc/controller/calculation/PremiseController.java +++ b/src/main/java/de/avatic/lcc/controller/calculation/PremiseController.java @@ -22,11 +22,13 @@ import de.avatic.lcc.service.calculation.PremiseCreationService; import de.avatic.lcc.service.calculation.PremiseSearchStringAnalyzerService; import de.avatic.lcc.util.exception.badrequest.InvalidArgumentException; import de.avatic.lcc.util.exception.base.BadRequestException; +import jakarta.annotation.security.RolesAllowed; import jakarta.validation.Valid; import jakarta.validation.constraints.Min; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -60,6 +62,7 @@ public class PremiseController { } @GetMapping({"/view", "/view/"}) + @PreAuthorize("hasAnyRole('SUPER', 'CALCULATION')") public ResponseEntity> listPremises(@RequestParam(required = false) String filter, @RequestParam(defaultValue = "20") @Min(1) int limit, @RequestParam(defaultValue = "1") @Min(1) int page, @@ -78,6 +81,7 @@ public class PremiseController { } @GetMapping({"/search", "/search/"}) + @PreAuthorize("hasAnyRole('SUPER', 'CALCULATION')") public ResponseEntity findMaterialsAndSuppliers(@RequestHeader String search) { try { @@ -93,6 +97,7 @@ public class PremiseController { } @PostMapping({"/create", "/create/"}) + @PreAuthorize("hasAnyRole('SUPER', 'CALCULATION')") public ResponseEntity> createPremises(@RequestBody @Valid CreatePremiseDTO dto) { if ((dto.getSupplierIds() == null || dto.getSupplierIds().isEmpty()) && (dto.getUserSupplierIds() == null || dto.getUserSupplierIds().isEmpty())) { @@ -109,23 +114,27 @@ public class PremiseController { @PostMapping({"/delete", "/delete/"}) + @PreAuthorize("hasAnyRole('SUPER', 'CALCULATION')") public ResponseEntity deletePremises(@RequestParam List premissIds) { premisesServices.delete(premissIds); return ResponseEntity.ok().build(); } @PostMapping({"/archive", "/archive/"}) + @PreAuthorize("hasAnyRole('SUPER', 'CALCULATION')") public ResponseEntity archivePremises(@RequestParam List premissIds) { premisesServices.archive(premissIds); return ResponseEntity.ok().build(); } @GetMapping({"/edit", "/edit/"}) + @PreAuthorize("hasAnyRole('SUPER', 'CALCULATION')") public ResponseEntity> getPremises(@RequestParam List premissIds) { return ResponseEntity.ok(premisesServices.getPremises(premissIds)); } @PutMapping({"/start", "/start/"}) + @PreAuthorize("hasAnyRole('SUPER', 'CALCULATION')") public ResponseEntity startCalculation(@RequestBody List premiseIds) { premisesServices.startCalculation(premiseIds); return ResponseEntity.ok().build(); @@ -138,45 +147,53 @@ public class PremiseController { * @return A ResponseEntity with the bulk processing status payload. */ @GetMapping({"/status/{processing_id}", "/status/{processing_id}/"}) + @PreAuthorize("hasAnyRole('SUPER', 'CALCULATION')") public ResponseEntity getCalculationStatus(@PathVariable("processing_id") Integer id) { return ResponseEntity.ok(premisesServices.getCalculationStatus(id)); } @PostMapping({"/packaging", "/packaging/"}) + @PreAuthorize("hasAnyRole('SUPER', 'CALCULATION')") public ResponseEntity updatePackaging(@RequestBody PackagingUpdateDTO packagingDTO) { premisesServices.updatePackaging(packagingDTO); return ResponseEntity.ok().build(); } @PostMapping({"/material", "/material/"}) + @PreAuthorize("hasAnyRole('SUPER', 'CALCULATION')") public ResponseEntity updateMaterial(@RequestBody @Valid MaterialUpdateDTO materialUpdateDTO) { premisesServices.updateMaterial(materialUpdateDTO); return ResponseEntity.ok().build(); } @PostMapping({"/price", "/price/"}) + @PreAuthorize("hasAnyRole('SUPER', 'CALCULATION')") public ResponseEntity updatePrice(@RequestBody @Valid PriceUpdateDTO priceUpdateDTO) { premisesServices.updatePrice(priceUpdateDTO); return ResponseEntity.ok().build(); } @PostMapping({"/destination", "/destination/"}) + @PreAuthorize("hasAnyRole('SUPER', 'CALCULATION')") public ResponseEntity> createDestination(@RequestBody @Valid DestinationCreateDTO destinationCreateDTO) { return ResponseEntity.ok(destinationService.createDestination(destinationCreateDTO)); } @PutMapping({"/destination", "/destination/"}) + @PreAuthorize("hasAnyRole('SUPER', 'CALCULATION')") public ResponseEntity>> setDestination(@RequestBody DestinationSetDTO destinationSetDTO) { return ResponseEntity.ok(destinationService.setDestination(destinationSetDTO)); } @GetMapping({"/destination/{id}", "/destination/{id}/"}) + @PreAuthorize("hasAnyRole('SUPER', 'CALCULATION')") public ResponseEntity getDestination(@PathVariable Integer id) { return ResponseEntity.ok(destinationService.getDestination(id)); } @PutMapping({"/destination/{id}", "/destination/{id}/"}) + @PreAuthorize("hasAnyRole('SUPER', 'CALCULATION')") public ResponseEntity updateDestination(@PathVariable @Min(1) Integer id, @RequestBody @Valid DestinationUpdateDTO destinationUpdateDTO) { log.info("Updating destination {}", destinationUpdateDTO); destinationService.updateDestination(id, destinationUpdateDTO); @@ -184,17 +201,20 @@ public class PremiseController { } @DeleteMapping({"/destination/{id}", "/destination/{id}/"}) + @PreAuthorize("hasAnyRole('SUPER', 'CALCULATION')") public ResponseEntity deleteDestination(@PathVariable Integer id) { destinationService.deleteDestinationById(id, false); return ResponseEntity.ok().build(); } @PutMapping({"/supplier", "/supplier/"}) + @PreAuthorize("hasAnyRole('SUPER', 'CALCULATION')") public ResponseEntity> setSupplier(@RequestBody SetDataDTO setSupplierDTO) { return ResponseEntity.ok(changeSupplierService.setSupplier(setSupplierDTO)); } @PutMapping({"/material", "/material/"}) + @PreAuthorize("hasAnyRole('SUPER', 'CALCULATION')") public ResponseEntity> setMaterial(@RequestBody SetDataDTO setMaterialDTO) { return ResponseEntity.ok(changeMaterialService.setMaterial(setMaterialDTO)); } diff --git a/src/main/java/de/avatic/lcc/controller/configuration/CountryController.java b/src/main/java/de/avatic/lcc/controller/configuration/CountryController.java index 5a13325..f33ca5a 100644 --- a/src/main/java/de/avatic/lcc/controller/configuration/CountryController.java +++ b/src/main/java/de/avatic/lcc/controller/configuration/CountryController.java @@ -7,6 +7,8 @@ import de.avatic.lcc.service.access.CountryService; import de.avatic.lcc.util.exception.badrequest.NotFoundException; import jakarta.validation.constraints.Min; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -43,10 +45,13 @@ public class CountryController { * and additional pagination headers */ @GetMapping({"/", ""}) + @PreAuthorize("hasRole('SUPER')") public ResponseEntity> listCountries(@RequestParam(defaultValue = "20") @Min(1) int limit, @RequestParam(defaultValue = "1") @Min(1) int page, @RequestParam(required = false) Optional filter) { + var auth = SecurityContextHolder.getContext().getAuthentication(); + SearchQueryResult countries = countryService.listCountries(filter, page, limit); return ResponseEntity.ok() @@ -65,6 +70,7 @@ public class CountryController { * and additional headers with metadata */ @GetMapping({"/all", "/all/"}) + @PreAuthorize("hasRole('SUPER')") public ResponseEntity> allCountries(@RequestParam(required = false) Optional filter) { SearchQueryResult countries = countryService.listCountries(filter); @@ -86,6 +92,7 @@ public class CountryController { * @return a {@link ResponseEntity} containing a {@link CountryDetailDTO} with the country's details */ @GetMapping({"/{id}", "/{id}/"}) + @PreAuthorize("hasRole('SUPER')") public ResponseEntity getCountryDetails(@PathVariable String id, @RequestParam(name = "property_set", defaultValue = "0", required = false) @Min(0) Integer propertySetId) { return ResponseEntity.ok(countryService.getCountry(id, propertySetId).orElseThrow(() -> new NotFoundException(NotFoundException.NotFoundType.COUNTRY, id))); } diff --git a/src/main/java/de/avatic/lcc/controller/configuration/MaterialController.java b/src/main/java/de/avatic/lcc/controller/configuration/MaterialController.java index 107be63..aca2970 100644 --- a/src/main/java/de/avatic/lcc/controller/configuration/MaterialController.java +++ b/src/main/java/de/avatic/lcc/controller/configuration/MaterialController.java @@ -7,6 +7,7 @@ import de.avatic.lcc.service.access.MaterialService; import jakarta.validation.constraints.Min; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -16,6 +17,7 @@ import java.util.Optional; @RestController @RequestMapping("/api/materials") @Validated +@PreAuthorize("hasRole('SUPER')") public class MaterialController { @@ -36,6 +38,7 @@ public class MaterialController { * X-Total-Count (total elements), X-Page-Count (total pages), and X-Current-Page (current page). */ @GetMapping("/") + @PreAuthorize("hasRole('SUPER')") public ResponseEntity> listMaterials( @RequestParam(defaultValue = "true") String excludeDeprecated, @RequestParam(defaultValue = "20") @Min(1) int limit, @@ -59,6 +62,7 @@ public class MaterialController { * @throws RuntimeException if the material with the given ID is not found. */ @GetMapping("/{id}") + @PreAuthorize("hasRole('SUPER')") public ResponseEntity getMaterialDetails(@PathVariable Integer id) { return ResponseEntity.ok(materialService.getMaterial(id)); } diff --git a/src/main/java/de/avatic/lcc/controller/configuration/NodeController.java b/src/main/java/de/avatic/lcc/controller/configuration/NodeController.java index d455dea..4ad05fb 100644 --- a/src/main/java/de/avatic/lcc/controller/configuration/NodeController.java +++ b/src/main/java/de/avatic/lcc/controller/configuration/NodeController.java @@ -11,8 +11,10 @@ import de.avatic.lcc.service.GeoApiService; import de.avatic.lcc.service.access.NodeService; import de.avatic.lcc.service.access.UserNodeService; import de.avatic.lcc.util.Check; +import jakarta.annotation.security.RolesAllowed; import jakarta.validation.constraints.Min; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -21,6 +23,7 @@ import java.util.List; @RestController @RequestMapping("/api/nodes") @Validated +@PreAuthorize("hasAnyRole('SUPER', 'FREIGHT', 'PACKAGING')") public class NodeController { private final NodeService nodeService; @@ -34,6 +37,7 @@ public class NodeController { } @GetMapping({"","/"}) + @PreAuthorize("hasAnyRole('SUPER', 'FREIGHT', 'PACKAGING')") public ResponseEntity> listNodes(@RequestParam(required = false) String filter, @RequestParam(defaultValue = "1") @Min(1) Integer page, @RequestParam(defaultValue = "20") @Min(1) Integer limit) { nodeService.listNodes(filter, page, limit); @@ -47,32 +51,38 @@ public class NodeController { } @GetMapping({"/search", "/search/"}) + @PreAuthorize("hasAnyRole('SUPER', 'FREIGHT', 'PACKAGING')") public ResponseEntity> searchNodes(@RequestParam(required = false) String filter, @RequestParam(defaultValue = "10") @Min(1) int limit, @RequestParam(name = "node_type", required = false) NodeType nodeType, @RequestParam(name = "include_user_node", defaultValue = "false") boolean includeUserNode) { return ResponseEntity.ok(nodeService.searchNode(filter, limit, nodeType, includeUserNode)); } @GetMapping("/{id}") + @PreAuthorize("hasAnyRole('SUPER', 'FREIGHT', 'PACKAGING')") public ResponseEntity getNode(@PathVariable Integer id) { return ResponseEntity.ok(nodeService.getNode(id)); } @DeleteMapping("/{id}") + @PreAuthorize("hasRole('SUPER')") public ResponseEntity deleteNode(@PathVariable Integer id) { return ResponseEntity.ok(nodeService.deleteNode(id)); } @PutMapping("/{id}") + @PreAuthorize("hasRole('SUPER')") public ResponseEntity updateNode(@PathVariable Integer id, @RequestBody NodeUpdateDTO node) { Check.equals(id, node.getId()); return ResponseEntity.ok(nodeService.updateNode(node)); } @GetMapping("/locate") + @PreAuthorize("hasAnyRole('SUPER', 'FREIGHT', 'PACKAGING')") public ResponseEntity locateNode(@RequestParam String address) { return ResponseEntity.ok(geoApiService.locate(address)); } @PutMapping("/") + @PreAuthorize("hasAnyRole('SUPER', 'CALCULATION')") public ResponseEntity addUserNode(@RequestBody AddUserNodeDTO node) { userNodeService.addUserNode(node); return ResponseEntity.ok().build(); diff --git a/src/main/java/de/avatic/lcc/controller/configuration/PropertyController.java b/src/main/java/de/avatic/lcc/controller/configuration/PropertyController.java index 3c3d201..cd1f113 100644 --- a/src/main/java/de/avatic/lcc/controller/configuration/PropertyController.java +++ b/src/main/java/de/avatic/lcc/controller/configuration/PropertyController.java @@ -8,8 +8,10 @@ import de.avatic.lcc.service.access.CountryService; import de.avatic.lcc.service.access.PropertyService; import de.avatic.lcc.service.configuration.PropertyApprovalService; import de.avatic.lcc.util.exception.badrequest.NotFoundException; +import jakarta.annotation.security.RolesAllowed; import jakarta.validation.constraints.Min; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -46,6 +48,7 @@ public class PropertyController { * @return ResponseEntity containing the list of PropertyDTO objects. */ @GetMapping({"/", ""}) + @PreAuthorize("hasRole('SUPER')") public ResponseEntity> listProperties(@RequestParam(name = "property_set", defaultValue = "0", required = false) @Min(0) Integer propertySetId) { var props = propertyService.listProperties(propertySetId); @@ -64,6 +67,7 @@ public class PropertyController { * @return ResponseEntity containing the list of ValidityPeriodDTO objects. */ @GetMapping({"/periods", "/periods/"}) + @PreAuthorize("hasRole('SUPER')") public ResponseEntity> listPeriods() { return ResponseEntity.ok(propertyService.listPropertySets()); } @@ -75,6 +79,7 @@ public class PropertyController { * @return ResponseEntity indicating the operation status. */ @DeleteMapping({"/periods/{id}", "/periods/{id}/"}) + @PreAuthorize("hasRole('SUPER')") public ResponseEntity invalidatePeriod(@PathVariable @Min(1) Integer id) { propertyService.invalidate(id); return ResponseEntity.ok().build(); @@ -90,6 +95,7 @@ public class PropertyController { * @return ResponseEntity indicating the operation status. */ @PutMapping({"/country/{iso}/{external_mapping_id}", "/country/{iso}/{external_mapping_id}/"}) + @PreAuthorize("hasRole('SUPER')") public ResponseEntity setCountryProperty(@PathVariable("iso") IsoCode isoCode, @PathVariable(name = "external_mapping_id") String mappingId, @RequestBody SetPropertyDTO dto) { countryService.setProperties(isoCode, mappingId, dto.getValue()); return ResponseEntity.ok().build(); @@ -103,6 +109,7 @@ public class PropertyController { * @return ResponseEntity indicating the operation status. */ @PutMapping({"/system/{external_mapping_id}", "/system/{external_mapping_id}/"}) + @PreAuthorize("hasRole('SUPER')") public ResponseEntity setProperties(@PathVariable(name = "external_mapping_id") String externalMappingId, @RequestBody SetPropertyDTO dto) { propertyService.setProperties(externalMappingId, dto.getValue()); return ResponseEntity.ok().build(); @@ -114,6 +121,7 @@ public class PropertyController { * @return ResponseEntity containing true if drafts are present, false otherwise. */ @GetMapping({"/staged_changes", "/staged_changes/"}) + @PreAuthorize("hasRole('SUPER')") public ResponseEntity checkPropertiesDrafts() { return ResponseEntity.ok(propertyApprovalService.hasPropertiesDraft()); } @@ -124,6 +132,7 @@ public class PropertyController { * @return ResponseEntity indicating the operation status. */ @PutMapping({"/staged_changes", "/staged_changes/"}) + @PreAuthorize("hasRole('SUPER')") public ResponseEntity approvePropertiesDrafts() { propertyApprovalService.applyDraft(); return ResponseEntity.ok().build(); diff --git a/src/main/java/de/avatic/lcc/controller/configuration/RateController.java b/src/main/java/de/avatic/lcc/controller/configuration/RateController.java index f67c4b7..1abcad9 100644 --- a/src/main/java/de/avatic/lcc/controller/configuration/RateController.java +++ b/src/main/java/de/avatic/lcc/controller/configuration/RateController.java @@ -9,9 +9,11 @@ import de.avatic.lcc.service.access.ContainerRateService; import de.avatic.lcc.service.access.MatrixRateService; import de.avatic.lcc.service.configuration.RateApprovalService; import de.avatic.lcc.service.access.ValidityPeriodService; +import jakarta.annotation.security.RolesAllowed; import jakarta.validation.constraints.Min; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -53,6 +55,7 @@ public class RateController { * @return a ResponseEntity containing the list of container rates and additional pagination headers */ @GetMapping({"/container", "/container/" }) + @PreAuthorize("hasAnyRole('SUPER', 'FREIGHT')") public ResponseEntity> listContainerRates( @RequestParam(defaultValue = "") String filter, @RequestParam(defaultValue = "20") @Min(1) int limit, @@ -86,6 +89,7 @@ public class RateController { * @return a ResponseEntity containing the ContainerRateDTO with the rate information of the specified container */ @GetMapping({"/container/{id}", "/container/{id}/"}) + @PreAuthorize("hasAnyRole('SUPER', 'FREIGHT')") public ResponseEntity getContainerRate(@PathVariable Integer id) { return ResponseEntity.ok(containerRateService.getContainerRate(id)); } @@ -101,6 +105,7 @@ public class RateController { * including pagination headers. */ @GetMapping({"/matrix","/matrix/"}) + @PreAuthorize("hasAnyRole('SUPER', 'FREIGHT')") public ResponseEntity> listMatrixRates( @RequestParam(defaultValue = "") String filter, @RequestParam(defaultValue = "20") @Min(1) int limit, @@ -134,6 +139,7 @@ public class RateController { * @return a ResponseEntity containing the MatrixRateDTO for the specified ID */ @GetMapping({"/matrix/{id}", "/matrix/{id}/"}) + @PreAuthorize("hasAnyRole('SUPER', 'FREIGHT')") public ResponseEntity getMatrixRate(@PathVariable Integer id) { return ResponseEntity.ok(matrixRateService.getRate(id)); } @@ -144,6 +150,7 @@ public class RateController { * @return ResponseEntity containing the list of ValidityPeriodDTO objects. */ @GetMapping({"/periods", "/periods/"}) + @PreAuthorize("hasAnyRole('SUPER', 'FREIGHT')") public ResponseEntity> listPeriods() { return ResponseEntity.ok(validityPeriodService.listPeriods()); } @@ -155,6 +162,7 @@ public class RateController { * @return ResponseEntity indicating the operation status. */ @DeleteMapping({"/periods/{id}", "/periods/{id}/"}) + @PreAuthorize("hasAnyRole('SUPER', 'FREIGHT')") public ResponseEntity invalidatePeriod(@PathVariable Integer id) { validityPeriodService.invalidate(id); return ResponseEntity.ok().build(); @@ -167,6 +175,7 @@ public class RateController { * whether rate drafts exist (true) or not (false). */ @GetMapping( {"/staged_changes", "/staged_changes/"}) + @PreAuthorize("hasAnyRole('SUPER', 'FREIGHT')") public ResponseEntity checkRateDrafts() { return ResponseEntity.ok(rateApprovalService.getStagedRateDTO()); } @@ -177,6 +186,7 @@ public class RateController { * @return ResponseEntity with HTTP 200 status if the operation is successful. */ @PutMapping({"/staged_changes", "/staged_changes/"}) + @PreAuthorize("hasAnyRole('SUPER', 'FREIGHT')") public ResponseEntity approveRateDrafts() { rateApprovalService.approveRateDrafts(); return ResponseEntity.ok().build(); diff --git a/src/main/java/de/avatic/lcc/controller/maps/AzureMapsController.java b/src/main/java/de/avatic/lcc/controller/maps/AzureMapsController.java index 7ee7c42..b2fd4f3 100644 --- a/src/main/java/de/avatic/lcc/controller/maps/AzureMapsController.java +++ b/src/main/java/de/avatic/lcc/controller/maps/AzureMapsController.java @@ -1,5 +1,6 @@ package de.avatic.lcc.controller.maps; +import jakarta.annotation.security.RolesAllowed; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.*; import org.springframework.web.bind.annotation.*; @@ -15,6 +16,7 @@ import java.time.Instant; @RestController @RequestMapping("/api/maps") @CrossOrigin(origins = {"http://localhost:3000", "http://localhost:8080", "https://yourdomain.com"}) +@RolesAllowed({"ROLE_SUPER", "ROLE_CALCULATION"}) public class AzureMapsController { private static final Logger logger = LoggerFactory.getLogger(AzureMapsController.class); diff --git a/src/main/java/de/avatic/lcc/controller/report/ReportingController.java b/src/main/java/de/avatic/lcc/controller/report/ReportingController.java index 316a823..445babe 100644 --- a/src/main/java/de/avatic/lcc/controller/report/ReportingController.java +++ b/src/main/java/de/avatic/lcc/controller/report/ReportingController.java @@ -4,6 +4,7 @@ import de.avatic.lcc.dto.generic.NodeDTO; import de.avatic.lcc.dto.report.ReportDTO; import de.avatic.lcc.service.report.ExcelReportingService; import de.avatic.lcc.service.report.ReportingService; +import jakarta.annotation.security.RolesAllowed; import org.springframework.core.io.InputStreamResource; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; diff --git a/src/main/java/de/avatic/lcc/controller/users/ActiveUserController.java b/src/main/java/de/avatic/lcc/controller/users/ActiveUserController.java index a28a314..0ea86af 100644 --- a/src/main/java/de/avatic/lcc/controller/users/ActiveUserController.java +++ b/src/main/java/de/avatic/lcc/controller/users/ActiveUserController.java @@ -1,12 +1,10 @@ package de.avatic.lcc.controller.users; -import de.avatic.lcc.config.LccOidcUser; import de.avatic.lcc.dto.users.UserDTO; import de.avatic.lcc.repositories.users.UserRepository; import de.avatic.lcc.service.transformer.users.UserTransformer; +import de.avatic.lcc.service.users.AuthorizationService; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -16,24 +14,16 @@ import org.springframework.web.bind.annotation.RestController; public class ActiveUserController { - private final UserTransformer userTransformer; - private final UserRepository userRepository; + private final AuthorizationService authorizationService; - public ActiveUserController(UserTransformer userTransformer, UserRepository userRepository) { - - this.userTransformer = userTransformer; - this.userRepository = userRepository; + public ActiveUserController(AuthorizationService authorizationService) { + this.authorizationService = authorizationService; } @GetMapping public ResponseEntity getActiveUser() { - - Authentication auth = SecurityContextHolder.getContext().getAuthentication(); - if(auth.getPrincipal() instanceof LccOidcUser) { - LccOidcUser user = (LccOidcUser) auth.getPrincipal(); - return ResponseEntity.ok(userTransformer.toUserDTO(userRepository.getById(user.getSqlUserId()))); - } + var user = authorizationService.getActiveUser(); + if (user != null) return ResponseEntity.ok(user); return ResponseEntity.internalServerError().build(); } - } diff --git a/src/main/java/de/avatic/lcc/service/access/DestinationService.java b/src/main/java/de/avatic/lcc/service/access/DestinationService.java index b27615c..dde17cf 100644 --- a/src/main/java/de/avatic/lcc/service/access/DestinationService.java +++ b/src/main/java/de/avatic/lcc/service/access/DestinationService.java @@ -14,6 +14,7 @@ import de.avatic.lcc.repositories.properties.PropertyRepository; 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; @@ -39,8 +40,9 @@ public class DestinationService { 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) { + 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) { this.destinationRepository = destinationRepository; this.destinationTransformer = destinationTransformer; this.routeRepository = routeRepository; @@ -52,6 +54,7 @@ public class DestinationService { this.userNodeRepository = userNodeRepository; this.propertyRepository = propertyRepository; this.propertyService = propertyService; + this.authorizationService = authorizationService; } @Transactional @@ -115,7 +118,7 @@ public class DestinationService { @Transactional public Map createDestination(DestinationCreateDTO dto) { - Integer userId = 1; //TODO get user id. + Integer userId = authorizationService.getUserId(); var existingDestinations = destinationRepository.getByPremiseIdsAndNodeId(dto.getPremiseId(), dto.getDestinationNodeId(), userId); @@ -146,7 +149,7 @@ public class DestinationService { @Transactional public void updateDestination(Integer id, DestinationUpdateDTO destinationUpdateDTO) { //todo check authorization - Integer userId = 1; + Integer userId = authorizationService.getUserId(); destinationRepository.checkOwner(id, userId); @@ -290,10 +293,10 @@ public class DestinationService { @Transactional public void deleteDestinationById(Integer id, boolean deleteRoutesOnly) { //todo check authorization - Integer userId = 1; + Integer userId = authorizationService.getUserId(); Optional ownerId = destinationRepository.getOwnerIdById(id); - if (userId == 1 /* todo remove */ || ownerId.isPresent() && ownerId.get().equals(userId)) { + if (ownerId.isPresent() && ownerId.get().equals(userId)) { List routes = routeRepository.getByDestinationId(id); for (var route : routes) { diff --git a/src/main/java/de/avatic/lcc/service/access/NodeService.java b/src/main/java/de/avatic/lcc/service/access/NodeService.java index f0cc764..02fd7a4 100644 --- a/src/main/java/de/avatic/lcc/service/access/NodeService.java +++ b/src/main/java/de/avatic/lcc/service/access/NodeService.java @@ -12,6 +12,7 @@ import de.avatic.lcc.repositories.users.UserNodeRepository; import de.avatic.lcc.service.transformer.nodes.NodeUpdateDTOTransformer; import de.avatic.lcc.service.transformer.nodes.NodeDetailTransformer; import de.avatic.lcc.service.transformer.generic.NodeTransformer; +import de.avatic.lcc.service.users.AuthorizationService; import de.avatic.lcc.util.exception.badrequest.NodeNotFoundException; import de.avatic.lcc.util.exception.badrequest.NotFoundException; import org.springframework.stereotype.Service; @@ -31,14 +32,15 @@ public class NodeService { private final NodeDetailTransformer nodeDetailTransformer; private final NodeUpdateDTOTransformer nodeUpdateDTOTransformer; private final UserNodeRepository userNodeRepository; + private final AuthorizationService authorizationService; - public NodeService(NodeRepository nodeRepository, NodeTransformer nodeTransformer, NodeDetailTransformer nodeDetailTransformer, NodeUpdateDTOTransformer nodeUpdateDTOTransformer, UserNodeRepository userNodeRepository) { + public NodeService(NodeRepository nodeRepository, NodeTransformer nodeTransformer, NodeDetailTransformer nodeDetailTransformer, NodeUpdateDTOTransformer nodeUpdateDTOTransformer, UserNodeRepository userNodeRepository, AuthorizationService authorizationService) { this.nodeRepository = nodeRepository; this.nodeTransformer = nodeTransformer; this.nodeDetailTransformer = nodeDetailTransformer; this.nodeUpdateDTOTransformer = nodeUpdateDTOTransformer; this.userNodeRepository = userNodeRepository; - + this.authorizationService = authorizationService; } /** @@ -108,7 +110,7 @@ public class NodeService { * @return a list of {@link NodeDTO} objects representing the search results. */ public List searchNode(String filter, int limit, NodeType nodeType, boolean includeUserNode) { List nodes = new ArrayList<>(); - int userId = 1; //TODO get current user's id + int userId = authorizationService.getUserId(); if( includeUserNode && NodeType.SOURCE.equals(nodeType)) { nodes.addAll(userNodeRepository.searchNode(filter, limit, userId, true).stream().map(nodeTransformer::toNodeDTO).toList()); diff --git a/src/main/java/de/avatic/lcc/service/access/PremisesService.java b/src/main/java/de/avatic/lcc/service/access/PremisesService.java index 370d6b6..15b5176 100644 --- a/src/main/java/de/avatic/lcc/service/access/PremisesService.java +++ b/src/main/java/de/avatic/lcc/service/access/PremisesService.java @@ -25,6 +25,7 @@ import de.avatic.lcc.service.precalculation.PostCalculationCheckService; import de.avatic.lcc.service.precalculation.PreCalculationCheckService; import de.avatic.lcc.service.transformer.generic.DimensionTransformer; import de.avatic.lcc.service.transformer.premise.PremiseTransformer; +import de.avatic.lcc.service.users.AuthorizationService; import de.avatic.lcc.util.exception.base.InternalErrorException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -61,8 +62,9 @@ public class PremisesService { private final CalculationJobDestinationRepository calculationJobDestinationRepository; private final CalculationJobRouteSectionRepository calculationJobRouteSectionRepository; private final PostCalculationCheckService postCalculationCheckService; + private final AuthorizationService authorizationService; - public PremisesService(PremiseRepository premiseRepository, PremiseTransformer premiseTransformer, DimensionTransformer dimensionTransformer, DestinationService destinationService, CalculationJobRepository calculationJobRepository, PropertySetRepository propertySetRepository, ValidityPeriodRepository validityPeriodRepository, CalculationStatusService calculationStatusService, CalculationExecutionService calculationExecutionService, PreCalculationCheckService preCalculationCheckService, CalculationJobDestinationRepository calculationJobDestinationRepository, CalculationJobRouteSectionRepository calculationJobRouteSectionRepository, PostCalculationCheckService postCalculationCheckService) { + public PremisesService(PremiseRepository premiseRepository, PremiseTransformer premiseTransformer, DimensionTransformer dimensionTransformer, DestinationService destinationService, CalculationJobRepository calculationJobRepository, PropertySetRepository propertySetRepository, ValidityPeriodRepository validityPeriodRepository, CalculationStatusService calculationStatusService, CalculationExecutionService calculationExecutionService, PreCalculationCheckService preCalculationCheckService, CalculationJobDestinationRepository calculationJobDestinationRepository, CalculationJobRouteSectionRepository calculationJobRouteSectionRepository, PostCalculationCheckService postCalculationCheckService, AuthorizationService authorizationService) { this.premiseRepository = premiseRepository; this.premiseTransformer = premiseTransformer; this.dimensionTransformer = dimensionTransformer; @@ -77,27 +79,19 @@ public class PremisesService { this.calculationJobDestinationRepository = calculationJobDestinationRepository; this.calculationJobRouteSectionRepository = calculationJobRouteSectionRepository; this.postCalculationCheckService = postCalculationCheckService; + this.authorizationService = authorizationService; } @Transactional(readOnly = true) public SearchQueryResult listPremises(String filter, Integer page, Integer limit, Integer userId, Boolean deleted, Boolean archived, Boolean done) { - - //TODO check if user is admin. if not: use the user_id of currently authorized user. - var admin = false; - - //TODO use actual user. - userId = 1; - - - + var admin = authorizationService.isSuper(); + userId = authorizationService.getUserId(); return SearchQueryResult.map(premiseRepository.listPremises(filter, new SearchQueryPagination(page, limit), userId, deleted, archived, done), admin ? premiseTransformer::toPremiseDTOWithUserInfo : premiseTransformer::toPremiseDTO); - } @Transactional(readOnly = true) public List getPremises(List premiseIds) { - //TODO use real user - var userId = 1; + var userId = authorizationService.getUserId(); premiseRepository.checkOwner(premiseIds, userId); return premiseRepository.getPremisesById(premiseIds).stream().filter(p -> p.getState().equals(PremiseState.DRAFT)).map(premiseTransformer::toPremiseDetailDTO).toList(); @@ -106,7 +100,7 @@ public class PremisesService { public void startCalculation(List premises) { - var userId = 1; // TODO get current user id + var userId = authorizationService.getUserId(); // todo check if user is allowed to schedule this @@ -182,7 +176,7 @@ public class PremisesService { public void updatePackaging(PackagingUpdateDTO packagingDTO) { //TODO check values. and return errors if needed - var userId = 1; // todo get id from current user. + var userId = authorizationService.getUserId(); premiseRepository.checkOwner(packagingDTO.getPremiseIds(), userId); var dimensions = packagingDTO.getDimensions() == null ? null : dimensionTransformer.toDimensionEntity(packagingDTO.getDimensions()); @@ -193,7 +187,7 @@ public class PremisesService { public void updateMaterial(MaterialUpdateDTO materialUpdateDTO) { //TODO check values. and return errors if needed - var userId = 1; // todo get id from current user. + var userId = authorizationService.getUserId(); premiseRepository.checkOwner(materialUpdateDTO.getPremiseIds(), userId); var tariffRate = materialUpdateDTO.getTariffRate() == null ? null : BigDecimal.valueOf(materialUpdateDTO.getTariffRate().doubleValue()); @@ -203,7 +197,7 @@ public class PremisesService { public void updatePrice(PriceUpdateDTO priceUpdateDTO) { //TODO check values. and return errors if needed - var userId = 1; // todo get id from current user. + var userId = authorizationService.getUserId(); premiseRepository.checkOwner(priceUpdateDTO.getPremiseIds(), userId); var price = priceUpdateDTO.getPrice() == null ? null : BigDecimal.valueOf(priceUpdateDTO.getPrice().doubleValue()); @@ -214,8 +208,7 @@ public class PremisesService { @Transactional public void delete(List premiseIds) { - //TODO check authorization - var userId = 1; + var userId = authorizationService.getUserId(); premiseRepository.checkOwner(premiseIds, userId); // only delete drafts. @@ -230,8 +223,7 @@ public class PremisesService { } public void archive(List premiseIds) { - //TODO check authorization - var userId = 1; + var userId = authorizationService.getUserId(); premiseRepository.checkOwner(premiseIds, userId); // only archive completed. diff --git a/src/main/java/de/avatic/lcc/service/access/UserNodeService.java b/src/main/java/de/avatic/lcc/service/access/UserNodeService.java index 7e0d7cc..14cff17 100644 --- a/src/main/java/de/avatic/lcc/service/access/UserNodeService.java +++ b/src/main/java/de/avatic/lcc/service/access/UserNodeService.java @@ -5,6 +5,7 @@ import de.avatic.lcc.model.country.IsoCode; import de.avatic.lcc.model.nodes.Node; import de.avatic.lcc.repositories.country.CountryRepository; import de.avatic.lcc.repositories.users.UserNodeRepository; +import de.avatic.lcc.service.users.AuthorizationService; import de.avatic.lcc.util.exception.badrequest.InvalidArgumentException; import de.avatic.lcc.util.exception.badrequest.NotFoundException; import org.springframework.stereotype.Service; @@ -15,10 +16,12 @@ import java.math.BigDecimal; public class UserNodeService { private final CountryRepository countryRepository; private final UserNodeRepository userNodeRepository; + private final AuthorizationService authorizationService; - public UserNodeService(CountryRepository countryRepository, UserNodeRepository userNodeRepository) { + public UserNodeService(CountryRepository countryRepository, UserNodeRepository userNodeRepository, AuthorizationService authorizationService) { this.countryRepository = countryRepository; this.userNodeRepository = userNodeRepository; + this.authorizationService = authorizationService; } public Node getUserNode(Integer id) { @@ -35,7 +38,7 @@ public class UserNodeService { public void addUserNode(AddUserNodeDTO dto) { // TODO: get real user id. - var userId = 1; + var userId = authorizationService.getUserId(); Node node = new Node(); diff --git a/src/main/java/de/avatic/lcc/service/bulk/BulkOperationService.java b/src/main/java/de/avatic/lcc/service/bulk/BulkOperationService.java index 32270e6..756e4b8 100644 --- a/src/main/java/de/avatic/lcc/service/bulk/BulkOperationService.java +++ b/src/main/java/de/avatic/lcc/service/bulk/BulkOperationService.java @@ -8,6 +8,7 @@ import de.avatic.lcc.model.bulk.BulkOperation; import de.avatic.lcc.repositories.bulk.BulkOperationRepository; 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.InternalErrorException; import org.springframework.stereotype.Service; @@ -24,18 +25,20 @@ public class BulkOperationService { private final ValidityPeriodRepository validityPeriodRepository; private final BulkOperationTransformer bulkOperationTransformer; private final BulkOperationExecutionService bulkOperationExecutionService; + private final AuthorizationService authorizationService; - public BulkOperationService(BulkOperationRepository bulkOperationRepository, ValidityPeriodRepository validityPeriodRepository, BulkOperationTransformer bulkOperationTransformer, BulkOperationExecutionService bulkOperationExecutionService) { + public BulkOperationService(BulkOperationRepository bulkOperationRepository, ValidityPeriodRepository validityPeriodRepository, BulkOperationTransformer bulkOperationTransformer, BulkOperationExecutionService bulkOperationExecutionService, AuthorizationService authorizationService) { this.bulkOperationRepository = bulkOperationRepository; this.validityPeriodRepository = validityPeriodRepository; this.bulkOperationTransformer = bulkOperationTransformer; this.bulkOperationExecutionService = bulkOperationExecutionService; + this.authorizationService = authorizationService; } public void processFileImport(BulkFileType fileType, MultipartFile file) { - int userId = 1; //TODO actual user + var userId = authorizationService.getUserId(); String contentType = file.getContentType(); if (!"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet".equals(contentType) && @@ -64,7 +67,7 @@ public class BulkOperationService { } public void processFileExport(BulkFileType type, Integer validityPeriodId) { - int userId = 1; //TODO set actual user id + var userId = authorizationService.getUserId(); BulkOperation op = new BulkOperation(); op.setUserId(userId); @@ -82,7 +85,7 @@ public class BulkOperationService { } public List getStatus() { - int userId = 1; //TODO actual user + var userId = authorizationService.getUserId(); return bulkOperationRepository.listByUserId(userId).stream().map(bulkOperationTransformer::toBulkOperationDTO).toList(); } diff --git a/src/main/java/de/avatic/lcc/service/calculation/ChangeMaterialService.java b/src/main/java/de/avatic/lcc/service/calculation/ChangeMaterialService.java index 52727bd..f4a42bd 100644 --- a/src/main/java/de/avatic/lcc/service/calculation/ChangeMaterialService.java +++ b/src/main/java/de/avatic/lcc/service/calculation/ChangeMaterialService.java @@ -15,6 +15,7 @@ import de.avatic.lcc.repositories.premise.PremiseRepository; import de.avatic.lcc.repositories.users.UserNodeRepository; import de.avatic.lcc.service.CustomApiService; import de.avatic.lcc.service.access.PremisesService; +import de.avatic.lcc.service.users.AuthorizationService; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -32,8 +33,9 @@ public class ChangeMaterialService { private final PackagingDimensionRepository packagingDimensionRepository; private final PackagingPropertiesRepository packagingPropertiesRepository; private final MaterialRepository materialRepository; + private final AuthorizationService authorizationService; - public ChangeMaterialService(PremiseRepository premiseRepository, PremisesService premisesService, CustomApiService customApiService, NodeRepository nodeRepository, UserNodeRepository userNodeRepository, PackagingRepository packagingRepository, PackagingDimensionRepository packagingDimensionRepository, PackagingPropertiesRepository packagingPropertiesRepository, MaterialRepository materialRepository) { + public ChangeMaterialService(PremiseRepository premiseRepository, PremisesService premisesService, CustomApiService customApiService, NodeRepository nodeRepository, UserNodeRepository userNodeRepository, PackagingRepository packagingRepository, PackagingDimensionRepository packagingDimensionRepository, PackagingPropertiesRepository packagingPropertiesRepository, MaterialRepository materialRepository, AuthorizationService authorizationService) { this.premiseRepository = premiseRepository; this.premisesService = premisesService; this.customApiService = customApiService; @@ -43,11 +45,12 @@ public class ChangeMaterialService { this.packagingDimensionRepository = packagingDimensionRepository; this.packagingPropertiesRepository = packagingPropertiesRepository; this.materialRepository = materialRepository; + this.authorizationService = authorizationService; } @Transactional public List setMaterial(SetDataDTO dto) { - Integer userId = 1; /* TODO get user id */ + var userId = authorizationService.getUserId(); Integer materialId = dto.getMaterialId(); List premiseIds = dto.getPremiseId(); diff --git a/src/main/java/de/avatic/lcc/service/calculation/ChangeSupplierService.java b/src/main/java/de/avatic/lcc/service/calculation/ChangeSupplierService.java index 258445b..4d4ff25 100644 --- a/src/main/java/de/avatic/lcc/service/calculation/ChangeSupplierService.java +++ b/src/main/java/de/avatic/lcc/service/calculation/ChangeSupplierService.java @@ -17,6 +17,7 @@ import de.avatic.lcc.repositories.users.UserNodeRepository; import de.avatic.lcc.service.CustomApiService; import de.avatic.lcc.service.access.DestinationService; import de.avatic.lcc.service.access.PremisesService; +import de.avatic.lcc.service.users.AuthorizationService; import de.avatic.lcc.util.exception.badrequest.InvalidArgumentException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -40,8 +41,9 @@ public class ChangeSupplierService { private final RouteRepository routeRepository; private final RouteSectionRepository routeSectionRepository; private final RouteNodeRepository routeNodeRepository; + private final AuthorizationService authorizationService; - public ChangeSupplierService(PremiseRepository premiseRepository, DestinationService destinationService, RoutingService routingService, PremisesService premisesService, CustomApiService customApiService, NodeRepository nodeRepository, UserNodeRepository userNodeRepository, PackagingRepository packagingRepository, PackagingDimensionRepository packagingDimensionRepository, PackagingPropertiesRepository packagingPropertiesRepository, DestinationRepository destinationRepository, RouteRepository routeRepository, RouteSectionRepository routeSectionRepository, RouteNodeRepository routeNodeRepository) { + public ChangeSupplierService(PremiseRepository premiseRepository, DestinationService destinationService, RoutingService routingService, PremisesService premisesService, CustomApiService customApiService, NodeRepository nodeRepository, UserNodeRepository userNodeRepository, PackagingRepository packagingRepository, PackagingDimensionRepository packagingDimensionRepository, PackagingPropertiesRepository packagingPropertiesRepository, DestinationRepository destinationRepository, RouteRepository routeRepository, RouteSectionRepository routeSectionRepository, RouteNodeRepository routeNodeRepository, AuthorizationService authorizationService) { this.premiseRepository = premiseRepository; this.destinationService = destinationService; this.routingService = routingService; @@ -56,13 +58,14 @@ public class ChangeSupplierService { this.routeRepository = routeRepository; this.routeSectionRepository = routeSectionRepository; this.routeNodeRepository = routeNodeRepository; + this.authorizationService = authorizationService; } @Transactional public List setSupplier(SetDataDTO dto) { - Integer userId = 1; // todo get current user; + var userId = authorizationService.getUserId(); Integer supplierNodeId = dto.getSupplierNodeId(); List premiseIds = dto.getPremiseId(); diff --git a/src/main/java/de/avatic/lcc/service/calculation/PremiseCreationService.java b/src/main/java/de/avatic/lcc/service/calculation/PremiseCreationService.java index 21d3b14..5a69053 100644 --- a/src/main/java/de/avatic/lcc/service/calculation/PremiseCreationService.java +++ b/src/main/java/de/avatic/lcc/service/calculation/PremiseCreationService.java @@ -17,6 +17,7 @@ import de.avatic.lcc.service.CustomApiService; import de.avatic.lcc.service.access.DestinationService; import de.avatic.lcc.service.transformer.generic.DimensionTransformer; import de.avatic.lcc.service.transformer.premise.PremiseTransformer; +import de.avatic.lcc.service.users.AuthorizationService; import de.avatic.lcc.util.exception.badrequest.InvalidArgumentException; import de.avatic.lcc.util.exception.badrequest.NotFoundException; import de.avatic.lcc.util.exception.base.ForbiddenException; @@ -40,8 +41,9 @@ public class PremiseCreationService { private final PackagingDimensionRepository packagingDimensionRepository; private final PackagingPropertiesRepository packagingPropertiesRepository; private final CustomApiService customApiService; + private final AuthorizationService authorizationService; - public PremiseCreationService(PremiseRepository premiseRepository, PremiseTransformer premiseTransformer, DestinationService destinationService, UserNodeRepository userNodeRepository, NodeRepository nodeRepository, MaterialRepository materialRepository, DimensionTransformer dimensionTransformer, PackagingRepository packagingRepository, PackagingDimensionRepository packagingDimensionRepository, PackagingPropertiesRepository packagingPropertiesRepository, CustomApiService customApiService) { + public PremiseCreationService(PremiseRepository premiseRepository, PremiseTransformer premiseTransformer, DestinationService destinationService, UserNodeRepository userNodeRepository, NodeRepository nodeRepository, MaterialRepository materialRepository, DimensionTransformer dimensionTransformer, PackagingRepository packagingRepository, PackagingDimensionRepository packagingDimensionRepository, PackagingPropertiesRepository packagingPropertiesRepository, CustomApiService customApiService, AuthorizationService authorizationService) { this.premiseRepository = premiseRepository; this.premiseTransformer = premiseTransformer; this.destinationService = destinationService; @@ -53,11 +55,12 @@ public class PremiseCreationService { this.packagingDimensionRepository = packagingDimensionRepository; this.packagingPropertiesRepository = packagingPropertiesRepository; this.customApiService = customApiService; + this.authorizationService = authorizationService; } @Transactional public List createPremises(List materialIds, List supplierIds, List userSupplierIds, boolean createEmpty) { - Integer userId = 1; //TODO get user id + var userId = authorizationService.getUserId(); userNodeRepository.checkOwner(userSupplierIds, userId); /* Build all resulting premises */ diff --git a/src/main/java/de/avatic/lcc/service/calculation/PremiseSearchStringAnalyzerService.java b/src/main/java/de/avatic/lcc/service/calculation/PremiseSearchStringAnalyzerService.java index 67bdb8e..5a450c1 100644 --- a/src/main/java/de/avatic/lcc/service/calculation/PremiseSearchStringAnalyzerService.java +++ b/src/main/java/de/avatic/lcc/service/calculation/PremiseSearchStringAnalyzerService.java @@ -8,6 +8,7 @@ import de.avatic.lcc.repositories.premise.PremiseRepository; import de.avatic.lcc.repositories.users.UserNodeRepository; import de.avatic.lcc.service.transformer.generic.MaterialTransformer; import de.avatic.lcc.service.transformer.generic.NodeTransformer; +import de.avatic.lcc.service.users.AuthorizationService; import org.springframework.stereotype.Service; import java.util.Collection; @@ -33,6 +34,7 @@ public class PremiseSearchStringAnalyzerService { private final NodeTransformer nodeTransformer; private final MaterialTransformer materialTransformer; private final UserNodeRepository userNodeRepository; + private final AuthorizationService authorizationService; /** * Constructor for the PremiseSearchStringAnalyzerService, initializing the required repositories @@ -44,13 +46,14 @@ public class PremiseSearchStringAnalyzerService { * @param nodeTransformer Transformer for mapping database node entities (suppliers) to DTOs. * @param materialTransformer Transformer for mapping database material entities to DTOs. */ - public PremiseSearchStringAnalyzerService(MaterialRepository materialRepository, NodeRepository nodeRepository, PremiseRepository premiseRepository, NodeTransformer nodeTransformer, MaterialTransformer materialTransformer, UserNodeRepository userNodeRepository) { + public PremiseSearchStringAnalyzerService(MaterialRepository materialRepository, NodeRepository nodeRepository, PremiseRepository premiseRepository, NodeTransformer nodeTransformer, MaterialTransformer materialTransformer, UserNodeRepository userNodeRepository, AuthorizationService authorizationService) { this.materialRepository = materialRepository; this.nodeRepository = nodeRepository; this.premiseRepository = premiseRepository; this.nodeTransformer = nodeTransformer; this.materialTransformer = materialTransformer; this.userNodeRepository = userNodeRepository; + this.authorizationService = authorizationService; } /** @@ -67,7 +70,7 @@ public class PremiseSearchStringAnalyzerService { */ public PremiseSearchResultDTO findMaterialAndSuppliers(String search) { - var userId = 1; //TODO get actual user id. + var userId = authorizationService.getUserId(); List material = materialRepository.getByPartNumbers(findPartNumbers(search)); List materialIds = material.stream().map(Material::getId).toList(); diff --git a/src/main/java/de/avatic/lcc/service/transformer/error/SysErrorTransformer.java b/src/main/java/de/avatic/lcc/service/transformer/error/SysErrorTransformer.java index 3f3640d..b43186d 100644 --- a/src/main/java/de/avatic/lcc/service/transformer/error/SysErrorTransformer.java +++ b/src/main/java/de/avatic/lcc/service/transformer/error/SysErrorTransformer.java @@ -6,6 +6,7 @@ import de.avatic.lcc.dto.error.FrontendErrorDTO; import de.avatic.lcc.model.error.SysError; import de.avatic.lcc.model.error.SysErrorTraceItem; import de.avatic.lcc.model.error.SysErrorType; +import de.avatic.lcc.service.users.AuthorizationService; import org.springframework.stereotype.Service; import java.util.ArrayList; @@ -19,9 +20,14 @@ public class SysErrorTransformer { private static final String TRACE_REGEX = "at\\s+(?:async\\s+)?(?:(.+?)\\s+)?\\(([^?]+(?:\\?[^:]*)?):(\\d+):\\d+\\)"; private static final Pattern TRACE_REGEX_PATTERN = Pattern.compile(TRACE_REGEX); + private final AuthorizationService authorizationService; + + public SysErrorTransformer(AuthorizationService authorizationService) { + this.authorizationService = authorizationService; + } public SysError toSysErrorEntity(FrontendErrorDTO frontendErrorDTO) { - int userId = 1; //TODO use actual user + var userId = authorizationService.getUserId(); SysError entity = new SysError(); diff --git a/src/main/java/de/avatic/lcc/service/users/AuthorizationService.java b/src/main/java/de/avatic/lcc/service/users/AuthorizationService.java new file mode 100644 index 0000000..f858398 --- /dev/null +++ b/src/main/java/de/avatic/lcc/service/users/AuthorizationService.java @@ -0,0 +1,46 @@ +package de.avatic.lcc.service.users; + +import de.avatic.lcc.config.LccOidcUser; +import de.avatic.lcc.dto.users.UserDTO; +import de.avatic.lcc.model.users.User; +import de.avatic.lcc.repositories.users.UserRepository; +import de.avatic.lcc.service.transformer.users.UserTransformer; +import de.avatic.lcc.util.exception.base.ForbiddenException; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Service; + +@Service +public class AuthorizationService { + + + private final UserRepository userRepository; + private final UserTransformer userTransformer; + + public AuthorizationService(UserRepository userRepository, UserTransformer userTransformer) { + this.userRepository = userRepository; + this.userTransformer = userTransformer; + } + + public UserDTO getActiveUser() { + var id = getUserId(); + return id == null ? null : userTransformer.toUserDTO(userRepository.getById(id)); + } + + public Integer getUserId() { + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + if (auth.getPrincipal() instanceof LccOidcUser user) { + return user.getSqlUserId(); + } + throw new ForbiddenException("No user found"); + } + + public boolean isSuper() { + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + if (auth.getPrincipal() instanceof LccOidcUser user) { + return user.getAuthorities().stream().anyMatch(authority -> authority.getAuthority().equals("ROLE_SUPER")); + } + + throw new ForbiddenException("No user found"); + } +}