New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Mrc-5298-Role Update Endpoints #64
Conversation
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #64 +/- ##
=======================================
Coverage 93.98% 93.98%
=======================================
Files 67 67
Lines 532 532
Branches 131 131
=======================================
Hits 500 500
Misses 30 30
Partials 2 2 ☔ View full report in Codecov by Sentry. |
… in RolePermission constructor
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you explain a bit more about the need for equality/hash checking for the role permissions? Is it related to trying to get hibernate to handle the deletion of the rows in the link table? Or was it to support this line in RoleService:
if (rolePermissionsToAdd.any { role.rolePermissions.contains(it) })
...
or in RolePermissionService
val matchedPermission = role.rolePermissions.find { rolePermissionToRemove == it }
?
Those would be easier if you were dealing with the data classes (dto's) which would support equality automatically.
|
||
return ResponseEntity.ok(mapOf("message" to "Role deleted")) | ||
} | ||
|
||
@PutMapping("/add-permissions/{roleName}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These endpoints don't seem very RESTful.
Would usually be something like
POST or DELETE /role/{name}/permission
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
its not really a post or delete because we are not creating/deleting the role. We are updating the role object i.e its a update operation. technically should be patch but spring didn't like patch - said it doesn't accept them
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe not POST, but should at least be PUT and DELETE then, as described here:
https://www.mscharhag.com/api-design/rest-many-to-many-relations
REST is generally disapproving of using verbs like "add" which could be expressed as http verbs. I guess it's a bit muddier if you're not providing the GET equivalent, but I still think it would be better than embedding the verb in the endpoint name.
And even if we do keep add-permissions
, it should go after the roleName not before (route to the role, then say what you're going to do to it).
Is this discussion all irrelevant though, as didn't you say you were going to replace these with a single PUT update endpoint?
{ | ||
roleService.addPermissionsToRole(roleName, addRolePermissions) | ||
|
||
return ResponseEntity.ok(mapOf("message" to "Permissions added")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, not sure this is useful. Empty response or updated role would be more so.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yup agree have created ticjet fir all these as prev pr comment
data class UpdateRolePermission( | ||
@field:NotNull | ||
val permission: String, | ||
val packetId: String? = null, | ||
val tagId: Int? = null, | ||
val packetGroupId: Int? = null | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So we're only allowing global permissions when we create a role, but can update with scoped permissions? That seems inconsistent. Could accept a list of these on create role, rather than just permission names.. and then wouldn't this type also be the scoped permission type returned from the GET request too? ...oh, minus the id of course
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think for the create the FE most likely wont pass any permissions.. And here update is where it can pass in scoped if needed...The idea was it would be weird adding scoped permissions when creating a role as well... But adding a scoped permission when updating makes sense also.... This logic will be mirrored on the FE.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, but you still want to allow global permissions on create to make life easier for the admin cli/setup scripts?
I'm not really convinced it's a useful distinction. Setup might want to make roles with scoped permissions in some cases for example. I think it would be fine to make role create take no perms and require that to be done in a second request.
init | ||
{ | ||
val nonNullFields = listOf(packetId, tagId, packetGroupId).count { it != null } | ||
require(nonNullFields <= 1) { | ||
"Either all of packetId, tagId, packetGroupId should be null or only one of them should be not null" | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, this is nice, and I think we could still keep this anyway, even this type was also re-used as the RolePermission dto.
{ | ||
init | ||
{ | ||
val nonNullFields = listOf(packet, tag, packetGroup).count { it != null } | ||
require(nonNullFields <= 1) { | ||
"Either all of packet, tag, packetGroup should be null or only one of them should be not null" | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Repeated logic... Given that you've got this constraint on the db, and also in the dto, maybe you don't need it here as well. But if so, should be able to implement once and use from both types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah i thought that too but couldnt find a way to implement once and use both places.. Also they a bit different as one is ids and other is whole objects... And probs don't need but put here to for safety and mirror this constraint on DB and make more visible (as many people don't check db constraints haha)
val role = roleRepository.findByName(roleName) | ||
?: throw PackitException("roleNotFound", HttpStatus.BAD_REQUEST) | ||
rolePermissionService.removeRolePermissionsFromRole(role, removeRolePermissions) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems a bit odd to check that permissions being added don't yet exist here in RoleService
but that permissions deing deleted do exist, in RolePermissionService
service. Should both be in the same service, probably RolePermissionsService
{ | ||
override fun getRolePermissionsToUpdate( | ||
role: Role, | ||
addRolePermissions: List<UpdateRolePermission> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
addRolePermissions: List<UpdateRolePermission> | |
updateRolePermissions: List<UpdateRolePermission> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i named like that to differentiate the adding and deleting of role permissions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But you're using it for permissions being removed too:
https://github.com/mrc-ide/packit/pull/64/files#diff-91662b556b4c3ee7dc5037863b1a55465c6b8218de7a6675c404baec77f0fb66R56
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh yup was mistaken have updated
return addRolePermissions.map { addRolePermission -> | ||
val permission = permissionRepository.findByName(addRolePermission.permission) | ||
?: throw PackitException("permissionNotFound", HttpStatus.BAD_REQUEST) | ||
RolePermission( | ||
role = role, | ||
permission = permission, | ||
packet = addRolePermission.packetId?.let { | ||
packetRepository.findById(it) | ||
.orElseThrow { PackitException("packetNotFound", HttpStatus.BAD_REQUEST) } | ||
}, | ||
packetGroup = addRolePermission.packetGroupId?.let { | ||
packetGroupRepository.findById(it) | ||
.orElseThrow { PackitException("packetGroupNotFound", HttpStatus.BAD_REQUEST) } | ||
}, | ||
tag = addRolePermission.tagId?.let { | ||
tagRepository.findById(it).orElseThrow { PackitException("tagNotFound", HttpStatus.BAD_REQUEST) } | ||
} | ||
) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is quite dense visuall,y but makes sense on closer inspection.
I think an empty line at l.34 would make it more readable by splitting off the main chunk.
val rolePermissionsToRemove = getRolePermissionsToUpdate(role, removeRolePermissions) | ||
|
||
val matchedRolePermissionsToRemove = rolePermissionsToRemove.map { rolePermissionToRemove -> | ||
val matchedPermission = role.rolePermissions.find { rolePermissionToRemove == it } | ||
?: throw PackitException("rolePermissionDoesNotExist", HttpStatus.BAD_REQUEST) | ||
matchedPermission | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think you really need to do a map here, just a forEach
then just map the original rolePermissionsToRemove
to ids to pass to the deleteAllByIdIn
method
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unsure how the foreach would work here? and the benefits of doing it that way?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah sorry that doesn't work because you don't actually have the ID on the UpdateRolePermissions.
@@ -83,4 +97,50 @@ class RoleControllerTest : IntegrationTest() | |||
assertSuccess(result) | |||
assertNull(roleRepository.findByName("testRole")) | |||
} | |||
|
|||
@Test | |||
@WithAuthenticatedUser(authorities = ["user.manage"]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could test for unauthorized request too.
Yup so pretty much used to compare equality fo the entities.. For delete we need to filter out some out and things just in general for match... In general good to have them for these as can be used for comparison.. Sadly if we were to use dtos we would have to first convert and then compare and then convert back to entity |
@PutMapping("/add-permissions/{roleName}") | ||
fun addPermissionsToRole( | ||
@RequestBody @Validated addRolePermissions: List<UpdateRolePermission>, | ||
@PutMapping("/update-permissions/{roleName}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, this overrides my previous comment!
I think this should just be PUT /role/{roleName}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
have done for all PUT as suggested as above in PR #68.. /role/{rolenmame}/permissions & /role/{rolenmame}/users
rolePermissionRepository.deleteAllByIdIn(matchedRolePermissionsToRemove.map { it.id!! }) | ||
} | ||
|
||
override fun getAddRolePermissionsFromRole( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's quite confusing having getRolePermissionsToUpdate and getAddRolePermissionsFromRole (that sounds like it's getting the permissions from the role, but if the permissions are already in the role, you're going to throw an exception!). I wouldn't mind just making the method into a checkPermissionsToAddAreNotOnRole or something, and have the main update method call `getRolePermissionsToUpdate directly, and pass the perms into here to just do the check...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yup agree ended up changing to getRolePermissionsToAdd
and making the getRolePermissionsToUpdate
internal so not visible to public
Mrc-5296 Role read
The work here contains 2 endpoints that encompass role update. They are
addPermissionsToRole
andremovePermissionsToRole
. They respectively add and remove permissions from a role ..NOTE:
For removePermissionsToRole we had to delete the rolepermission rows directly. Hibernate didn't work with just removing the rolePermission from the role. I will create a new ticket that can come back and investigate this further