From 1876501b3326c9d7fe60b1eb77353d136ddec7c5 Mon Sep 17 00:00:00 2001 From: Ben Yip Date: Fri, 28 Jun 2024 09:36:57 +0100 Subject: [PATCH] CORE-20731: Implement DeleteUser endpoint (#6224) --- .../v1/user/impl/UserEndpointImpl.kt | 15 +++++-- .../v1/user/impl/UserEndpointImplTest.kt | 29 ++++++++++++-- .../manager/impl/PermissionUserManagerImpl.kt | 18 +++++++++ .../impl/PermissionUserManagerImplTest.kt | 33 +++++++++++++++ .../manager/PermissionUserManager.kt | 6 +++ .../manager/request/DeleteUserRequestDto.kt | 12 ++++++ .../impl/PermissionStorageReaderImpl.kt | 4 ++ .../storage/reader/PermissionStorageReader.kt | 7 ++++ .../PermissionStorageWriterProcessorImpl.kt | 7 ++++ .../storage/writer/impl/user/UserWriter.kt | 9 +++++ .../writer/impl/user/impl/UserWriterImpl.kt | 40 ++++++++++++++++++- ...ermissionStorageWriterProcessorImplTest.kt | 29 ++++++++++++++ .../writer/impl/user/UserWriterImplTest.kt | 36 +++++++++++++++++ 13 files changed, 237 insertions(+), 8 deletions(-) create mode 100644 libs/permissions/permission-manager/src/main/kotlin/net/corda/libs/permissions/manager/request/DeleteUserRequestDto.kt diff --git a/components/permissions/permission-rest-resource-impl/src/main/kotlin/net/corda/libs/permissions/endpoints/v1/user/impl/UserEndpointImpl.kt b/components/permissions/permission-rest-resource-impl/src/main/kotlin/net/corda/libs/permissions/endpoints/v1/user/impl/UserEndpointImpl.kt index 1ba9113d4e2..dadb9e3554f 100644 --- a/components/permissions/permission-rest-resource-impl/src/main/kotlin/net/corda/libs/permissions/endpoints/v1/user/impl/UserEndpointImpl.kt +++ b/components/permissions/permission-rest-resource-impl/src/main/kotlin/net/corda/libs/permissions/endpoints/v1/user/impl/UserEndpointImpl.kt @@ -14,6 +14,7 @@ import net.corda.libs.permissions.endpoints.v1.user.types.UserResponseType import net.corda.libs.permissions.manager.PermissionManager import net.corda.libs.permissions.manager.request.AddRoleToUserRequestDto import net.corda.libs.permissions.manager.request.ChangeUserPasswordDto +import net.corda.libs.permissions.manager.request.DeleteUserRequestDto import net.corda.libs.permissions.manager.request.GetPermissionSummaryRequestDto import net.corda.libs.permissions.manager.request.GetRoleRequestDto import net.corda.libs.permissions.manager.request.GetUserRequestDto @@ -126,12 +127,18 @@ class UserEndpointImpl @Activate constructor( return ResponseEntity.created(createUserResult.convertToEndpointType()) } - override fun getUserPath(loginName: String): UserResponseType { - return doGetUser(loginName) + override fun deleteUser(loginName: String): ResponseEntity { + val principal = getRestThreadLocalContext() + + val userResponseDto = withPermissionManager(permissionManagementService.permissionManager, logger) { + deleteUser(DeleteUserRequestDto(principal, loginName.lowercase())) + } + + return ResponseEntity.deleted(userResponseDto.convertToEndpointType()) } - override fun deleteUser(loginName: String): ResponseEntity { - TODO("To be implemented in CORE-20731") + override fun getUserPath(loginName: String): UserResponseType { + return doGetUser(loginName) } private fun doGetUser(loginName: String): UserResponseType { diff --git a/components/permissions/permission-rest-resource-impl/src/test/kotlin/net/corda/libs/permissions/endpoints/v1/user/impl/UserEndpointImplTest.kt b/components/permissions/permission-rest-resource-impl/src/test/kotlin/net/corda/libs/permissions/endpoints/v1/user/impl/UserEndpointImplTest.kt index 31a0f009446..94ea204df96 100644 --- a/components/permissions/permission-rest-resource-impl/src/test/kotlin/net/corda/libs/permissions/endpoints/v1/user/impl/UserEndpointImplTest.kt +++ b/components/permissions/permission-rest-resource-impl/src/test/kotlin/net/corda/libs/permissions/endpoints/v1/user/impl/UserEndpointImplTest.kt @@ -4,6 +4,7 @@ import net.corda.libs.permissions.endpoints.v1.user.types.CreateUserType import net.corda.libs.permissions.manager.PermissionManager import net.corda.libs.permissions.manager.request.AddRoleToUserRequestDto import net.corda.libs.permissions.manager.request.CreateUserRequestDto +import net.corda.libs.permissions.manager.request.DeleteUserRequestDto import net.corda.libs.permissions.manager.request.GetUserRequestDto import net.corda.libs.permissions.manager.request.RemoveRoleFromUserRequestDto import net.corda.libs.permissions.manager.response.RoleAssociationResponseDto @@ -19,7 +20,7 @@ import net.corda.rest.exception.InvalidInputDataException import net.corda.rest.exception.ResourceNotFoundException import net.corda.rest.security.CURRENT_REST_CONTEXT import net.corda.rest.security.RestAuthContext -import org.assertj.core.api.Assertions +import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNotNull import org.junit.jupiter.api.Assertions.assertTrue @@ -30,7 +31,6 @@ import org.mockito.kotlin.any import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.mock import org.mockito.kotlin.whenever -import java.lang.IllegalArgumentException import java.time.Instant import java.util.UUID @@ -136,6 +136,29 @@ internal class UserEndpointImplTest { assertEquals(parentGroup, responseType.parentGroup) } + @Test + fun `delete a user successfully`() { + val deleteUserDtoCapture = argumentCaptor() + whenever(lifecycleCoordinator.isRunning).thenReturn(true) + whenever(permissionService.isRunning).thenReturn(true) + whenever(permissionManager.deleteUser(deleteUserDtoCapture.capture())).thenReturn(userResponseDto) + + endpoint.start() + val response = endpoint.deleteUser("loginName1") + val responseType = response.responseBody + + assertEquals(ResponseCode.OK, response.responseCode) + assertNotNull(responseType) + assertEquals("uuid", responseType.id) + assertEquals(0, responseType.version) + assertEquals(now, responseType.updateTimestamp) + assertEquals("fullName1", responseType.fullName) + assertEquals("loginName1", responseType.loginName) + assertEquals(true, responseType.enabled) + assertEquals(now, responseType.passwordExpiry) + assertEquals(parentGroup, responseType.parentGroup) + } + @Test fun `get a user throws with resource not found exception when the user isn't found`() { val getUserRequestDtoCapture = argumentCaptor() @@ -263,7 +286,7 @@ internal class UserEndpointImplTest { whenever(permissionService.isRunning).thenReturn(true) endpoint.start() - Assertions.assertThatThrownBy { + assertThatThrownBy { endpoint.createUser(createUserType.copy(loginName = "foo/bar")) }.isInstanceOf(InvalidInputDataException::class.java) .hasMessageContaining("Invalid input data for user creation.") diff --git a/libs/permissions/permission-manager-impl/src/main/kotlin/net/corda/libs/permissions/manager/impl/PermissionUserManagerImpl.kt b/libs/permissions/permission-manager-impl/src/main/kotlin/net/corda/libs/permissions/manager/impl/PermissionUserManagerImpl.kt index fe3933b1b90..08a91439096 100644 --- a/libs/permissions/permission-manager-impl/src/main/kotlin/net/corda/libs/permissions/manager/impl/PermissionUserManagerImpl.kt +++ b/libs/permissions/permission-manager-impl/src/main/kotlin/net/corda/libs/permissions/manager/impl/PermissionUserManagerImpl.kt @@ -6,6 +6,7 @@ import net.corda.data.permissions.management.PermissionManagementResponse import net.corda.data.permissions.management.user.AddRoleToUserRequest import net.corda.data.permissions.management.user.ChangeUserPasswordRequest import net.corda.data.permissions.management.user.CreateUserRequest +import net.corda.data.permissions.management.user.DeleteUserRequest import net.corda.data.permissions.management.user.RemoveRoleFromUserRequest import net.corda.libs.configuration.SmartConfig import net.corda.libs.permissions.management.cache.PermissionManagementCache @@ -15,6 +16,7 @@ import net.corda.libs.permissions.manager.impl.converter.convertToResponseDto import net.corda.libs.permissions.manager.request.AddRoleToUserRequestDto import net.corda.libs.permissions.manager.request.ChangeUserPasswordDto import net.corda.libs.permissions.manager.request.CreateUserRequestDto +import net.corda.libs.permissions.manager.request.DeleteUserRequestDto import net.corda.libs.permissions.manager.request.GetPermissionSummaryRequestDto import net.corda.libs.permissions.manager.request.GetUserRequestDto import net.corda.libs.permissions.manager.request.RemoveRoleFromUserRequestDto @@ -78,6 +80,22 @@ class PermissionUserManagerImpl( return cachedUser.convertToResponseDto() } + override fun deleteUser(deleteUserRequestDto: DeleteUserRequestDto): UserResponseDto { + val result = sendPermissionWriteRequest( + rpcSender, + writerTimeout, + PermissionManagementRequest( + deleteUserRequestDto.requestedBy, + null, + DeleteUserRequest( + deleteUserRequestDto.loginName + ) + ) + ) + + return result.convertToResponseDto() + } + override fun changeUserPasswordSelf(changeUserPasswordDto: ChangeUserPasswordDto): UserResponseDto = changeUserPassword(changeUserPasswordDto, selfUserPasswordExpiryDays) diff --git a/libs/permissions/permission-manager-impl/src/test/kotlin/net/corda/libs/permissions/manager/impl/PermissionUserManagerImplTest.kt b/libs/permissions/permission-manager-impl/src/test/kotlin/net/corda/libs/permissions/manager/impl/PermissionUserManagerImplTest.kt index 8290a2b92ce..1f82fe8ff93 100644 --- a/libs/permissions/permission-manager-impl/src/test/kotlin/net/corda/libs/permissions/manager/impl/PermissionUserManagerImplTest.kt +++ b/libs/permissions/permission-manager-impl/src/test/kotlin/net/corda/libs/permissions/manager/impl/PermissionUserManagerImplTest.kt @@ -9,6 +9,7 @@ import net.corda.data.permissions.management.PermissionManagementRequest import net.corda.data.permissions.management.PermissionManagementResponse import net.corda.data.permissions.management.user.AddRoleToUserRequest import net.corda.data.permissions.management.user.CreateUserRequest +import net.corda.data.permissions.management.user.DeleteUserRequest import net.corda.data.permissions.management.user.RemoveRoleFromUserRequest import net.corda.libs.configuration.SmartConfig import net.corda.libs.configuration.SmartConfigImpl @@ -17,6 +18,7 @@ import net.corda.libs.permissions.manager.exception.UnexpectedPermissionResponse import net.corda.libs.permissions.manager.request.AddRoleToUserRequestDto import net.corda.libs.permissions.manager.request.ChangeUserPasswordDto import net.corda.libs.permissions.manager.request.CreateUserRequestDto +import net.corda.libs.permissions.manager.request.DeleteUserRequestDto import net.corda.libs.permissions.manager.request.GetUserRequestDto import net.corda.libs.permissions.manager.request.RemoveRoleFromUserRequestDto import net.corda.libs.permissions.validation.cache.PermissionValidationCache @@ -77,6 +79,7 @@ class PermissionUserManagerImplTest { parentGroup = parentGroup ) + private val deleteUserRequestDto = DeleteUserRequestDto(requestedBy = requestUserName, loginName = "loginname123") private val userCreationTime = Instant.now() private val getUserRequestDto = GetUserRequestDto(requestedBy = requestUserName, loginName = "loginname123") private val changeUserPasswordDto = ChangeUserPasswordDto("requestedBy", "loginname123", "mypassword") @@ -213,6 +216,36 @@ class PermissionUserManagerImplTest { assertThrows(UnexpectedPermissionResponseException::class.java) { manager.createUser(createUserRequestDto) } } + @Test + fun `delete a user sends rpc request and converts result`() { + val future = mock>() + whenever(future.getOrThrow(defaultTimeout)).thenReturn(permissionManagementResponse) + + val requestCaptor = argumentCaptor() + whenever(rpcSender.sendRequest(requestCaptor.capture())).thenReturn(future) + + val result = manager.deleteUser(deleteUserRequestDto) + + val capturedPermissionManagementRequest = requestCaptor.firstValue + assertEquals(requestUserName, capturedPermissionManagementRequest.requestUserId) + assertEquals(null, capturedPermissionManagementRequest.virtualNodeId) + + val capturedCreateUserRequest = capturedPermissionManagementRequest.request as DeleteUserRequest + assertEquals(deleteUserRequestDto.loginName, capturedCreateUserRequest.loginName) + + assertEquals(fullName, result.fullName) + assertEquals(avroUser.enabled, result.enabled) + assertEquals(avroUser.lastChangeDetails.updateTimestamp, result.lastUpdatedTimestamp) + assertEquals(false, result.ssoAuth) + assertEquals(avroUser.parentGroupId, result.parentGroup) + assertEquals(1, result.properties.size) + + val property = result.properties.first() + assertEquals(userProperty.lastChangeDetails.updateTimestamp, property.lastChangedTimestamp) + assertEquals(userProperty.key, property.key) + assertEquals(userProperty.value, property.value) + } + @Test fun `get a user uses the cache and converts avro user to dto`() { whenever(permissionManagementCache.getUser("loginname123")).thenReturn(avroUser) diff --git a/libs/permissions/permission-manager/src/main/kotlin/net/corda/libs/permissions/manager/PermissionUserManager.kt b/libs/permissions/permission-manager/src/main/kotlin/net/corda/libs/permissions/manager/PermissionUserManager.kt index 49ee2230ab7..ff73f3af17f 100644 --- a/libs/permissions/permission-manager/src/main/kotlin/net/corda/libs/permissions/manager/PermissionUserManager.kt +++ b/libs/permissions/permission-manager/src/main/kotlin/net/corda/libs/permissions/manager/PermissionUserManager.kt @@ -3,6 +3,7 @@ package net.corda.libs.permissions.manager import net.corda.libs.permissions.manager.request.AddRoleToUserRequestDto import net.corda.libs.permissions.manager.request.ChangeUserPasswordDto import net.corda.libs.permissions.manager.request.CreateUserRequestDto +import net.corda.libs.permissions.manager.request.DeleteUserRequestDto import net.corda.libs.permissions.manager.request.GetPermissionSummaryRequestDto import net.corda.libs.permissions.manager.request.GetUserRequestDto import net.corda.libs.permissions.manager.request.RemoveRoleFromUserRequestDto @@ -23,6 +24,11 @@ interface PermissionUserManager { */ fun getUser(userRequestDto: GetUserRequestDto): UserResponseDto? + /** + * Delete a user in the RBAC Permission System. + */ + fun deleteUser(deleteUserRequestDto: DeleteUserRequestDto): UserResponseDto + /** * Change a user's own password. */ diff --git a/libs/permissions/permission-manager/src/main/kotlin/net/corda/libs/permissions/manager/request/DeleteUserRequestDto.kt b/libs/permissions/permission-manager/src/main/kotlin/net/corda/libs/permissions/manager/request/DeleteUserRequestDto.kt new file mode 100644 index 00000000000..994b501ff08 --- /dev/null +++ b/libs/permissions/permission-manager/src/main/kotlin/net/corda/libs/permissions/manager/request/DeleteUserRequestDto.kt @@ -0,0 +1,12 @@ +package net.corda.libs.permissions.manager.request + +data class DeleteUserRequestDto( + /** + * ID of the user making the request. + */ + val requestedBy: String, + /** + * Login name of the User to delete. + */ + val loginName: String, +) diff --git a/libs/permissions/permission-storage-reader-impl/src/main/kotlin/net/corda/libs/permissions/storage/reader/impl/PermissionStorageReaderImpl.kt b/libs/permissions/permission-storage-reader-impl/src/main/kotlin/net/corda/libs/permissions/storage/reader/impl/PermissionStorageReaderImpl.kt index 3f2bb69a9c8..f67e49a5ede 100644 --- a/libs/permissions/permission-storage-reader-impl/src/main/kotlin/net/corda/libs/permissions/storage/reader/impl/PermissionStorageReaderImpl.kt +++ b/libs/permissions/permission-storage-reader-impl/src/main/kotlin/net/corda/libs/permissions/storage/reader/impl/PermissionStorageReaderImpl.kt @@ -73,6 +73,10 @@ class PermissionStorageReaderImpl( publisher.publish(listOf(Record(REST_PERM_USER_TOPIC, key = user.loginName, value = user))).single().getOrThrow() } + override fun publishDeletedUser(loginName: String) { + publisher.publish(listOf(Record(REST_PERM_USER_TOPIC, key = loginName, value = null))).single().getOrThrow() + } + override fun publishUpdatedRole(role: AvroRole) { publisher.publish(listOf(Record(REST_PERM_ROLE_TOPIC, key = role.id, value = role))).single().getOrThrow() } diff --git a/libs/permissions/permission-storage-reader/src/main/kotlin/net/corda/libs/permissions/storage/reader/PermissionStorageReader.kt b/libs/permissions/permission-storage-reader/src/main/kotlin/net/corda/libs/permissions/storage/reader/PermissionStorageReader.kt index 7330dea4bb6..2244cf3588a 100644 --- a/libs/permissions/permission-storage-reader/src/main/kotlin/net/corda/libs/permissions/storage/reader/PermissionStorageReader.kt +++ b/libs/permissions/permission-storage-reader/src/main/kotlin/net/corda/libs/permissions/storage/reader/PermissionStorageReader.kt @@ -30,6 +30,13 @@ interface PermissionStorageReader : Resource { */ fun publishUpdatedUser(user: AvroUser) + /** + * Broadcasts the deleted user onto the messaging bus. + * + * @param loginName The login name of the user to be deleted. + */ + fun publishDeletedUser(loginName: String) + /** * Broadcasts a new role onto the messaging bus. * diff --git a/libs/permissions/permission-storage-writer-impl/src/main/kotlin/net/corda/libs/permissions/storage/writer/impl/PermissionStorageWriterProcessorImpl.kt b/libs/permissions/permission-storage-writer-impl/src/main/kotlin/net/corda/libs/permissions/storage/writer/impl/PermissionStorageWriterProcessorImpl.kt index 24875108853..01fdeebc08c 100644 --- a/libs/permissions/permission-storage-writer-impl/src/main/kotlin/net/corda/libs/permissions/storage/writer/impl/PermissionStorageWriterProcessorImpl.kt +++ b/libs/permissions/permission-storage-writer-impl/src/main/kotlin/net/corda/libs/permissions/storage/writer/impl/PermissionStorageWriterProcessorImpl.kt @@ -13,6 +13,7 @@ import net.corda.data.permissions.management.role.RemovePermissionFromRoleReques import net.corda.data.permissions.management.user.AddRoleToUserRequest import net.corda.data.permissions.management.user.ChangeUserPasswordRequest import net.corda.data.permissions.management.user.CreateUserRequest +import net.corda.data.permissions.management.user.DeleteUserRequest import net.corda.data.permissions.management.user.RemoveRoleFromUserRequest import net.corda.libs.permissions.storage.reader.PermissionStorageReader import net.corda.libs.permissions.storage.writer.PermissionStorageWriterProcessor @@ -45,6 +46,12 @@ class PermissionStorageWriterProcessorImpl( permissionStorageReader.reconcilePermissionSummaries() avroUser } + is DeleteUserRequest -> { + val avroUser = userWriter.deleteUser(permissionRequest, request.requestUserId) + permissionStorageReader.publishDeletedUser(avroUser.loginName) + permissionStorageReader.reconcilePermissionSummaries() + avroUser + } is CreateRoleRequest -> { val avroRole = roleWriter.createRole(permissionRequest, request.requestUserId) permissionStorageReader.publishNewRole(avroRole) diff --git a/libs/permissions/permission-storage-writer-impl/src/main/kotlin/net/corda/libs/permissions/storage/writer/impl/user/UserWriter.kt b/libs/permissions/permission-storage-writer-impl/src/main/kotlin/net/corda/libs/permissions/storage/writer/impl/user/UserWriter.kt index 882d87f301e..3d28d74d054 100644 --- a/libs/permissions/permission-storage-writer-impl/src/main/kotlin/net/corda/libs/permissions/storage/writer/impl/user/UserWriter.kt +++ b/libs/permissions/permission-storage-writer-impl/src/main/kotlin/net/corda/libs/permissions/storage/writer/impl/user/UserWriter.kt @@ -3,6 +3,7 @@ package net.corda.libs.permissions.storage.writer.impl.user import net.corda.data.permissions.management.user.AddRoleToUserRequest import net.corda.data.permissions.management.user.ChangeUserPasswordRequest import net.corda.data.permissions.management.user.CreateUserRequest +import net.corda.data.permissions.management.user.DeleteUserRequest import net.corda.data.permissions.management.user.RemoveRoleFromUserRequest import net.corda.data.permissions.User as AvroUser @@ -18,6 +19,14 @@ interface UserWriter { */ fun createUser(request: CreateUserRequest, requestUserId: String): AvroUser + /** + * Delete a User entity and return its Avro representation. + * + * @param request DeleteUserRequest containing the information of the User to delete. + * @param requestUserId ID of the user who made the request. + */ + fun deleteUser(request: DeleteUserRequest, requestUserId: String): AvroUser + /** * Change the password field of a User entity and return its Avro representation. * diff --git a/libs/permissions/permission-storage-writer-impl/src/main/kotlin/net/corda/libs/permissions/storage/writer/impl/user/impl/UserWriterImpl.kt b/libs/permissions/permission-storage-writer-impl/src/main/kotlin/net/corda/libs/permissions/storage/writer/impl/user/impl/UserWriterImpl.kt index 310bcbdf01e..7b014876753 100644 --- a/libs/permissions/permission-storage-writer-impl/src/main/kotlin/net/corda/libs/permissions/storage/writer/impl/user/impl/UserWriterImpl.kt +++ b/libs/permissions/permission-storage-writer-impl/src/main/kotlin/net/corda/libs/permissions/storage/writer/impl/user/impl/UserWriterImpl.kt @@ -3,6 +3,7 @@ package net.corda.libs.permissions.storage.writer.impl.user.impl import net.corda.data.permissions.management.user.AddRoleToUserRequest import net.corda.data.permissions.management.user.ChangeUserPasswordRequest import net.corda.data.permissions.management.user.CreateUserRequest +import net.corda.data.permissions.management.user.DeleteUserRequest import net.corda.data.permissions.management.user.RemoveRoleFromUserRequest import net.corda.libs.permissions.storage.common.converter.toAvroUser import net.corda.libs.permissions.storage.writer.impl.user.UserWriter @@ -36,7 +37,7 @@ class UserWriterImpl( return entityManagerFactory.transaction { entityManager -> val validator = EntityValidationUtil(entityManager) - validator.validateUserDoesNotAlreadyExist(request.loginName) + validator.validateUserDoesNotAlreadyExist(loginName) val parentGroup = validator.validateAndGetOptionalParentGroup(request.parentGroupId) val user = persistNewUser(request, parentGroup, entityManager, requestUserId, loginName) @@ -44,6 +45,19 @@ class UserWriterImpl( } } + override fun deleteUser(request: DeleteUserRequest, requestUserId: String): AvroUser { + val loginName = request.loginName + log.debug { "Received request to delete user: $loginName" } + return entityManagerFactory.transaction { entityManager -> + + val validator = EntityValidationUtil(entityManager) + val user = validator.validateAndGetUniqueUser(loginName) + + val resultUser = removeUser(user, entityManager, requestUserId, loginName) + resultUser.toAvroUser() + } + } + override fun changeUserPassword( request: ChangeUserPasswordRequest, requestUserId: String @@ -139,6 +153,30 @@ class UserWriterImpl( return user } + private fun removeUser( + user: User, + entityManager: EntityManager, + requestUserId: String, + loginName: String + ): User { + val updateTimestamp = Instant.now() + + entityManager.remove(user) + + val auditLog = ChangeAudit( + id = UUID.randomUUID().toString(), + updateTimestamp = updateTimestamp, + actorUser = requestUserId, + changeType = RestPermissionOperation.USER_DELETE, + details = "User '${user.loginName}' deleted by '$requestUserId'." + ) + + entityManager.persist(auditLog) + + log.info("Successfully deleted user: $loginName.") + return user + } + private fun persistUserRoleAssociation(entityManager: EntityManager, requestUserId: String, user: User, role: Role): User { val updateTimestamp = Instant.now() val association = RoleUserAssociation(UUID.randomUUID().toString(), role, user, updateTimestamp) diff --git a/libs/permissions/permission-storage-writer-impl/src/test/kotlin/net/corda/libs/permissions/storage/writer/impl/PermissionStorageWriterProcessorImplTest.kt b/libs/permissions/permission-storage-writer-impl/src/test/kotlin/net/corda/libs/permissions/storage/writer/impl/PermissionStorageWriterProcessorImplTest.kt index 30ce5c13444..514857dcbdc 100644 --- a/libs/permissions/permission-storage-writer-impl/src/test/kotlin/net/corda/libs/permissions/storage/writer/impl/PermissionStorageWriterProcessorImplTest.kt +++ b/libs/permissions/permission-storage-writer-impl/src/test/kotlin/net/corda/libs/permissions/storage/writer/impl/PermissionStorageWriterProcessorImplTest.kt @@ -10,6 +10,7 @@ import net.corda.data.permissions.management.permission.CreatePermissionRequest import net.corda.data.permissions.management.role.CreateRoleRequest import net.corda.data.permissions.management.user.AddRoleToUserRequest import net.corda.data.permissions.management.user.CreateUserRequest +import net.corda.data.permissions.management.user.DeleteUserRequest import net.corda.data.permissions.management.user.RemoveRoleFromUserRequest import net.corda.libs.permissions.storage.reader.PermissionStorageReader import net.corda.libs.permissions.storage.writer.impl.permission.PermissionWriter @@ -41,6 +42,10 @@ class PermissionStorageWriterProcessorImplTest { loginName = "lankydan" enabled = true } + + private val deleteUserRequest = DeleteUserRequest().apply { + loginName = "lankydan" + } private val createRoleRequest = CreateRoleRequest("role1", null) private val createPermissionRequest = CreatePermissionRequest( @@ -52,6 +57,7 @@ class PermissionStorageWriterProcessorImplTest { private val creatorUserId = "creatorUserId" private val avroUser = AvroUser().apply { enabled = true + loginName = "lankydan" } private val avroRole = AvroRole() @@ -133,6 +139,29 @@ class PermissionStorageWriterProcessorImplTest { assertEquals("Entity manager error.", exception.errorMessage) } + @Test + fun `receiving DeleteUserRequest calls userWriter to delete user and publishes null user and completes future`() { + whenever(userWriter.deleteUser(deleteUserRequest, creatorUserId)).thenReturn(avroUser) + + val future = CompletableFuture() + processor.onNext( + request = PermissionManagementRequest().apply { + request = deleteUserRequest + requestUserId = creatorUserId + }, + respFuture = future + ) + + verify(permissionStorageReader, times(1)).publishDeletedUser(avroUser.loginName) + + val response = future.getOrThrow().response + assertTrue(response is AvroUser) + (response as? AvroUser)?.let { user -> + assertEquals(avroUser, user) + assertEquals(user.loginName, deleteUserRequest.loginName) + } + } + @Test fun `receiving CreateRoleRequest calls roleWriter to create role and publishes role and completes future`() { whenever(roleWriter.createRole(createRoleRequest, creatorUserId)).thenReturn(avroRole) diff --git a/libs/permissions/permission-storage-writer-impl/src/test/kotlin/net/corda/libs/permissions/storage/writer/impl/user/UserWriterImplTest.kt b/libs/permissions/permission-storage-writer-impl/src/test/kotlin/net/corda/libs/permissions/storage/writer/impl/user/UserWriterImplTest.kt index 78ccf86ca96..c45fa3f0d10 100644 --- a/libs/permissions/permission-storage-writer-impl/src/test/kotlin/net/corda/libs/permissions/storage/writer/impl/user/UserWriterImplTest.kt +++ b/libs/permissions/permission-storage-writer-impl/src/test/kotlin/net/corda/libs/permissions/storage/writer/impl/user/UserWriterImplTest.kt @@ -3,6 +3,7 @@ package net.corda.libs.permissions.storage.writer.impl.user import net.corda.data.permissions.management.user.AddRoleToUserRequest import net.corda.data.permissions.management.user.ChangeUserPasswordRequest import net.corda.data.permissions.management.user.CreateUserRequest +import net.corda.data.permissions.management.user.DeleteUserRequest import net.corda.data.permissions.management.user.RemoveRoleFromUserRequest import net.corda.libs.permissions.common.exception.EntityAlreadyExistsException import net.corda.libs.permissions.common.exception.EntityAssociationAlreadyExistsException @@ -53,6 +54,10 @@ internal class UserWriterImplTest { parentGroupId = "parent1" } + private val deleteUserRequest = DeleteUserRequest().apply { + loginName = "lankydan" + } + private val now = Instant.now() private val user = User("userId1", now, "user", "userLogin1", true, null, null, null, null) private val role = Role("role1", now, "roleName1", null) @@ -151,6 +156,37 @@ internal class UserWriterImplTest { assertEquals(requestUserId, audit.actorUser) } + @Test + fun `delete a user successfully removes the user and writes to audit log`() { + val capture = argumentCaptor() + + val typedQueryMock = mock>() + whenever(entityManager.createQuery(any(), eq(User::class.java))).thenReturn(typedQueryMock) + whenever(typedQueryMock.setParameter(eq("loginName"), eq(deleteUserRequest.loginName))) + .thenReturn(typedQueryMock) + whenever(typedQueryMock.resultList).thenReturn(listOf(user)) + + userWriter.deleteUser(deleteUserRequest, requestUserId) + + inOrder(entityTransaction, entityManager) { + verify(entityTransaction).begin() + verify(entityManager, times(1)).remove(capture.capture()) + verify(entityManager, times(1)).persist(capture.capture()) + verify(entityTransaction).commit() + } + + val deletedUser = capture.firstValue as User + assertNotNull(deletedUser) + assertEquals("user", deletedUser.fullName) + assertEquals("userLogin1", deletedUser.loginName) + assertTrue(deletedUser.enabled) + + val audit = capture.secondValue as ChangeAudit + assertNotNull(audit) + assertEquals(RestPermissionOperation.USER_DELETE, audit.changeType) + assertEquals("User '${user.loginName}' deleted by '$requestUserId'.", audit.details) + } + @Test fun `changing users own password successfully changes password`() { // Arrange