From 1bd0c94a4ddc9f4751df53f8fcbea2856e5c2bd8 Mon Sep 17 00:00:00 2001 From: FyloZ Date: Sun, 4 Apr 2021 21:02:53 -0400 Subject: [PATCH 1/5] =?UTF-8?q?Am=C3=A9lioration=20de=20l'impl=C3=A9mentat?= =?UTF-8?q?ion=20des=20controlleurs.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/WebSecurityConfig.kt | 116 +---------- .../model/AccountModel.kt | 23 +-- .../rest/AccountControllers.kt | 180 ++++++++++-------- .../rest/CompanyController.kt | 41 +++- .../rest/InventoryController.kt | 14 +- .../rest/MaterialController.kt | 119 ++++++------ .../rest/MaterialTypeController.kt | 43 ++++- .../rest/RecipeController.kt | 98 +++++++--- .../rest/RestApiController.kt | 87 --------- .../colorrecipesexplorer/rest/RestUtils.kt | 58 ++++++ .../service/AccountService.kt | 4 +- .../colorrecipesexplorer/service/Service.kt | 11 +- .../service/AccountsServiceTest.kt | 1 - 13 files changed, 388 insertions(+), 407 deletions(-) delete mode 100644 src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/RestApiController.kt create mode 100644 src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/RestUtils.kt diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/WebSecurityConfig.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/WebSecurityConfig.kt index 0e793a3..45422f6 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/WebSecurityConfig.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/WebSecurityConfig.kt @@ -26,21 +26,18 @@ import org.springframework.security.config.annotation.method.configuration.Enabl import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter -import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer import org.springframework.security.config.http.SessionCreationPolicy import org.springframework.security.core.Authentication import org.springframework.security.core.AuthenticationException import org.springframework.security.core.context.SecurityContextHolder import org.springframework.security.core.userdetails.User import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder -import org.springframework.security.crypto.password.PasswordEncoder import org.springframework.security.web.AuthenticationEntryPoint import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter import org.springframework.security.web.authentication.www.BasicAuthenticationFilter import org.springframework.stereotype.Component import org.springframework.util.Assert import org.springframework.web.cors.CorsConfiguration -import org.springframework.web.cors.CorsConfigurationSource import org.springframework.web.cors.UrlBasedCorsConfigurationSource import org.springframework.web.util.WebUtils import java.util.* @@ -67,18 +64,16 @@ class WebSecurityConfig( } @Bean - fun passwordEncoder(): PasswordEncoder { - return BCryptPasswordEncoder() - } + fun passwordEncoder() = + BCryptPasswordEncoder() @Bean - override fun authenticationManagerBean(): AuthenticationManager { - return super.authenticationManagerBean() - } + override fun authenticationManagerBean(): AuthenticationManager = + super.authenticationManagerBean() @Bean - fun corsConfigurationSource(): CorsConfigurationSource { - return UrlBasedCorsConfigurationSource().apply { + fun corsConfigurationSource() = + UrlBasedCorsConfigurationSource().apply { registerCorsConfiguration("/**", CorsConfiguration().apply { allowedOrigins = listOf("http://localhost:4200") // Angular development server allowedMethods = listOf( @@ -92,7 +87,6 @@ class WebSecurityConfig( allowCredentials = true }.applyPermitDefaultValues()) } - } @PostConstruct fun initWebSecurity() { @@ -126,18 +120,6 @@ class WebSecurityConfig( } override fun configure(http: HttpSecurity) { - fun ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry.generateAuthorizations(): - ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry { - ControllerAuthorizations.values().forEach { controller -> - val antMatcher = controller.antMatcher - controller.permissions.forEach { - antMatchers(it.key, antMatcher).hasAuthority(it.value.name) - logger.debug("Added authorization for path '$antMatcher': ${it.key.name} -> ${it.value.name}") - } - } - return this - } - http .headers().frameOptions().disable() .and() @@ -160,11 +142,9 @@ class WebSecurityConfig( if (!debugMode) { http.authorizeRequests() - .antMatchers(HttpMethod.GET, "/").permitAll() .antMatchers("/api/login").permitAll() - .antMatchers("/api/employee/logout").permitAll() - .antMatchers(HttpMethod.GET, "/api/employee/current").authenticated() - .generateAuthorizations() + .antMatchers("/api/logout").authenticated() + .anyRequest().authenticated() } else { http .cors() @@ -304,83 +284,3 @@ class SecurityConfigurationProperties { class SystemUserCredentials(var id: Long? = null, var password: String? = null) } - -private enum class ControllerAuthorizations( - val antMatcher: String, - val permissions: Map -) { - INVENTORY_ADD( - "/api/inventory/add/**", mapOf( - HttpMethod.PUT to EmployeePermission.EDIT_MATERIAL - ) - ), - INVENTORY_DEDUCT( - "/api/inventory/deduct/**", mapOf( - HttpMethod.PUT to EmployeePermission.VIEW_MATERIAL - ) - ), - MATERIALS( - "/api/material/**", mapOf( - HttpMethod.GET to EmployeePermission.VIEW_MATERIAL, - HttpMethod.POST to EmployeePermission.EDIT_MATERIAL, - HttpMethod.PUT to EmployeePermission.EDIT_MATERIAL, - HttpMethod.DELETE to EmployeePermission.REMOVE_MATERIAL - ) - ), - MATERIAL_TYPES( - "/api/materialtype/**", mapOf( - HttpMethod.GET to EmployeePermission.VIEW_MATERIAL_TYPE, - HttpMethod.POST to EmployeePermission.EDIT_MATERIAL_TYPE, - HttpMethod.PUT to EmployeePermission.EDIT_MATERIAL_TYPE, - HttpMethod.DELETE to EmployeePermission.REMOVE_MATERIAL_TYPE - ) - ), - COMPANY( - "/api/company/**", mapOf( - HttpMethod.GET to EmployeePermission.VIEW_COMPANY, - HttpMethod.POST to EmployeePermission.EDIT_COMPANY, - HttpMethod.PUT to EmployeePermission.EDIT_COMPANY, - HttpMethod.DELETE to EmployeePermission.REMOVE_COMPANY - ) - ), - RECIPE_STEP( - "/api/recipe/**", mapOf( - HttpMethod.GET to EmployeePermission.VIEW_RECIPE, - HttpMethod.POST to EmployeePermission.EDIT_RECIPE, - HttpMethod.PUT to EmployeePermission.EDIT_RECIPE, - HttpMethod.DELETE to EmployeePermission.REMOVE_RECIPE - ) - ), - SET_BROWSER_DEFAULT_GROUP( - "/api/employee/group/default/**", mapOf( - HttpMethod.GET to EmployeePermission.VIEW_EMPLOYEE_GROUP, - HttpMethod.POST to EmployeePermission.SET_BROWSER_DEFAULT_GROUP - ) - ), - EMPLOYEES_FOR_GROUP( - "/api/employee/group/*/employees", mapOf( - HttpMethod.GET to EmployeePermission.VIEW_EMPLOYEE - ) - ), - EMPLOYEE_GROUP( - "/api/employee/group/**", mapOf( - HttpMethod.GET to EmployeePermission.VIEW_EMPLOYEE_GROUP, - HttpMethod.POST to EmployeePermission.EDIT_EMPLOYEE_GROUP, - HttpMethod.PUT to EmployeePermission.EDIT_EMPLOYEE_GROUP, - HttpMethod.DELETE to EmployeePermission.REMOVE_EMPLOYEE_GROUP - ) - ), - EMPLOYEE_PASSWORD( - "/api/employee/*/password", mapOf( - HttpMethod.PUT to EmployeePermission.EDIT_EMPLOYEE_PASSWORD - ) - ), - EMPLOYEE( - "/api/employee/**", mapOf( - HttpMethod.GET to EmployeePermission.VIEW_EMPLOYEE, - HttpMethod.POST to EmployeePermission.EDIT_EMPLOYEE, - HttpMethod.PUT to EmployeePermission.EDIT_EMPLOYEE, - HttpMethod.DELETE to EmployeePermission.REMOVE_EMPLOYEE - ) - ) -} diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/AccountModel.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/AccountModel.kt index d1503bd..bb1bf85 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/AccountModel.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/AccountModel.kt @@ -80,25 +80,11 @@ open class EmployeeSaveDto( @field:Size(min = 8, message = EMPLOYEE_PASSWORD_TOO_SHORT_MESSAGE) val password: String, - @field:ManyToOne - @Fetch(FetchMode.SELECT) - var groupId: Long? = null, + val groupId: Long?, @Enumerated(EnumType.STRING) val permissions: MutableSet = mutableSetOf() -) : EntityDto { - override fun toEntity(): Employee = - Employee( - id, - firstName, - lastName, - "", - isDefaultGroupUser = false, - isSystemUser = false, - group = null, - permissions = permissions - ) -} +) : EntityDto open class EmployeeUpdateDto( @field:NotNull(message = EMPLOYEE_ID_NULL_MESSAGE) @@ -110,6 +96,8 @@ open class EmployeeUpdateDto( @field:NullOrNotBlank(message = EMPLOYEE_LAST_NAME_EMPTY_MESSAGE) val lastName: String?, + val groupId: Long?, + @Enumerated(EnumType.STRING) val permissions: Set? ) : EntityDto @@ -311,9 +299,10 @@ fun employeeUpdateDto( id: Long = 0L, firstName: String = "firstName", lastName: String = "lastName", + groupId: Long? = null, permissions: MutableSet = mutableSetOf(), op: EmployeeUpdateDto.() -> Unit = {} -) = EmployeeUpdateDto(id, firstName, lastName, permissions).apply(op) +) = EmployeeUpdateDto(id, firstName, lastName, groupId, permissions).apply(op) fun employeeGroup( id: Long? = null, diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/AccountControllers.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/AccountControllers.kt index f2d857f..43a718e 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/AccountControllers.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/AccountControllers.kt @@ -2,122 +2,144 @@ package dev.fyloz.colorrecipesexplorer.rest import dev.fyloz.colorrecipesexplorer.model.* import dev.fyloz.colorrecipesexplorer.service.EmployeeGroupServiceImpl -import dev.fyloz.colorrecipesexplorer.service.EmployeeServiceImpl -import org.springframework.http.HttpStatus +import dev.fyloz.colorrecipesexplorer.service.EmployeeService import org.springframework.http.MediaType -import org.springframework.http.ResponseEntity +import org.springframework.security.access.prepost.PreAuthorize import org.springframework.web.bind.annotation.* import java.security.Principal import javax.servlet.http.HttpServletRequest import javax.servlet.http.HttpServletResponse +import javax.validation.Valid private const val EMPLOYEE_CONTROLLER_PATH = "api/employee" private const val EMPLOYEE_GROUP_CONTROLLER_PATH = "api/employee/group" @RestController @RequestMapping(EMPLOYEE_CONTROLLER_PATH) -class EmployeeController(employeeService: EmployeeServiceImpl) : - AbstractModelRestApiController( - employeeService, - EMPLOYEE_CONTROLLER_PATH - ) { +@PreAuthorize("hasAuthority('VIEW_EMPLOYEE')") +class EmployeeController(private val employeeService: EmployeeService) { + @GetMapping + fun getAll() = + ok(employeeService.getAll()) + + @GetMapping("{id}") + fun getById(@PathVariable id: Long) = + ok(employeeService.getById(id)) + @GetMapping("current") - @ResponseStatus(HttpStatus.OK) - fun getCurrent(loggedInEmployee: Principal?): ResponseEntity = if (loggedInEmployee != null) - ResponseEntity.ok( - service.getById( - loggedInEmployee.name.toLong(), - ignoreDefaultGroupUsers = false, - ignoreSystemUsers = false + @PreAuthorize("authenticated()") + fun getCurrent(loggedInEmployee: Principal?) = + if (loggedInEmployee != null) + ok( + employeeService.getById( + loggedInEmployee.name.toLong(), + ignoreDefaultGroupUsers = false, + ignoreSystemUsers = false + ) ) - ) - else - ResponseEntity.status(HttpStatus.FORBIDDEN).build() + else + forbidden() + + @PostMapping + @PreAuthorize("hasAuthority('EDIT_EMPLOYEE')") + fun save(@Valid @RequestBody employee: EmployeeSaveDto) = + created(EMPLOYEE_CONTROLLER_PATH) { + employeeService.save(employee) + } + + @PutMapping + @PreAuthorize("hasAuthority('EDIT_EMPLOYEE')") + fun update(@Valid @RequestBody employee: EmployeeUpdateDto) = + noContent { + employeeService.update(employee) + } @PutMapping("{id}/password", consumes = [MediaType.TEXT_PLAIN_VALUE]) - @ResponseStatus(HttpStatus.NO_CONTENT) - fun updatePassword(@PathVariable id: Long, @RequestBody password: String): ResponseEntity { - service.updatePassword(id, password) - return ResponseEntity - .noContent() - .build() - } + @PreAuthorize("hasAuthority('EDIT_EMPLOYEE_PASSWORD')") + fun updatePassword(@PathVariable id: Long, @RequestBody password: String) = + noContent { + employeeService.updatePassword(id, password) + } @PutMapping("{employeeId}/permissions/{permission}") - @ResponseStatus(HttpStatus.NO_CONTENT) + @PreAuthorize("hasAuthority('EDIT_EMPLOYEE')") fun addPermission( @PathVariable employeeId: Long, @PathVariable permission: EmployeePermission - ): ResponseEntity { - service.addPermission(employeeId, permission) - return ResponseEntity - .noContent() - .build() + ) = noContent { + employeeService.addPermission(employeeId, permission) } @DeleteMapping("{employeeId}/permissions/{permission}") - @ResponseStatus(HttpStatus.NO_CONTENT) + @PreAuthorize("hasAuthority('EDIT_EMPLOYEE')") fun removePermission( @PathVariable employeeId: Long, @PathVariable permission: EmployeePermission - ): ResponseEntity { - service.removePermission(employeeId, permission) - return ResponseEntity - .noContent() - .build() + ) = noContent { + employeeService.removePermission(employeeId, permission) } - @GetMapping("logout") - @ResponseStatus(HttpStatus.OK) - fun logout(request: HttpServletRequest): ResponseEntity { - service.logout(request) - return ResponseEntity.ok().build() - } + @DeleteMapping("{id}") + @PreAuthorize("hasAuthority('REMOVE_EMPLOYEE')") + fun deleteById(@PathVariable id: Long) = + employeeService.deleteById(id) } @RestController @RequestMapping(EMPLOYEE_GROUP_CONTROLLER_PATH) -class GroupsController(groupService: EmployeeGroupServiceImpl) : - AbstractModelRestApiController( - groupService, - EMPLOYEE_GROUP_CONTROLLER_PATH - ) { +@PreAuthorize("hasAuthority('VIEW_EMPLOYEE')") +class GroupsController(private val groupService: EmployeeGroupServiceImpl) { + @GetMapping + fun getAll() = + ok(groupService.getAll()) + + @GetMapping("{id}") + fun getById(@PathVariable id: Long) = + ok(groupService.getById(id)) + @GetMapping("{id}/employees") - @ResponseStatus(HttpStatus.OK) - fun getEmployeesForGroup(@PathVariable id: Long): ResponseEntity> = - ResponseEntity.ok(service.getEmployeesForGroup(id)) + fun getEmployeesForGroup(@PathVariable id: Long) = + ok(groupService.getEmployeesForGroup(id)) @PostMapping("default/{groupId}") - @ResponseStatus(HttpStatus.NO_CONTENT) - fun setDefaultGroup(@PathVariable groupId: Long, response: HttpServletResponse): ResponseEntity { - service.setResponseDefaultGroup(groupId, response) - return ResponseEntity - .noContent() - .build() - } + @PreAuthorize("hasAuthority('SET_BROWSER_DEFAULT_GROUP')") + fun setDefaultGroup(@PathVariable groupId: Long, response: HttpServletResponse) = + noContent { + groupService.setResponseDefaultGroup(groupId, response) + } @GetMapping("default") - @ResponseStatus(HttpStatus.OK) - fun getRequestDefaultGroup(request: HttpServletRequest): ResponseEntity = - ResponseEntity.ok(service.getRequestDefaultGroup(request)) + fun getRequestDefaultGroup(request: HttpServletRequest) = + ok(groupService.getRequestDefaultGroup(request)) - @PutMapping("{groupId}/{employeeId}") - @ResponseStatus(HttpStatus.NO_CONTENT) - fun addEmployeeToGroup(@PathVariable groupId: Long, @PathVariable employeeId: Long): ResponseEntity { -// service.addEmployeeToGroup(groupId, employeeId) -// return ResponseEntity -// .noContent() -// .build() - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build() // TODO Go to employee controller - } + @PostMapping + @PreAuthorize("hasAuthority('EDIT_EMPLOYEE')") + fun save(@Valid @RequestBody group: EmployeeGroupSaveDto) = + created(EMPLOYEE_GROUP_CONTROLLER_PATH) { + groupService.save(group) + } - @DeleteMapping("{groupId}/{employeeId}") - @ResponseStatus(HttpStatus.NO_CONTENT) - fun removeEmployeeFromGroup(@PathVariable groupId: Long, @PathVariable employeeId: Long): ResponseEntity { -// service.removeEmployeeFromGroup(groupId, employeeId) -// return ResponseEntity -// .noContent() -// .build() - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build() // TODO Go to employee controller - } + @PutMapping + @PreAuthorize("hasAuthority('EDIT_EMPLOYEE')") + fun update(@Valid @RequestBody group: EmployeeGroupUpdateDto) = + noContent { + groupService.update(group) + } + + @DeleteMapping("{id}") + @PreAuthorize("hasAuthority('REMOVE_EMPLOYEE')") + fun deleteById(@PathVariable id: Long) = + noContent { + groupService.deleteById(id) + } +} + +@RestController +@RequestMapping("api") +class LogoutController(private val employeeService: EmployeeService) { + @GetMapping("logout") + fun logout(request: HttpServletRequest) = + ok { + employeeService.logout(request) + } } diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/CompanyController.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/CompanyController.kt index 2da0018..d2eb66d 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/CompanyController.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/CompanyController.kt @@ -4,15 +4,42 @@ import dev.fyloz.colorrecipesexplorer.model.Company import dev.fyloz.colorrecipesexplorer.model.CompanySaveDto import dev.fyloz.colorrecipesexplorer.model.CompanyUpdateDto import dev.fyloz.colorrecipesexplorer.service.CompanyService -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RestController +import org.springframework.security.access.prepost.PreAuthorize +import org.springframework.web.bind.annotation.* +import javax.validation.Valid private const val COMPANY_CONTROLLER_PATH = "api/company" @RestController @RequestMapping(COMPANY_CONTROLLER_PATH) -class CompanyController(companyService: CompanyService) : - AbstractModelRestApiController( - companyService, - COMPANY_CONTROLLER_PATH - ) +@PreAuthorize("hasAuthority('VIEW_COMPANY')") +class CompanyController(private val companyService: CompanyService) { + @GetMapping + fun getAll() = + ok(companyService.getAll()) + + @GetMapping("{id}") + fun getById(@PathVariable id: Long) = + ok(companyService.getById(id)) + + @PostMapping + @PreAuthorize("hasAuthority('EDIT_COMPANY')") + fun save(@Valid @RequestBody company: CompanySaveDto) = + created(COMPANY_CONTROLLER_PATH) { + companyService.save(company) + } + + @PutMapping + @PreAuthorize("hasAuthority('EDIT_COMPANY')") + fun update(@Valid @RequestBody company: CompanyUpdateDto) = + noContent { + companyService.update(company) + } + + @DeleteMapping("{id}") + @PreAuthorize("hasAuthority('REMOVE_COMPANY')") + fun deleteById(@PathVariable id: Long) = + noContent { + companyService.deleteById(id) + } +} diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/InventoryController.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/InventoryController.kt index 17b061f..ae9443f 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/InventoryController.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/InventoryController.kt @@ -4,6 +4,7 @@ import dev.fyloz.colorrecipesexplorer.model.MaterialQuantityDto import dev.fyloz.colorrecipesexplorer.model.MixDeductDto import dev.fyloz.colorrecipesexplorer.service.InventoryService import org.springframework.http.ResponseEntity +import org.springframework.security.access.prepost.PreAuthorize import org.springframework.web.bind.annotation.PutMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping @@ -17,17 +18,18 @@ class InventoryController( private val inventoryService: InventoryService ) { @PutMapping("add") + @PreAuthorize("hasAuthority('EDIT_MATERIAL')") fun add(@RequestBody quantities: Collection): ResponseEntity> { return ResponseEntity.ok(inventoryService.add(quantities)) } @PutMapping("deduct") - fun deduct(@RequestBody quantities: Collection): ResponseEntity> { - return ResponseEntity.ok(inventoryService.deduct(quantities)) - } + @PreAuthorize("hasAuthority('VIEW_RECIPE')") + fun deduct(@RequestBody quantities: Collection) = + ok(inventoryService.deduct(quantities)) @PutMapping("deduct/mix") - fun deduct(@RequestBody mixRatio: MixDeductDto): ResponseEntity> { - return ResponseEntity.ok(inventoryService.deductMix(mixRatio)) - } + @PreAuthorize("hasAuthority('VIEW_RECIPE')") + fun deduct(@RequestBody mixRatio: MixDeductDto) = + ok(inventoryService.deductMix(mixRatio)) } diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/MaterialController.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/MaterialController.kt index 6f30487..37e7e7b 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/MaterialController.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/MaterialController.kt @@ -2,10 +2,9 @@ package dev.fyloz.colorrecipesexplorer.rest import dev.fyloz.colorrecipesexplorer.model.* import dev.fyloz.colorrecipesexplorer.service.MaterialService -import org.springframework.http.HttpHeaders -import org.springframework.http.HttpStatus import org.springframework.http.MediaType import org.springframework.http.ResponseEntity +import org.springframework.security.access.prepost.PreAuthorize import org.springframework.web.bind.annotation.* import org.springframework.web.multipart.MultipartFile import javax.validation.Valid @@ -14,75 +13,79 @@ private const val MATERIAL_CONTROLLER_PATH = "api/material" @RestController @RequestMapping(MATERIAL_CONTROLLER_PATH) -class MaterialController(materialService: MaterialService) : - AbstractModelRestApiController( - materialService, - MATERIAL_CONTROLLER_PATH - ) { +@PreAuthorize("hasAuthority('VIEW_MATERIAL')") +class MaterialController(private val materialService: MaterialService) { + @GetMapping + fun getAll() = + ok(materialService.getAll()) + @GetMapping("notmixtype") - fun getAllNotMixType(): ResponseEntity> = - ResponseEntity.ok(service.getAllNotMixType()) + fun getAllNotMixType() = + ok(materialService.getAllNotMixType()) - @GetMapping("mix/create/{recipeId}") - @ResponseStatus(HttpStatus.OK) - fun getAllForMixCreation(@PathVariable recipeId: Long): ResponseEntity> = - ResponseEntity.ok(service.getAllForMixCreation(recipeId)) + @GetMapping("{id}") + fun getById(@PathVariable id: Long) = + ok(materialService.getById(id)) - @GetMapping("mix/update/{mixId}") - @ResponseStatus(HttpStatus.OK) - fun getAllForMixUpdate(@PathVariable mixId: Long): ResponseEntity> = - ResponseEntity.ok(service.getAllForMixUpdate(mixId)) + @PostMapping(consumes = [MediaType.MULTIPART_FORM_DATA_VALUE]) + @PreAuthorize("hasAuthority('EDIT_MATERIAL')") + fun save(@Valid @RequestBody material: MaterialSaveDto, simdutFile: MultipartFile?) = + created(MATERIAL_CONTROLLER_PATH) { + materialService.save( + materialSaveDto( + name = material.name, + inventoryQuantity = material.inventoryQuantity, + materialTypeId = material.materialTypeId, + simdutFile = simdutFile + ) + ) + } + + @PutMapping(consumes = [MediaType.MULTIPART_FORM_DATA_VALUE]) + @PreAuthorize("hasAuthority('EDIT_MATERIAL')") + fun update(@Valid @RequestBody material: MaterialUpdateDto, simdutFile: MultipartFile?) = + noContent { + materialService.update( + materialUpdateDto( + id = material.id, + name = material.name, + inventoryQuantity = material.inventoryQuantity, + materialTypeId = material.materialTypeId, + simdutFile = simdutFile + ) + ) + } + + @DeleteMapping("{id}") + @PreAuthorize("hasAuthority('REMOVE_MATERIAL')") + fun deleteById(@PathVariable id: Long) = + noContent { + materialService.deleteById(id) + } @GetMapping("{id}/simdut/exists") - @ResponseStatus(HttpStatus.OK) - fun hasSimdut(@PathVariable id: Long): ResponseEntity = - ResponseEntity.ok(service.hasSimdut(id)) + fun hasSimdut(@PathVariable id: Long) = + ok(materialService.hasSimdut(id)) @GetMapping("{id}/simdut", produces = [MediaType.APPLICATION_PDF_VALUE]) - @ResponseStatus(HttpStatus.OK) - fun getSimdut(@PathVariable id: Long): ResponseEntity { - val simdutFile = service.getSimdut(id) - return if (simdutFile.isEmpty()) { - ResponseEntity.notFound().build() + fun getSimdut(@PathVariable id: Long): ResponseEntity = with(materialService.getSimdut(id)) { + if (this.isEmpty()) { + notFound() } else { - val headers = HttpHeaders().apply { contentType = MediaType.APPLICATION_PDF } - ResponseEntity(simdutFile, headers, HttpStatus.OK) + ok(this, httpHeaders(contentType = MediaType.APPLICATION_PDF)) } } @GetMapping("/simdut") - fun getAllIdsWithSimdut(): ResponseEntity> = - ResponseEntity.ok(service.getAllIdsWithSimdut()) + fun getAllIdsWithSimdut() = + ok(materialService.getAllIdsWithSimdut()) - @PostMapping(consumes = [MediaType.MULTIPART_FORM_DATA_VALUE]) - fun save(@Valid entity: MaterialSaveDto, simdutFile: MultipartFile?): ResponseEntity = - super.save( - materialSaveDto( - name = entity.name, - inventoryQuantity = entity.inventoryQuantity, - materialTypeId = entity.materialTypeId, - simdutFile = simdutFile - ) - ) + @GetMapping("mix/create/{recipeId}") + fun getAllForMixCreation(@PathVariable recipeId: Long) = + ok(materialService.getAllForMixCreation(recipeId)) - @PostMapping("unused_save") - override fun save(entity: MaterialSaveDto): ResponseEntity = - ResponseEntity.notFound().build() - - @PutMapping(consumes = [MediaType.MULTIPART_FORM_DATA_VALUE]) - fun update(@Valid entity: MaterialUpdateDto, simdutFile: MultipartFile?): ResponseEntity = - super.update( - materialUpdateDto( - id = entity.id, - name = entity.name, - inventoryQuantity = entity.inventoryQuantity, - materialTypeId = entity.materialTypeId, - simdutFile = simdutFile - ) - ) - - @PutMapping("unused_update") - override fun update(entity: MaterialUpdateDto): ResponseEntity = - ResponseEntity.notFound().build() + @GetMapping("mix/update/{mixId}") + fun getAllForMixUpdate(@PathVariable mixId: Long) = + ok(materialService.getAllForMixUpdate(mixId)) } diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/MaterialTypeController.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/MaterialTypeController.kt index 459babe..f17cd4e 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/MaterialTypeController.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/MaterialTypeController.kt @@ -4,16 +4,43 @@ import dev.fyloz.colorrecipesexplorer.model.MaterialType import dev.fyloz.colorrecipesexplorer.model.MaterialTypeSaveDto import dev.fyloz.colorrecipesexplorer.model.MaterialTypeUpdateDto import dev.fyloz.colorrecipesexplorer.service.MaterialTypeService -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RestController - +import org.springframework.security.access.prepost.PreAuthorize +import org.springframework.web.bind.annotation.* +import javax.validation.Valid private const val MATERIAL_TYPE_CONTROLLER_PATH = "api/materialtype" @RestController @RequestMapping(MATERIAL_TYPE_CONTROLLER_PATH) -class MaterialTypeController(materialTypeService: MaterialTypeService) : - AbstractModelRestApiController( - materialTypeService, - MATERIAL_TYPE_CONTROLLER_PATH - ) +@PreAuthorize("hasAuthority('VIEW_MATERIAL_TYPE')") +class MaterialTypeController(private val materialTypeService: MaterialTypeService) { + @GetMapping + fun getAll() = + ok(materialTypeService.getAll()) + + @GetMapping("{id}") + fun getById(@PathVariable id: Long) = + ok(materialTypeService.getById(id)) + + @PostMapping + @PreAuthorize("hasAuthority('EDIT_MATERIAL_TYPE')") + fun save(@Valid @RequestBody materialType: MaterialTypeSaveDto) = + created(MATERIAL_TYPE_CONTROLLER_PATH) { + materialTypeService.save(materialType) + } + + @PutMapping + @PreAuthorize("hasAuthority('EDIT_MATERIAL_TYPE')") + fun update(@Valid @RequestBody materialType: MaterialTypeUpdateDto) = + noContent { + materialTypeService.update(materialType) + } + + @DeleteMapping("{id}") + @PreAuthorize("hasAuthority('REMOVE_MATERIAL_TYPE')") + fun deleteById(@PathVariable id: Long) = + noContent { + materialTypeService.deleteById(id) + } +} + diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/RecipeController.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/RecipeController.kt index 5af2e41..c54caa8 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/RecipeController.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/RecipeController.kt @@ -4,9 +4,9 @@ import dev.fyloz.colorrecipesexplorer.model.* import dev.fyloz.colorrecipesexplorer.service.MixService import dev.fyloz.colorrecipesexplorer.service.RecipeImageService import dev.fyloz.colorrecipesexplorer.service.RecipeService -import org.springframework.http.HttpStatus import org.springframework.http.MediaType import org.springframework.http.ResponseEntity +import org.springframework.security.access.prepost.PreAuthorize import org.springframework.web.bind.annotation.* import org.springframework.web.multipart.MultipartFile import java.net.URI @@ -18,49 +18,97 @@ private const val MIX_CONTROLLER_PATH = "api/recipe/mix" @RestController @RequestMapping(RECIPE_CONTROLLER_PATH) -class RecipeController(recipeService: RecipeService) : - AbstractModelRestApiController( - recipeService, - RECIPE_CONTROLLER_PATH - ) { +@PreAuthorize("hasAuthority('VIEW_RECIPE')") +class RecipeController(private val recipeService: RecipeService) { + @GetMapping + fun getAll() = + ok(recipeService.getAll()) + + @GetMapping("{id}") + fun getById(@PathVariable id: Long) = + ok(recipeService.getById(id)) + + @PostMapping + @PreAuthorize("hasAuthority('EDIT_RECIPE')") + fun save(@Valid @RequestBody recipe: RecipeSaveDto) = + created(RECIPE_CONTROLLER_PATH) { + recipeService.save(recipe) + } + + @PutMapping + @PreAuthorize("hasAuthority('EDIT_RECIPE')") + fun update(@Valid @RequestBody recipe: RecipeUpdateDto) = + noContent { + recipeService.update(recipe) + } @PutMapping("public") - @ResponseStatus(HttpStatus.NO_CONTENT) - fun updatePublicData(@Valid @RequestBody publicDataDto: RecipePublicDataDto): ResponseEntity { - service.updatePublicData(publicDataDto) - return ResponseEntity.noContent().build() - } + fun updatePublicData(@Valid @RequestBody publicDataDto: RecipePublicDataDto) = + noContent { + recipeService.updatePublicData(publicDataDto) + } + + @DeleteMapping("{id}") + @PreAuthorize("hasAuthority('REMOVE_RECIPE')") + fun deleteById(@PathVariable id: Long) = + noContent { + recipeService.deleteById(id) + } } @RestController @RequestMapping(RECIPE_CONTROLLER_PATH) +@PreAuthorize("hasAuthority('VIEW_RECIPE')") class RecipeImageController(val recipeImageService: RecipeImageService) { @GetMapping("{recipeId}/image") - @ResponseStatus(HttpStatus.OK) - fun getAllIdsForRecipe(@PathVariable recipeId: Long): ResponseEntity> = - ResponseEntity.ok(recipeImageService.getAllIdsForRecipe(recipeId)) + fun getAllIdsForRecipe(@PathVariable recipeId: Long) = + ok(recipeImageService.getAllIdsForRecipe(recipeId)) @GetMapping("{recipeId}/image/{id}", produces = [MediaType.IMAGE_JPEG_VALUE, MediaType.IMAGE_PNG_VALUE]) - @ResponseStatus(HttpStatus.OK) - fun getById(@PathVariable recipeId: Long, @PathVariable id: Long): ResponseEntity = - ResponseEntity.ok(recipeImageService.getByIdForRecipe(id, recipeId)) + fun getById(@PathVariable recipeId: Long, @PathVariable id: Long) = + ok(recipeImageService.getByIdForRecipe(id, recipeId)) @PostMapping("{recipeId}/image", consumes = [MediaType.MULTIPART_FORM_DATA_VALUE]) - @ResponseStatus(HttpStatus.CREATED) + @PreAuthorize("hasAuthority('EDIT_RECIPE')") fun save(@PathVariable recipeId: Long, image: MultipartFile): ResponseEntity { val id = recipeImageService.save(image, recipeId) return ResponseEntity.created(URI.create("/$RECIPE_CONTROLLER_PATH/$recipeId/image/$id")).build() } @DeleteMapping("{recipeId}/image/{id}") - @ResponseStatus(HttpStatus.NO_CONTENT) - fun delete(@PathVariable recipeId: Long, @PathVariable id: Long): ResponseEntity { - recipeImageService.delete(id, recipeId) - return ResponseEntity.noContent().build() - } + @PreAuthorize("hasAuthority('REMOVE_RECIPE')") + fun delete(@PathVariable recipeId: Long, @PathVariable id: Long) = + noContent { + recipeImageService.delete(id, recipeId) + } } @RestController @RequestMapping(MIX_CONTROLLER_PATH) -class MixController(val mixService: MixService) : - AbstractModelRestApiController(mixService, MIX_CONTROLLER_PATH) +@PreAuthorize("hasAuthority('VIEW_RECIPE')") +class MixController(private val mixService: MixService) { + @GetMapping("{id}") + fun getById(@PathVariable id: Long) = + ok(mixService.getById(id)) + + @PostMapping + @PreAuthorize("hasAuthority('EDIT_RECIPE')") + fun save(@Valid @RequestBody mix: MixSaveDto) = + created(MIX_CONTROLLER_PATH) { + mixService.save(mix) + } + + @PutMapping + @PreAuthorize("hasAuthority('EDIT_RECIPE')") + fun update(@Valid @RequestBody mix: MixUpdateDto) = + noContent { + mixService.update(mix) + } + + @DeleteMapping("{id}") + @PreAuthorize("hasAuthority('REMOVE_RECIPE')") + fun deleteById(@PathVariable id: Long) = + noContent { + mixService.deleteById(id) + } +} diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/RestApiController.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/RestApiController.kt deleted file mode 100644 index 797794c..0000000 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/RestApiController.kt +++ /dev/null @@ -1,87 +0,0 @@ -package dev.fyloz.colorrecipesexplorer.rest - -import dev.fyloz.colorrecipesexplorer.model.EntityDto -import dev.fyloz.colorrecipesexplorer.model.Model -import dev.fyloz.colorrecipesexplorer.service.ExternalModelService -import dev.fyloz.colorrecipesexplorer.service.ExternalService -import org.springframework.http.HttpStatus -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.* -import java.net.URI -import javax.validation.Valid - -interface RestApiController, U : EntityDto> { - @ResponseStatus(HttpStatus.OK) - fun getAll(): ResponseEntity> - - @ResponseStatus(HttpStatus.CREATED) - fun save(entity: S): ResponseEntity - - @ResponseStatus(HttpStatus.NO_CONTENT) - fun update(entity: U): ResponseEntity - - @ResponseStatus(HttpStatus.NO_CONTENT) - fun delete(entity: E): ResponseEntity -} - -interface RestModelApiController, U : EntityDto> : RestApiController { - @ResponseStatus(HttpStatus.OK) - fun getById(id: Long): ResponseEntity - - @ResponseStatus(HttpStatus.NO_CONTENT) - fun deleteById(id: Long): ResponseEntity -} - -abstract class AbstractRestApiController, U : EntityDto, S : ExternalService>( - val service: S, - private val controllerPath: String -) : - RestApiController { - protected abstract fun getEntityId(entity: E): Any? - - @GetMapping - override fun getAll(): ResponseEntity> = ResponseEntity.ok(service.getAll()) - - @PostMapping - override fun save(@Valid @RequestBody entity: N): ResponseEntity { - val saved = service.save(entity) - return ResponseEntity - .created(URI("$controllerPath/${getEntityId(saved)}")) - .body(saved) - } - - @PutMapping - override fun update(@Valid @RequestBody entity: U): ResponseEntity { - service.update(entity) - return ResponseEntity - .noContent() - .build() - } - - @DeleteMapping - override fun delete(@Valid @RequestBody entity: E): ResponseEntity { - service.delete(entity) - return ResponseEntity - .noContent() - .build() - } -} - -abstract class AbstractModelRestApiController, U : EntityDto, S : ExternalModelService>( - service: S, - controllerPath: String -) : - AbstractRestApiController(service, controllerPath), RestModelApiController { - override fun getEntityId(entity: E) = entity.id - - @GetMapping("{id}") - override fun getById(@PathVariable id: Long): ResponseEntity = ResponseEntity.ok(service.getById(id)) - - @DeleteMapping("{id}") - override fun deleteById(@PathVariable id: Long): ResponseEntity { - service.deleteById(id) - return ResponseEntity - .noContent() - .build() - } -} diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/RestUtils.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/RestUtils.kt new file mode 100644 index 0000000..0c0de8a --- /dev/null +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/RestUtils.kt @@ -0,0 +1,58 @@ +package dev.fyloz.colorrecipesexplorer.rest + +import dev.fyloz.colorrecipesexplorer.model.Model +import org.springframework.http.HttpHeaders +import org.springframework.http.HttpStatus +import org.springframework.http.MediaType +import org.springframework.http.ResponseEntity +import java.net.URI + +/** Creates a HTTP OK [ResponseEntity] from the given [body]. */ +fun ok(body: T): ResponseEntity = + ResponseEntity.ok(body) + +/** Creates a HTTP OK [ResponseEntity] from the given [body] and [headers]. */ +fun ok(body: T, headers: HttpHeaders): ResponseEntity = + ResponseEntity(body, headers, HttpStatus.OK) + +/** Executes the given [action] then returns an HTTP OK [ResponseEntity] form the given [body]. */ +fun ok(action: () -> Unit): ResponseEntity { + action() + return ResponseEntity.ok().build() +} + +/** Creates a HTTP CREATED [ResponseEntity] from the given [body] with the location set to [controllerPath]/id. */ +fun created(controllerPath: String, body: T): ResponseEntity = + ResponseEntity.created(URI.create("$controllerPath/${body.id}")).body(body) + +/** Creates a HTTP CREATED [ResponseEntity] with the result of the given [producer] as its body. */ +fun created(controllerPath: String, producer: () -> T): ResponseEntity = + created(controllerPath, producer()) + +/** Creates a HTTP NOT FOUND [ResponseEntity]. */ +fun notFound(): ResponseEntity = + ResponseEntity.notFound().build() + +/** Creates a HTTP NO CONTENT [ResponseEntity]. */ +fun noContent(): ResponseEntity = + ResponseEntity.noContent().build() + +/** Executes the given [action] then returns an HTTP NO CONTENT [ResponseEntity]. */ +fun noContent(action: () -> Unit): ResponseEntity { + action() + return noContent() +} + +/** Creates a HTTP FORBIDDEN [ResponseEntity]. */ +fun forbidden(): ResponseEntity = + ResponseEntity.status(HttpStatus.FORBIDDEN).build() + +/** Creates an [HttpHeaders] instance from the given options. */ +fun httpHeaders( + contentType: MediaType = MediaType.APPLICATION_JSON, + op: HttpHeaders.() -> Unit = {} +) = HttpHeaders().apply { + this.contentType = contentType + + op() +} diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/AccountService.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/AccountService.kt index 214dbbd..65f3ed1 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/AccountService.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/AccountService.kt @@ -116,7 +116,7 @@ class EmployeeServiceImpl( passwordEncoder.encode(password), isDefaultGroupUser = false, isSystemUser = false, - group = if (groupId != null) groupService.getById(groupId!!) else null, + group = if (groupId != null) groupService.getById(groupId) else null, permissions = permissions ) }) @@ -160,7 +160,7 @@ class EmployeeServiceImpl( password = persistedEmployee.password, isDefaultGroupUser = false, isSystemUser = false, - group = persistedEmployee.group, + group = if (entity.groupId != null) groupService.getById(entity.groupId) else persistedEmployee.group, permissions = permissions?.toMutableSet() ?: persistedEmployee.permissions, lastLoginTime = persistedEmployee.lastLoginTime ) diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/Service.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/Service.kt index 1b72600..d44f2ff 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/Service.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/Service.kt @@ -1,13 +1,11 @@ package dev.fyloz.colorrecipesexplorer.service -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import dev.fyloz.colorrecipesexplorer.exception.EntityAlreadyExistsException import dev.fyloz.colorrecipesexplorer.exception.EntityNotFoundException import dev.fyloz.colorrecipesexplorer.model.EntityDto import dev.fyloz.colorrecipesexplorer.model.Model import dev.fyloz.colorrecipesexplorer.model.NamedModel import dev.fyloz.colorrecipesexplorer.repository.NamedJpaRepository -import dev.fyloz.colorrecipesexplorer.rest.RestApiController import io.jsonwebtoken.lang.Assert import org.springframework.data.jpa.repository.JpaRepository import org.springframework.data.repository.findByIdOrNull @@ -114,13 +112,13 @@ abstract class AbstractNamedModelService, U : Ent abstract class AbstractExternalNamedModelService, U : EntityDto, R : NamedJpaRepository>( repository: R ) : AbstractNamedModelService(repository), ExternalNamedModelService - -/** Transforms the given object to JSON. **/ -fun Any.asJson(): String { - return jacksonObjectMapper().writeValueAsString(this) -} diff --git a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/AccountsServiceTest.kt b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/AccountsServiceTest.kt index 2830f0f..3c7e704 100644 --- a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/AccountsServiceTest.kt +++ b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/AccountsServiceTest.kt @@ -163,7 +163,6 @@ class EmployeeServiceTest : @Test fun `save(dto) calls and returns save() with the created employee`() { - whenever(entitySaveDto.toEntity()).doReturn(entitySaveDtoEmployee) doReturn(entitySaveDtoEmployee).whenever(service).save(any()) val found = service.save(entitySaveDto) From c374d76442effec560ce9b43257d02b0ae19a209 Mon Sep 17 00:00:00 2001 From: FyloZ Date: Sun, 4 Apr 2021 22:23:30 -0400 Subject: [PATCH 2/5] =?UTF-8?q?Mise=20=C3=A0=20jour=20des=20permissions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/WebSecurityConfig.kt | 1 + .../model/AccountModel.kt | 162 +++++++++--------- .../rest/AccountControllers.kt | 24 ++- .../rest/CompanyController.kt | 8 +- .../rest/InventoryController.kt | 6 +- .../rest/MaterialController.kt | 8 +- .../rest/MaterialTypeController.kt | 8 +- .../rest/RecipeController.kt | 17 +- .../service/AccountService.kt | 2 +- 9 files changed, 120 insertions(+), 116 deletions(-) diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/WebSecurityConfig.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/WebSecurityConfig.kt index 45422f6..389bd5a 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/WebSecurityConfig.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/WebSecurityConfig.kt @@ -144,6 +144,7 @@ class WebSecurityConfig( http.authorizeRequests() .antMatchers("/api/login").permitAll() .antMatchers("/api/logout").authenticated() + .antMatchers("/api/employee/current").authenticated() .anyRequest().authenticated() } else { http diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/AccountModel.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/AccountModel.kt index bb1bf85..4343a76 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/AccountModel.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/AccountModel.kt @@ -61,8 +61,19 @@ data class Employee( @Column(name = "last_login_time") var lastLoginTime: LocalDateTime? = null ) : Model { - @JsonProperty("permissions") - fun getFlattenedPermissions(): Iterable = getPermissions() + @get:JsonProperty("permissions") + val flatPermissions: Set + get() = permissions + .flatMap { it.flat() } + .filter { !it.deprecated } + .toMutableSet() + .apply { + if (group != null) this.addAll(group!!.flatPermissions) + } + + @get:JsonIgnore + val authorities: Set + get() = flatPermissions.map { it.toAuthority() }.toMutableSet() } /** DTO for creating employees. Allows a [password] a [groupId]. */ @@ -122,8 +133,16 @@ data class EmployeeGroup( @CollectionTable(name = "group_permission", joinColumns = [JoinColumn(name = "group_id")]) @Column(name = "permission") @Fetch(FetchMode.SUBSELECT) + @get:JsonProperty("explicitPermissions") val permissions: MutableSet = mutableSetOf(), -) : NamedModel +) : NamedModel { + @get:JsonProperty("permissions") + val flatPermissions: Set + get() = this.permissions + .flatMap { it.flat() } + .filter { !it.deprecated } + .toSet() +} open class EmployeeGroupSaveDto( @field:NotBlank(message = GROUP_NAME_NULL_MESSAGE) @@ -156,96 +175,81 @@ open class EmployeeGroupUpdateDto( data class EmployeeLoginRequest(val id: Long, val password: String) -enum class EmployeePermission(val impliedPermissions: List = listOf()) { - // View - VIEW_MATERIAL, - VIEW_MATERIAL_TYPE, - VIEW_COMPANY, - VIEW_RECIPE, - VIEW( - listOf( - VIEW_MATERIAL, - VIEW_MATERIAL_TYPE, - VIEW_COMPANY, - VIEW_RECIPE - ) - ), - VIEW_EMPLOYEE, - VIEW_EMPLOYEE_GROUP, +enum class EmployeePermission( + val impliedPermissions: List = listOf(), + val deprecated: Boolean = false +) { + VIEW_RECIPES, + VIEW_CATALOG, + VIEW_USERS, - // Edit - EDIT_MATERIAL(listOf(VIEW_MATERIAL)), - EDIT_MATERIAL_TYPE(listOf(VIEW_MATERIAL_TYPE)), - EDIT_COMPANY(listOf(VIEW_COMPANY)), - EDIT_RECIPE(listOf(VIEW_RECIPE)), - EDIT( - listOf( - EDIT_MATERIAL, - EDIT_MATERIAL_TYPE, - EDIT_COMPANY, - EDIT_RECIPE, - VIEW - ) - ), - EDIT_EMPLOYEE(listOf(VIEW_EMPLOYEE)), - EDIT_EMPLOYEE_PASSWORD(listOf(EDIT_EMPLOYEE)), - EDIT_EMPLOYEE_GROUP(listOf(VIEW_EMPLOYEE_GROUP)), + PRINT_MIX(listOf(VIEW_RECIPES)), - // Remove - REMOVE_MATERIAL(listOf(EDIT_MATERIAL)), - REMOVE_MATERIAL_TYPE(listOf(EDIT_MATERIAL_TYPE)), - REMOVE_COMPANY(listOf(EDIT_COMPANY)), - REMOVE_RECIPE(listOf(EDIT_RECIPE)), - REMOVE( - listOf( - REMOVE_MATERIAL, - REMOVE_MATERIAL_TYPE, - REMOVE_COMPANY, - REMOVE_RECIPE, - EDIT - ) - ), - REMOVE_EMPLOYEE(listOf(EDIT_EMPLOYEE)), - REMOVE_EMPLOYEE_GROUP(listOf(EDIT_EMPLOYEE_GROUP)), + EDIT_RECIPES_PUBLIC_DATA(listOf(VIEW_RECIPES)), + EDIT_RECIPES(listOf(EDIT_RECIPES_PUBLIC_DATA)), + EDIT_MATERIALS(listOf(VIEW_CATALOG)), + EDIT_MATERIAL_TYPES(listOf(VIEW_CATALOG)), + EDIT_COMPANIES(listOf(VIEW_CATALOG)), + EDIT_USERS(listOf(VIEW_USERS)), + EDIT_CATALOG(listOf(EDIT_MATERIALS, EDIT_MATERIAL_TYPES, EDIT_COMPANIES)), - // Others - SET_BROWSER_DEFAULT_GROUP( - listOf( - VIEW_EMPLOYEE_GROUP - ) - ), + ADD_TO_INVENTORY(listOf(VIEW_CATALOG)), + DEDUCT_FROM_INVENTORY(listOf(VIEW_RECIPES)), + + REMOVE_RECIPES(listOf(EDIT_RECIPES)), + REMOVE_MATERIALS(listOf(EDIT_MATERIALS)), + REMOVE_MATERIAL_TYPES(listOf(EDIT_MATERIAL_TYPES)), + REMOVE_COMPANIES(listOf(EDIT_COMPANIES)), + REMOVE_USERS(listOf(EDIT_USERS)), + REMOVE_CATALOG(listOf(REMOVE_MATERIALS, REMOVE_MATERIAL_TYPES, REMOVE_COMPANIES)), ADMIN( listOf( - REMOVE, - SET_BROWSER_DEFAULT_GROUP, + REMOVE_RECIPES, + REMOVE_USERS, + REMOVE_CATALOG, - // Admin only permissions - REMOVE_EMPLOYEE, - EDIT_EMPLOYEE_PASSWORD, - REMOVE_EMPLOYEE_GROUP, + PRINT_MIX, + ADD_TO_INVENTORY, + DEDUCT_FROM_INVENTORY ) - ); + ), + + // deprecated permissions + VIEW_RECIPE(listOf(VIEW_RECIPES), true), + VIEW_MATERIAL(listOf(VIEW_CATALOG), true), + VIEW_MATERIAL_TYPE(listOf(VIEW_CATALOG), true), + VIEW_COMPANY(listOf(VIEW_CATALOG), true), + VIEW(listOf(VIEW_RECIPES, VIEW_CATALOG), true), + VIEW_EMPLOYEE(listOf(VIEW_USERS), true), + VIEW_EMPLOYEE_GROUP(listOf(VIEW_USERS), true), + + EDIT_RECIPE(listOf(EDIT_RECIPES), true), + EDIT_MATERIAL(listOf(EDIT_MATERIALS), true), + EDIT_MATERIAL_TYPE(listOf(EDIT_MATERIAL_TYPES), true), + EDIT_COMPANY(listOf(EDIT_COMPANIES), true), + EDIT(listOf(EDIT_RECIPES, EDIT_CATALOG), true), + EDIT_EMPLOYEE(listOf(EDIT_USERS), true), + EDIT_EMPLOYEE_PASSWORD(listOf(EDIT_USERS), true), + EDIT_EMPLOYEE_GROUP(listOf(EDIT_USERS), true), + + REMOVE_RECIPE(listOf(REMOVE_RECIPES), true), + REMOVE_MATERIAL(listOf(REMOVE_MATERIALS), true), + REMOVE_MATERIAL_TYPE(listOf(REMOVE_MATERIAL_TYPES), true), + REMOVE_COMPANY(listOf(REMOVE_COMPANIES), true), + REMOVE(listOf(REMOVE_RECIPES, REMOVE_CATALOG), true), + REMOVE_EMPLOYEE(listOf(REMOVE_USERS), true), + REMOVE_EMPLOYEE_GROUP(listOf(REMOVE_USERS), true), + + SET_BROWSER_DEFAULT_GROUP(listOf(VIEW_USERS), true), + ; operator fun contains(permission: EmployeePermission): Boolean { return permission == this || impliedPermissions.any { permission in it } } } -/** Gets [GrantedAuthority]s of the given [Employee]. */ -fun Employee.getAuthorities(): MutableCollection { - return getPermissions().map { it.toAuthority() }.toMutableSet() -} - -/** Gets [EmployeePermission]s of the given [Employee]. */ -fun Employee.getPermissions(): Iterable { - val grantedPermissions: MutableSet = mutableSetOf() - if (group != null) grantedPermissions.addAll(group!!.permissions.flatMap { it.flat() }) - grantedPermissions.addAll(permissions.flatMap { it.flat() }) - return grantedPermissions -} - -private fun EmployeePermission.flat(): Iterable { +fun EmployeePermission.flat(): Iterable { return mutableSetOf(this).apply { impliedPermissions.forEach { addAll(it.flat()) diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/AccountControllers.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/AccountControllers.kt index 43a718e..0ea008a 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/AccountControllers.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/AccountControllers.kt @@ -16,7 +16,7 @@ private const val EMPLOYEE_GROUP_CONTROLLER_PATH = "api/employee/group" @RestController @RequestMapping(EMPLOYEE_CONTROLLER_PATH) -@PreAuthorize("hasAuthority('VIEW_EMPLOYEE')") +@PreAuthorize("hasAuthority('VIEW_USERS')") class EmployeeController(private val employeeService: EmployeeService) { @GetMapping fun getAll() = @@ -27,7 +27,6 @@ class EmployeeController(private val employeeService: EmployeeService) { ok(employeeService.getById(id)) @GetMapping("current") - @PreAuthorize("authenticated()") fun getCurrent(loggedInEmployee: Principal?) = if (loggedInEmployee != null) ok( @@ -41,28 +40,28 @@ class EmployeeController(private val employeeService: EmployeeService) { forbidden() @PostMapping - @PreAuthorize("hasAuthority('EDIT_EMPLOYEE')") + @PreAuthorize("hasAuthority('EDIT_USERS')") fun save(@Valid @RequestBody employee: EmployeeSaveDto) = created(EMPLOYEE_CONTROLLER_PATH) { employeeService.save(employee) } @PutMapping - @PreAuthorize("hasAuthority('EDIT_EMPLOYEE')") + @PreAuthorize("hasAuthority('EDIT_USERS')") fun update(@Valid @RequestBody employee: EmployeeUpdateDto) = noContent { employeeService.update(employee) } @PutMapping("{id}/password", consumes = [MediaType.TEXT_PLAIN_VALUE]) - @PreAuthorize("hasAuthority('EDIT_EMPLOYEE_PASSWORD')") + @PreAuthorize("hasAuthority('EDIT_USERS')") fun updatePassword(@PathVariable id: Long, @RequestBody password: String) = noContent { employeeService.updatePassword(id, password) } @PutMapping("{employeeId}/permissions/{permission}") - @PreAuthorize("hasAuthority('EDIT_EMPLOYEE')") + @PreAuthorize("hasAuthority('EDIT_USERS')") fun addPermission( @PathVariable employeeId: Long, @PathVariable permission: EmployeePermission @@ -71,7 +70,7 @@ class EmployeeController(private val employeeService: EmployeeService) { } @DeleteMapping("{employeeId}/permissions/{permission}") - @PreAuthorize("hasAuthority('EDIT_EMPLOYEE')") + @PreAuthorize("hasAuthority('EDIT_USERS')") fun removePermission( @PathVariable employeeId: Long, @PathVariable permission: EmployeePermission @@ -80,14 +79,14 @@ class EmployeeController(private val employeeService: EmployeeService) { } @DeleteMapping("{id}") - @PreAuthorize("hasAuthority('REMOVE_EMPLOYEE')") + @PreAuthorize("hasAuthority('REMOVE_USERS')") fun deleteById(@PathVariable id: Long) = employeeService.deleteById(id) } @RestController @RequestMapping(EMPLOYEE_GROUP_CONTROLLER_PATH) -@PreAuthorize("hasAuthority('VIEW_EMPLOYEE')") +@PreAuthorize("hasAuthority('VIEW_USERS')") class GroupsController(private val groupService: EmployeeGroupServiceImpl) { @GetMapping fun getAll() = @@ -102,7 +101,6 @@ class GroupsController(private val groupService: EmployeeGroupServiceImpl) { ok(groupService.getEmployeesForGroup(id)) @PostMapping("default/{groupId}") - @PreAuthorize("hasAuthority('SET_BROWSER_DEFAULT_GROUP')") fun setDefaultGroup(@PathVariable groupId: Long, response: HttpServletResponse) = noContent { groupService.setResponseDefaultGroup(groupId, response) @@ -113,21 +111,21 @@ class GroupsController(private val groupService: EmployeeGroupServiceImpl) { ok(groupService.getRequestDefaultGroup(request)) @PostMapping - @PreAuthorize("hasAuthority('EDIT_EMPLOYEE')") + @PreAuthorize("hasAuthority('EDIT_USERS')") fun save(@Valid @RequestBody group: EmployeeGroupSaveDto) = created(EMPLOYEE_GROUP_CONTROLLER_PATH) { groupService.save(group) } @PutMapping - @PreAuthorize("hasAuthority('EDIT_EMPLOYEE')") + @PreAuthorize("hasAuthority('EDIT_USERS')") fun update(@Valid @RequestBody group: EmployeeGroupUpdateDto) = noContent { groupService.update(group) } @DeleteMapping("{id}") - @PreAuthorize("hasAuthority('REMOVE_EMPLOYEE')") + @PreAuthorize("hasAuthority('REMOVE_USERS')") fun deleteById(@PathVariable id: Long) = noContent { groupService.deleteById(id) diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/CompanyController.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/CompanyController.kt index d2eb66d..de76240 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/CompanyController.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/CompanyController.kt @@ -12,7 +12,7 @@ private const val COMPANY_CONTROLLER_PATH = "api/company" @RestController @RequestMapping(COMPANY_CONTROLLER_PATH) -@PreAuthorize("hasAuthority('VIEW_COMPANY')") +@PreAuthorize("hasAuthority('VIEW_CATALOG')") class CompanyController(private val companyService: CompanyService) { @GetMapping fun getAll() = @@ -23,21 +23,21 @@ class CompanyController(private val companyService: CompanyService) { ok(companyService.getById(id)) @PostMapping - @PreAuthorize("hasAuthority('EDIT_COMPANY')") + @PreAuthorize("hasAuthority('EDIT_COMPANIES')") fun save(@Valid @RequestBody company: CompanySaveDto) = created(COMPANY_CONTROLLER_PATH) { companyService.save(company) } @PutMapping - @PreAuthorize("hasAuthority('EDIT_COMPANY')") + @PreAuthorize("hasAuthority('EDIT_COMPANIES')") fun update(@Valid @RequestBody company: CompanyUpdateDto) = noContent { companyService.update(company) } @DeleteMapping("{id}") - @PreAuthorize("hasAuthority('REMOVE_COMPANY')") + @PreAuthorize("hasAuthority('REMOVE_COMPANIES')") fun deleteById(@PathVariable id: Long) = noContent { companyService.deleteById(id) diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/InventoryController.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/InventoryController.kt index ae9443f..5971d86 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/InventoryController.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/InventoryController.kt @@ -18,18 +18,18 @@ class InventoryController( private val inventoryService: InventoryService ) { @PutMapping("add") - @PreAuthorize("hasAuthority('EDIT_MATERIAL')") + @PreAuthorize("hasAuthority('ADD_TO_INVENTORY')") fun add(@RequestBody quantities: Collection): ResponseEntity> { return ResponseEntity.ok(inventoryService.add(quantities)) } @PutMapping("deduct") - @PreAuthorize("hasAuthority('VIEW_RECIPE')") + @PreAuthorize("hasAuthority('DEDUCT_FROM_INVENTORY')") fun deduct(@RequestBody quantities: Collection) = ok(inventoryService.deduct(quantities)) @PutMapping("deduct/mix") - @PreAuthorize("hasAuthority('VIEW_RECIPE')") + @PreAuthorize("hasAuthority('DEDUCT_FROM_INVENTORY')") fun deduct(@RequestBody mixRatio: MixDeductDto) = ok(inventoryService.deductMix(mixRatio)) } diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/MaterialController.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/MaterialController.kt index 37e7e7b..64a5e10 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/MaterialController.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/MaterialController.kt @@ -13,7 +13,7 @@ private const val MATERIAL_CONTROLLER_PATH = "api/material" @RestController @RequestMapping(MATERIAL_CONTROLLER_PATH) -@PreAuthorize("hasAuthority('VIEW_MATERIAL')") +@PreAuthorize("hasAuthority('VIEW_CATALOG')") class MaterialController(private val materialService: MaterialService) { @GetMapping fun getAll() = @@ -28,7 +28,7 @@ class MaterialController(private val materialService: MaterialService) { ok(materialService.getById(id)) @PostMapping(consumes = [MediaType.MULTIPART_FORM_DATA_VALUE]) - @PreAuthorize("hasAuthority('EDIT_MATERIAL')") + @PreAuthorize("hasAuthority('EDIT_MATERIALS')") fun save(@Valid @RequestBody material: MaterialSaveDto, simdutFile: MultipartFile?) = created(MATERIAL_CONTROLLER_PATH) { materialService.save( @@ -42,7 +42,7 @@ class MaterialController(private val materialService: MaterialService) { } @PutMapping(consumes = [MediaType.MULTIPART_FORM_DATA_VALUE]) - @PreAuthorize("hasAuthority('EDIT_MATERIAL')") + @PreAuthorize("hasAuthority('EDIT_MATERIALS')") fun update(@Valid @RequestBody material: MaterialUpdateDto, simdutFile: MultipartFile?) = noContent { materialService.update( @@ -57,7 +57,7 @@ class MaterialController(private val materialService: MaterialService) { } @DeleteMapping("{id}") - @PreAuthorize("hasAuthority('REMOVE_MATERIAL')") + @PreAuthorize("hasAuthority('REMOVE_MATERIALS')") fun deleteById(@PathVariable id: Long) = noContent { materialService.deleteById(id) diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/MaterialTypeController.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/MaterialTypeController.kt index f17cd4e..03cc9fe 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/MaterialTypeController.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/MaterialTypeController.kt @@ -12,7 +12,7 @@ private const val MATERIAL_TYPE_CONTROLLER_PATH = "api/materialtype" @RestController @RequestMapping(MATERIAL_TYPE_CONTROLLER_PATH) -@PreAuthorize("hasAuthority('VIEW_MATERIAL_TYPE')") +@PreAuthorize("hasAuthority('VIEW_CATALOG')") class MaterialTypeController(private val materialTypeService: MaterialTypeService) { @GetMapping fun getAll() = @@ -23,21 +23,21 @@ class MaterialTypeController(private val materialTypeService: MaterialTypeServic ok(materialTypeService.getById(id)) @PostMapping - @PreAuthorize("hasAuthority('EDIT_MATERIAL_TYPE')") + @PreAuthorize("hasAuthority('EDIT_MATERIAL_TYPES')") fun save(@Valid @RequestBody materialType: MaterialTypeSaveDto) = created(MATERIAL_TYPE_CONTROLLER_PATH) { materialTypeService.save(materialType) } @PutMapping - @PreAuthorize("hasAuthority('EDIT_MATERIAL_TYPE')") + @PreAuthorize("hasAuthority('EDIT_MATERIAL_TYPES')") fun update(@Valid @RequestBody materialType: MaterialTypeUpdateDto) = noContent { materialTypeService.update(materialType) } @DeleteMapping("{id}") - @PreAuthorize("hasAuthority('REMOVE_MATERIAL_TYPE')") + @PreAuthorize("hasAuthority('REMOVE_MATERIAL_TYPEs')") fun deleteById(@PathVariable id: Long) = noContent { materialTypeService.deleteById(id) diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/RecipeController.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/RecipeController.kt index c54caa8..cb6bf29 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/RecipeController.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/RecipeController.kt @@ -29,27 +29,28 @@ class RecipeController(private val recipeService: RecipeService) { ok(recipeService.getById(id)) @PostMapping - @PreAuthorize("hasAuthority('EDIT_RECIPE')") + @PreAuthorize("hasAuthority('EDIT_RECIPES')") fun save(@Valid @RequestBody recipe: RecipeSaveDto) = created(RECIPE_CONTROLLER_PATH) { recipeService.save(recipe) } @PutMapping - @PreAuthorize("hasAuthority('EDIT_RECIPE')") + @PreAuthorize("hasAuthority('EDIT_RECIPES')") fun update(@Valid @RequestBody recipe: RecipeUpdateDto) = noContent { recipeService.update(recipe) } @PutMapping("public") + @PreAuthorize("hasAuthority('EDIT_RECIPES_PUBLIC_DATA')") fun updatePublicData(@Valid @RequestBody publicDataDto: RecipePublicDataDto) = noContent { recipeService.updatePublicData(publicDataDto) } @DeleteMapping("{id}") - @PreAuthorize("hasAuthority('REMOVE_RECIPE')") + @PreAuthorize("hasAuthority('REMOVE_RECIPES')") fun deleteById(@PathVariable id: Long) = noContent { recipeService.deleteById(id) @@ -69,14 +70,14 @@ class RecipeImageController(val recipeImageService: RecipeImageService) { ok(recipeImageService.getByIdForRecipe(id, recipeId)) @PostMapping("{recipeId}/image", consumes = [MediaType.MULTIPART_FORM_DATA_VALUE]) - @PreAuthorize("hasAuthority('EDIT_RECIPE')") + @PreAuthorize("hasAuthority('EDIT_RECIPES')") fun save(@PathVariable recipeId: Long, image: MultipartFile): ResponseEntity { val id = recipeImageService.save(image, recipeId) return ResponseEntity.created(URI.create("/$RECIPE_CONTROLLER_PATH/$recipeId/image/$id")).build() } @DeleteMapping("{recipeId}/image/{id}") - @PreAuthorize("hasAuthority('REMOVE_RECIPE')") + @PreAuthorize("hasAuthority('REMOVE_RECIPES')") fun delete(@PathVariable recipeId: Long, @PathVariable id: Long) = noContent { recipeImageService.delete(id, recipeId) @@ -92,21 +93,21 @@ class MixController(private val mixService: MixService) { ok(mixService.getById(id)) @PostMapping - @PreAuthorize("hasAuthority('EDIT_RECIPE')") + @PreAuthorize("hasAuthority('EDIT_RECIPES')") fun save(@Valid @RequestBody mix: MixSaveDto) = created(MIX_CONTROLLER_PATH) { mixService.save(mix) } @PutMapping - @PreAuthorize("hasAuthority('EDIT_RECIPE')") + @PreAuthorize("hasAuthority('EDIT_RECIPES')") fun update(@Valid @RequestBody mix: MixUpdateDto) = noContent { mixService.update(mix) } @DeleteMapping("{id}") - @PreAuthorize("hasAuthority('REMOVE_RECIPE')") + @PreAuthorize("hasAuthority('REMOVE_RECIPES')") fun deleteById(@PathVariable id: Long) = noContent { mixService.deleteById(id) diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/AccountService.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/AccountService.kt index 65f3ed1..48029c4 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/AccountService.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/AccountService.kt @@ -294,6 +294,6 @@ class EmployeeUserDetailsServiceImpl( ignoreDefaultGroupUsers = ignoreDefaultGroupUsers, ignoreSystemUsers = false ) - return User(employee.id.toString(), employee.password, employee.getAuthorities()) + return User(employee.id.toString(), employee.password, employee.authorities) } } From 6cad19b699d7fc83dbf14631e3d88cbdc642f461 Mon Sep 17 00:00:00 2001 From: FyloZ Date: Sun, 4 Apr 2021 22:38:28 -0400 Subject: [PATCH 3/5] =?UTF-8?q?Cr=C3=A9ation=20d'annotations=20pour=20les?= =?UTF-8?q?=20permissions=20couramment=20utilis=C3=A9es.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../annotations/PermissionAnnotations.kt | 45 +++++++++++++++++++ .../rest/AccountControllers.kt | 26 ++++++----- .../rest/CompanyController.kt | 3 +- .../rest/MaterialController.kt | 3 +- .../rest/MaterialTypeController.kt | 3 +- .../rest/RecipeController.kt | 25 ++++++----- 6 files changed, 80 insertions(+), 25 deletions(-) create mode 100644 src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/annotations/PermissionAnnotations.kt diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/annotations/PermissionAnnotations.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/annotations/PermissionAnnotations.kt new file mode 100644 index 0000000..04ff417 --- /dev/null +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/annotations/PermissionAnnotations.kt @@ -0,0 +1,45 @@ +package dev.fyloz.colorrecipesexplorer.config.annotations + +import org.springframework.security.access.prepost.PreAuthorize + +@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +@MustBeDocumented +@PreAuthorize("hasAuthority('VIEW_RECIPES')") +annotation class PreAuthorizeViewRecipes + +@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +@MustBeDocumented +@PreAuthorize("hasAuthority('EDIT_RECIPES')") +annotation class PreAuthorizeEditRecipes + +@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +@MustBeDocumented +@PreAuthorize("hasAuthority('REMOVE_RECIPES')") +annotation class PreAuthorizeRemoveRecipes + +@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +@MustBeDocumented +@PreAuthorize("hasAuthority('VIEW_CATALOG')") +annotation class PreAuthorizeViewCatalog + +@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +@MustBeDocumented +@PreAuthorize("hasAuthority('VIEW_USERS')") +annotation class PreAuthorizeViewUsers + +@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +@MustBeDocumented +@PreAuthorize("hasAuthority('EDIT_USERS')") +annotation class PreAuthorizeEditUsers + +@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +@MustBeDocumented +@PreAuthorize("hasAuthority('REMOVE_USERS')") +annotation class PreAuthorizeRemoveUsers diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/AccountControllers.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/AccountControllers.kt index 0ea008a..8ea1417 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/AccountControllers.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/AccountControllers.kt @@ -1,5 +1,8 @@ package dev.fyloz.colorrecipesexplorer.rest +import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeEditUsers +import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeRemoveUsers +import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeViewUsers import dev.fyloz.colorrecipesexplorer.model.* import dev.fyloz.colorrecipesexplorer.service.EmployeeGroupServiceImpl import dev.fyloz.colorrecipesexplorer.service.EmployeeService @@ -16,13 +19,14 @@ private const val EMPLOYEE_GROUP_CONTROLLER_PATH = "api/employee/group" @RestController @RequestMapping(EMPLOYEE_CONTROLLER_PATH) -@PreAuthorize("hasAuthority('VIEW_USERS')") class EmployeeController(private val employeeService: EmployeeService) { @GetMapping + @PreAuthorizeViewUsers fun getAll() = ok(employeeService.getAll()) @GetMapping("{id}") + @PreAuthorizeViewUsers fun getById(@PathVariable id: Long) = ok(employeeService.getById(id)) @@ -40,28 +44,28 @@ class EmployeeController(private val employeeService: EmployeeService) { forbidden() @PostMapping - @PreAuthorize("hasAuthority('EDIT_USERS')") + @PreAuthorizeEditUsers fun save(@Valid @RequestBody employee: EmployeeSaveDto) = created(EMPLOYEE_CONTROLLER_PATH) { employeeService.save(employee) } @PutMapping - @PreAuthorize("hasAuthority('EDIT_USERS')") + @PreAuthorizeEditUsers fun update(@Valid @RequestBody employee: EmployeeUpdateDto) = noContent { employeeService.update(employee) } @PutMapping("{id}/password", consumes = [MediaType.TEXT_PLAIN_VALUE]) - @PreAuthorize("hasAuthority('EDIT_USERS')") + @PreAuthorizeEditUsers fun updatePassword(@PathVariable id: Long, @RequestBody password: String) = noContent { employeeService.updatePassword(id, password) } @PutMapping("{employeeId}/permissions/{permission}") - @PreAuthorize("hasAuthority('EDIT_USERS')") + @PreAuthorizeEditUsers fun addPermission( @PathVariable employeeId: Long, @PathVariable permission: EmployeePermission @@ -70,7 +74,7 @@ class EmployeeController(private val employeeService: EmployeeService) { } @DeleteMapping("{employeeId}/permissions/{permission}") - @PreAuthorize("hasAuthority('EDIT_USERS')") + @PreAuthorizeEditUsers fun removePermission( @PathVariable employeeId: Long, @PathVariable permission: EmployeePermission @@ -79,14 +83,14 @@ class EmployeeController(private val employeeService: EmployeeService) { } @DeleteMapping("{id}") - @PreAuthorize("hasAuthority('REMOVE_USERS')") + @PreAuthorizeRemoveUsers fun deleteById(@PathVariable id: Long) = employeeService.deleteById(id) } @RestController @RequestMapping(EMPLOYEE_GROUP_CONTROLLER_PATH) -@PreAuthorize("hasAuthority('VIEW_USERS')") +@PreAuthorizeViewUsers class GroupsController(private val groupService: EmployeeGroupServiceImpl) { @GetMapping fun getAll() = @@ -111,21 +115,21 @@ class GroupsController(private val groupService: EmployeeGroupServiceImpl) { ok(groupService.getRequestDefaultGroup(request)) @PostMapping - @PreAuthorize("hasAuthority('EDIT_USERS')") + @PreAuthorizeEditUsers fun save(@Valid @RequestBody group: EmployeeGroupSaveDto) = created(EMPLOYEE_GROUP_CONTROLLER_PATH) { groupService.save(group) } @PutMapping - @PreAuthorize("hasAuthority('EDIT_USERS')") + @PreAuthorizeEditUsers fun update(@Valid @RequestBody group: EmployeeGroupUpdateDto) = noContent { groupService.update(group) } @DeleteMapping("{id}") - @PreAuthorize("hasAuthority('REMOVE_USERS')") + @PreAuthorizeRemoveUsers fun deleteById(@PathVariable id: Long) = noContent { groupService.deleteById(id) diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/CompanyController.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/CompanyController.kt index de76240..bf690b4 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/CompanyController.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/CompanyController.kt @@ -1,5 +1,6 @@ package dev.fyloz.colorrecipesexplorer.rest +import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeViewCatalog import dev.fyloz.colorrecipesexplorer.model.Company import dev.fyloz.colorrecipesexplorer.model.CompanySaveDto import dev.fyloz.colorrecipesexplorer.model.CompanyUpdateDto @@ -12,7 +13,7 @@ private const val COMPANY_CONTROLLER_PATH = "api/company" @RestController @RequestMapping(COMPANY_CONTROLLER_PATH) -@PreAuthorize("hasAuthority('VIEW_CATALOG')") +@PreAuthorizeViewCatalog class CompanyController(private val companyService: CompanyService) { @GetMapping fun getAll() = diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/MaterialController.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/MaterialController.kt index 64a5e10..b4b2745 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/MaterialController.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/MaterialController.kt @@ -1,5 +1,6 @@ package dev.fyloz.colorrecipesexplorer.rest +import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeViewCatalog import dev.fyloz.colorrecipesexplorer.model.* import dev.fyloz.colorrecipesexplorer.service.MaterialService import org.springframework.http.MediaType @@ -13,7 +14,7 @@ private const val MATERIAL_CONTROLLER_PATH = "api/material" @RestController @RequestMapping(MATERIAL_CONTROLLER_PATH) -@PreAuthorize("hasAuthority('VIEW_CATALOG')") +@PreAuthorizeViewCatalog class MaterialController(private val materialService: MaterialService) { @GetMapping fun getAll() = diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/MaterialTypeController.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/MaterialTypeController.kt index 03cc9fe..fb239a4 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/MaterialTypeController.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/MaterialTypeController.kt @@ -1,5 +1,6 @@ package dev.fyloz.colorrecipesexplorer.rest +import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeViewCatalog import dev.fyloz.colorrecipesexplorer.model.MaterialType import dev.fyloz.colorrecipesexplorer.model.MaterialTypeSaveDto import dev.fyloz.colorrecipesexplorer.model.MaterialTypeUpdateDto @@ -12,7 +13,7 @@ private const val MATERIAL_TYPE_CONTROLLER_PATH = "api/materialtype" @RestController @RequestMapping(MATERIAL_TYPE_CONTROLLER_PATH) -@PreAuthorize("hasAuthority('VIEW_CATALOG')") +@PreAuthorizeViewCatalog class MaterialTypeController(private val materialTypeService: MaterialTypeService) { @GetMapping fun getAll() = diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/RecipeController.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/RecipeController.kt index cb6bf29..0d81d5d 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/RecipeController.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/RecipeController.kt @@ -1,5 +1,8 @@ package dev.fyloz.colorrecipesexplorer.rest +import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeEditRecipes +import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeRemoveRecipes +import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeViewRecipes import dev.fyloz.colorrecipesexplorer.model.* import dev.fyloz.colorrecipesexplorer.service.MixService import dev.fyloz.colorrecipesexplorer.service.RecipeImageService @@ -18,7 +21,7 @@ private const val MIX_CONTROLLER_PATH = "api/recipe/mix" @RestController @RequestMapping(RECIPE_CONTROLLER_PATH) -@PreAuthorize("hasAuthority('VIEW_RECIPE')") +@PreAuthorizeViewRecipes class RecipeController(private val recipeService: RecipeService) { @GetMapping fun getAll() = @@ -29,14 +32,14 @@ class RecipeController(private val recipeService: RecipeService) { ok(recipeService.getById(id)) @PostMapping - @PreAuthorize("hasAuthority('EDIT_RECIPES')") + @PreAuthorizeEditRecipes fun save(@Valid @RequestBody recipe: RecipeSaveDto) = created(RECIPE_CONTROLLER_PATH) { recipeService.save(recipe) } @PutMapping - @PreAuthorize("hasAuthority('EDIT_RECIPES')") + @PreAuthorizeEditRecipes fun update(@Valid @RequestBody recipe: RecipeUpdateDto) = noContent { recipeService.update(recipe) @@ -50,7 +53,7 @@ class RecipeController(private val recipeService: RecipeService) { } @DeleteMapping("{id}") - @PreAuthorize("hasAuthority('REMOVE_RECIPES')") + @PreAuthorizeRemoveRecipes fun deleteById(@PathVariable id: Long) = noContent { recipeService.deleteById(id) @@ -59,7 +62,7 @@ class RecipeController(private val recipeService: RecipeService) { @RestController @RequestMapping(RECIPE_CONTROLLER_PATH) -@PreAuthorize("hasAuthority('VIEW_RECIPE')") +@PreAuthorizeViewRecipes class RecipeImageController(val recipeImageService: RecipeImageService) { @GetMapping("{recipeId}/image") fun getAllIdsForRecipe(@PathVariable recipeId: Long) = @@ -70,14 +73,14 @@ class RecipeImageController(val recipeImageService: RecipeImageService) { ok(recipeImageService.getByIdForRecipe(id, recipeId)) @PostMapping("{recipeId}/image", consumes = [MediaType.MULTIPART_FORM_DATA_VALUE]) - @PreAuthorize("hasAuthority('EDIT_RECIPES')") + @PreAuthorizeEditRecipes fun save(@PathVariable recipeId: Long, image: MultipartFile): ResponseEntity { val id = recipeImageService.save(image, recipeId) return ResponseEntity.created(URI.create("/$RECIPE_CONTROLLER_PATH/$recipeId/image/$id")).build() } @DeleteMapping("{recipeId}/image/{id}") - @PreAuthorize("hasAuthority('REMOVE_RECIPES')") + @PreAuthorizeRemoveRecipes fun delete(@PathVariable recipeId: Long, @PathVariable id: Long) = noContent { recipeImageService.delete(id, recipeId) @@ -86,28 +89,28 @@ class RecipeImageController(val recipeImageService: RecipeImageService) { @RestController @RequestMapping(MIX_CONTROLLER_PATH) -@PreAuthorize("hasAuthority('VIEW_RECIPE')") +@PreAuthorizeViewRecipes class MixController(private val mixService: MixService) { @GetMapping("{id}") fun getById(@PathVariable id: Long) = ok(mixService.getById(id)) @PostMapping - @PreAuthorize("hasAuthority('EDIT_RECIPES')") + @PreAuthorizeEditRecipes fun save(@Valid @RequestBody mix: MixSaveDto) = created(MIX_CONTROLLER_PATH) { mixService.save(mix) } @PutMapping - @PreAuthorize("hasAuthority('EDIT_RECIPES')") + @PreAuthorizeEditRecipes fun update(@Valid @RequestBody mix: MixUpdateDto) = noContent { mixService.update(mix) } @DeleteMapping("{id}") - @PreAuthorize("hasAuthority('REMOVE_RECIPES')") + @PreAuthorizeRemoveRecipes fun deleteById(@PathVariable id: Long) = noContent { mixService.deleteById(id) From 490d4a00274327859586b0dc9bbb4152a48fbf28 Mon Sep 17 00:00:00 2001 From: FyloZ Date: Sun, 4 Apr 2021 23:39:30 -0400 Subject: [PATCH 4/5] =?UTF-8?q?Mise=20=C3=A0=20jour=20du=20nom=20de=20la?= =?UTF-8?q?=20permission=20'PRINT=5FMIX'=20vers=20'PRINT=5FMIXES'.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dev/fyloz/colorrecipesexplorer/model/AccountModel.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/AccountModel.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/AccountModel.kt index 4343a76..4c99119 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/AccountModel.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/AccountModel.kt @@ -183,7 +183,7 @@ enum class EmployeePermission( VIEW_CATALOG, VIEW_USERS, - PRINT_MIX(listOf(VIEW_RECIPES)), + PRINT_MIXES(listOf(VIEW_RECIPES)), EDIT_RECIPES_PUBLIC_DATA(listOf(VIEW_RECIPES)), EDIT_RECIPES(listOf(EDIT_RECIPES_PUBLIC_DATA)), @@ -209,7 +209,7 @@ enum class EmployeePermission( REMOVE_USERS, REMOVE_CATALOG, - PRINT_MIX, + PRINT_MIXES, ADD_TO_INVENTORY, DEDUCT_FROM_INVENTORY ) From b925cdd02ae5abb5da17f063c5dfb3dda777f9b5 Mon Sep 17 00:00:00 2001 From: FyloZ Date: Mon, 5 Apr 2021 19:27:23 -0400 Subject: [PATCH 5/5] Corrections de certaines permissions --- .../dev/fyloz/colorrecipesexplorer/model/AccountModel.kt | 2 ++ .../fyloz/colorrecipesexplorer/rest/AccountControllers.kt | 6 +++++- .../dev/fyloz/colorrecipesexplorer/service/RecipeService.kt | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/AccountModel.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/AccountModel.kt index 4c99119..1f680f1 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/AccountModel.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/AccountModel.kt @@ -205,6 +205,8 @@ enum class EmployeePermission( ADMIN( listOf( + EDIT_CATALOG, + REMOVE_RECIPES, REMOVE_USERS, REMOVE_CATALOG, diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/AccountControllers.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/AccountControllers.kt index 8ea1417..8e89f85 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/AccountControllers.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/AccountControllers.kt @@ -90,27 +90,31 @@ class EmployeeController(private val employeeService: EmployeeService) { @RestController @RequestMapping(EMPLOYEE_GROUP_CONTROLLER_PATH) -@PreAuthorizeViewUsers class GroupsController(private val groupService: EmployeeGroupServiceImpl) { @GetMapping + @PreAuthorize("hasAnyAuthority('VIEW_RECIPES', 'VIEW_USERS')") fun getAll() = ok(groupService.getAll()) @GetMapping("{id}") + @PreAuthorizeViewUsers fun getById(@PathVariable id: Long) = ok(groupService.getById(id)) @GetMapping("{id}/employees") + @PreAuthorizeViewUsers fun getEmployeesForGroup(@PathVariable id: Long) = ok(groupService.getEmployeesForGroup(id)) @PostMapping("default/{groupId}") + @PreAuthorizeViewUsers fun setDefaultGroup(@PathVariable groupId: Long, response: HttpServletResponse) = noContent { groupService.setResponseDefaultGroup(groupId, response) } @GetMapping("default") + @PreAuthorizeViewUsers fun getRequestDefaultGroup(request: HttpServletRequest) = ok(groupService.getRequestDefaultGroup(request)) diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeService.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeService.kt index 0f35fe8..1173456 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeService.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeService.kt @@ -102,6 +102,7 @@ class RecipeServiceImpl( return updatedGroupsInformation } + @Transactional override fun updatePublicData(publicDataDto: RecipePublicDataDto) { if (publicDataDto.notes != null) { val recipe = getById(publicDataDto.recipeId)