Mise à jour des permissions

This commit is contained in:
FyloZ 2021-04-04 22:23:30 -04:00
parent 1bd0c94a4d
commit c374d76442
9 changed files with 120 additions and 116 deletions

View File

@ -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

View File

@ -61,8 +61,19 @@ data class Employee(
@Column(name = "last_login_time")
var lastLoginTime: LocalDateTime? = null
) : Model {
@JsonProperty("permissions")
fun getFlattenedPermissions(): Iterable<EmployeePermission> = getPermissions()
@get:JsonProperty("permissions")
val flatPermissions: Set<EmployeePermission>
get() = permissions
.flatMap { it.flat() }
.filter { !it.deprecated }
.toMutableSet()
.apply {
if (group != null) this.addAll(group!!.flatPermissions)
}
@get:JsonIgnore
val authorities: Set<GrantedAuthority>
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<EmployeePermission> = mutableSetOf(),
) : NamedModel
) : NamedModel {
@get:JsonProperty("permissions")
val flatPermissions: Set<EmployeePermission>
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<EmployeePermission> = 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<EmployeePermission> = 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<GrantedAuthority> {
return getPermissions().map { it.toAuthority() }.toMutableSet()
}
/** Gets [EmployeePermission]s of the given [Employee]. */
fun Employee.getPermissions(): Iterable<EmployeePermission> {
val grantedPermissions: MutableSet<EmployeePermission> = mutableSetOf()
if (group != null) grantedPermissions.addAll(group!!.permissions.flatMap { it.flat() })
grantedPermissions.addAll(permissions.flatMap { it.flat() })
return grantedPermissions
}
private fun EmployeePermission.flat(): Iterable<EmployeePermission> {
fun EmployeePermission.flat(): Iterable<EmployeePermission> {
return mutableSetOf(this).apply {
impliedPermissions.forEach {
addAll(it.flat())

View File

@ -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>(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<EmployeeGroup>(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)

View File

@ -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>(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)

View File

@ -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<MaterialQuantityDto>): ResponseEntity<Collection<MaterialQuantityDto>> {
return ResponseEntity.ok(inventoryService.add(quantities))
}
@PutMapping("deduct")
@PreAuthorize("hasAuthority('VIEW_RECIPE')")
@PreAuthorize("hasAuthority('DEDUCT_FROM_INVENTORY')")
fun deduct(@RequestBody quantities: Collection<MaterialQuantityDto>) =
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))
}

View File

@ -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>(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)

View File

@ -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<MaterialType>(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)

View File

@ -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>(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<Void> {
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>(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)

View File

@ -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)
}
}