Skip to content

Commit

Permalink
CORE-20737: Add change user's parent group ID endpoint (#6305)
Browse files Browse the repository at this point in the history
* Add change user parent group id endpoint
* Added unit tests
  • Loading branch information
BenYip123 authored Jul 25, 2024
1 parent 353bdc8 commit 7c19c15
Show file tree
Hide file tree
Showing 15 changed files with 346 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,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.AddPropertyToUserRequestDto
import net.corda.libs.permissions.manager.request.AddRoleToUserRequestDto
import net.corda.libs.permissions.manager.request.ChangeUserParentIdDto
import net.corda.libs.permissions.manager.request.ChangeUserPasswordDto
import net.corda.libs.permissions.manager.request.DeleteUserRequestDto
import net.corda.libs.permissions.manager.request.GetPermissionSummaryRequestDto
Expand Down Expand Up @@ -161,6 +162,23 @@ class UserEndpointImpl @Activate constructor(
return userResponseDto?.convertToEndpointType() ?: throw ResourceNotFoundException("User", loginName)
}

override fun changeUserParentGroup(loginName: String, newParentGroupId: String?): ResponseEntity<UserResponseType> {
val principal = getRestThreadLocalContext()

val userResponseDto = withPermissionManager(permissionManagementService.permissionManager, logger) {
try {
changeUserParentGroup(ChangeUserParentIdDto(principal, loginName, newParentGroupId))
} catch (e: NoSuchElementException) {
throw ResourceNotFoundException(
e::class.java.simpleName,
ExceptionDetails(e::class.java.name, e.message ?: "No resource found for this request.")
)
}
}

return ResponseEntity.updated(userResponseDto.convertToEndpointType())
}

override fun changeUserPasswordSelf(password: String): UserResponseType {
val principal = getRestThreadLocalContext()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.AddPropertyToUserRequestDto
import net.corda.libs.permissions.manager.request.AddRoleToUserRequestDto
import net.corda.libs.permissions.manager.request.ChangeUserParentIdDto
import net.corda.libs.permissions.manager.request.CreateUserRequestDto
import net.corda.libs.permissions.manager.request.DeleteUserRequestDto
import net.corda.libs.permissions.manager.request.GetUserPropertiesRequestDto
Expand Down Expand Up @@ -201,6 +202,29 @@ internal class UserEndpointImplTest {
assertEquals("abc", getUserRequestDtoCapture.firstValue.loginName)
}

@Test
fun `change a user's parent group successfully`() {
val changeUserParentIdDtoCapture = argumentCaptor<ChangeUserParentIdDto>()
whenever(lifecycleCoordinator.isRunning).thenReturn(true)
whenever(permissionService.isRunning).thenReturn(true)
whenever(permissionManager.changeUserParentGroup(changeUserParentIdDtoCapture.capture())).thenReturn(userResponseDto)

endpoint.start()
val response = endpoint.changeUserParentGroup("loginName1", parentGroup)
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 `add role to user`() {
val userResponseDtoWithRole = UserResponseDto(
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ commonsLangVersion = 3.12.0
commonsTextVersion = 1.10.0
# Corda API libs revision (change in 4th digit indicates a breaking change)
# Change to 5.3.0.xx-SNAPSHOT to pick up maven local published copy
cordaApiVersion=5.3.0.15-beta+
cordaApiVersion=5.3.0.16-beta+

disruptorVersion=3.4.4
felixConfigAdminVersion=1.9.26
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ object Permissions {
"CreateUser" to "POST:/api/$VERSION_PATH_REGEX/user",
"GetUser" to "GET:/api/$VERSION_PATH_REGEX/user/${RbacKeys.USER_URL_REGEX}",
"DeleteUser" to "DELETE:/api/$VERSION_PATH_REGEX/user/${RbacKeys.USER_URL_REGEX}",
"ChangeUserGroupParentId" to "PUT:/api/$VERSION_PATH_REGEX/user/${RbacKeys.USER_URL_REGEX}/parent/changeparentid/$UUID_REGEX",
"ChangeOtherUserPassword" to "POST:/api/$VERSION_PATH_REGEX/user/otheruserpassword",
"AddRoleToUser" to "PUT:/api/$VERSION_PATH_REGEX/user/${RbacKeys.USER_URL_REGEX}/role/$UUID_REGEX",
"DeleteRoleFromUser" to "DELETE:/api/$VERSION_PATH_REGEX/user/${RbacKeys.USER_URL_REGEX}/role/$UUID_REGEX",
Expand Down Expand Up @@ -90,7 +91,7 @@ object Permissions {
// Group manipulation permissions
"CreateGroup" to "POST:/api/$VERSION_PATH_REGEX/group",
"GetGroup" to "GET:/api/$VERSION_PATH_REGEX/group/$UUID_REGEX",
"ChangeGroupParentId" to "PUT:/api/$VERSION_PATH_REGEX/group/$UUID_REGEX/parent/changeParentId/$UUID_REGEX",
"ChangeGroupParentId" to "PUT:/api/$VERSION_PATH_REGEX/group/$UUID_REGEX/parent/changeparentid/$UUID_REGEX",
"AddRoleToGroup" to "PUT:/api/$VERSION_PATH_REGEX/group/$UUID_REGEX/role/$UUID_REGEX",
"DeleteRoleFromGroup" to "DELETE:/api/$VERSION_PATH_REGEX/group/$UUID_REGEX/role/$UUID_REGEX",
"DeleteGroup" to "DELETE:/api/$VERSION_PATH_REGEX/group/$UUID_REGEX"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,34 @@ interface UserEndpoint : RestResource {
loginName: String
): ResponseEntity<UserResponseType>

@HttpPUT(
path = "{loginName}/parent/changeparentid/{newParentGroupId}",
description = "This method changes the parent group of a specified user.",
responseDescription = """
The user with the updated parent group with the following attributes:
id: Unique server generated identifier for the user
version: The version of the user; version 0 is assigned to a newly created user
updateTimestamp: The date and time when the user was last updated
fullName: The full name for the new user
loginName: The login name for the new user
enabled: If true, the user account is enabled; false, the account is disabled
ssoAuth: If true, the user account is enabled for SSO authentication;
false, the account is enabled for password authentication
passwordExpiry: The date and time when the password should expire, specified as an ISO-8601 string;
value of null means that the password does not expire
parentGroup: An optional identifier of the user group for the new user to be included;
value of null means that the user will belong to the root group
properties: An optional set of key/value properties associated with a user account
roleAssociations: A set of roles associated with the user account""",
minVersion = RestApiVersion.C5_3
)
fun changeUserParentGroup(
@RestPathParameter(description = "ID of the user to change parent group.")
loginName: String,
@RestPathParameter(description = "New parent group ID.")
newParentGroupId: String?
): ResponseEntity<UserResponseType>

@HttpPOST(
path = "/selfpassword",
description = "This method updates a users own password.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import net.corda.data.permissions.management.PermissionManagementRequest
import net.corda.data.permissions.management.PermissionManagementResponse
import net.corda.data.permissions.management.user.AddPropertyToUserRequest
import net.corda.data.permissions.management.user.AddRoleToUserRequest
import net.corda.data.permissions.management.user.ChangeUserParentGroupIdRequest
import net.corda.data.permissions.management.user.ChangeUserPasswordRequest
import net.corda.data.permissions.management.user.CreateUserRequest
import net.corda.data.permissions.management.user.DeleteUserRequest
Expand All @@ -18,6 +19,7 @@ import net.corda.libs.permissions.manager.impl.SmartConfigUtil.getEndpointTimeou
import net.corda.libs.permissions.manager.impl.converter.convertToResponseDto
import net.corda.libs.permissions.manager.request.AddPropertyToUserRequestDto
import net.corda.libs.permissions.manager.request.AddRoleToUserRequestDto
import net.corda.libs.permissions.manager.request.ChangeUserParentIdDto
import net.corda.libs.permissions.manager.request.ChangeUserPasswordDto
import net.corda.libs.permissions.manager.request.CreateUserRequestDto
import net.corda.libs.permissions.manager.request.DeleteUserRequestDto
Expand Down Expand Up @@ -104,6 +106,30 @@ class PermissionUserManagerImpl(
return result.convertToResponseDto()
}

override fun changeUserParentGroup(changeUserParentGroupIdDto: ChangeUserParentIdDto): UserResponseDto {
if (changeUserParentGroupIdDto.newParentGroupId != null) {
val permissionManagementCache = checkNotNull(permissionManagementCacheRef.get()) {
"Permission management cache is null."
}
permissionManagementCache.getGroup(changeUserParentGroupIdDto.newParentGroupId!!)
?: throw NoSuchElementException("Could not find user with parent group id ${changeUserParentGroupIdDto.newParentGroupId}")
}

val result = sendPermissionWriteRequest<User>(
rpcSender,
writerTimeout,
PermissionManagementRequest(
changeUserParentGroupIdDto.requestedBy,
null,
ChangeUserParentGroupIdRequest(
changeUserParentGroupIdDto.loginName,
changeUserParentGroupIdDto.newParentGroupId,
)
)
)
return result.convertToResponseDto()
}

override fun changeUserPasswordSelf(changeUserPasswordDto: ChangeUserPasswordDto): UserResponseDto =
changeUserPassword(changeUserPasswordDto, selfUserPasswordExpiryDays)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ package net.corda.libs.permissions.manager.impl

import com.typesafe.config.ConfigValueFactory
import net.corda.data.permissions.ChangeDetails
import net.corda.data.permissions.Group
import net.corda.data.permissions.Property
import net.corda.data.permissions.RoleAssociation
import net.corda.data.permissions.User
import net.corda.data.permissions.management.PermissionManagementRequest
import net.corda.data.permissions.management.PermissionManagementResponse
import net.corda.data.permissions.management.user.AddPropertyToUserRequest
import net.corda.data.permissions.management.user.AddRoleToUserRequest
import net.corda.data.permissions.management.user.ChangeUserParentGroupIdRequest
import net.corda.data.permissions.management.user.CreateUserRequest
import net.corda.data.permissions.management.user.DeleteUserRequest
import net.corda.data.permissions.management.user.RemovePropertyFromUserRequest
Expand All @@ -20,6 +22,7 @@ import net.corda.libs.permissions.management.cache.PermissionManagementCache
import net.corda.libs.permissions.manager.exception.UnexpectedPermissionResponseException
import net.corda.libs.permissions.manager.request.AddPropertyToUserRequestDto
import net.corda.libs.permissions.manager.request.AddRoleToUserRequestDto
import net.corda.libs.permissions.manager.request.ChangeUserParentIdDto
import net.corda.libs.permissions.manager.request.ChangeUserPasswordDto
import net.corda.libs.permissions.manager.request.CreateUserRequestDto
import net.corda.libs.permissions.manager.request.DeleteUserRequestDto
Expand All @@ -39,6 +42,7 @@ import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Assertions.assertThrows
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
Expand All @@ -49,7 +53,6 @@ import org.mockito.kotlin.mock
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import java.lang.IllegalArgumentException
import java.time.Duration
import java.time.Instant
import java.util.UUID
Expand Down Expand Up @@ -293,6 +296,53 @@ class PermissionUserManagerImplTest {
assertNull(result)
}

@Test
fun `change user's parent group sends rpc request and converts result to response dto`() {
val loginName = UUID.randomUUID().toString()
val newParentGroupId = UUID.randomUUID().toString()
val avroUser = User(
"userId", 0,
ChangeDetails(
Instant.now()
),
loginName, "fullName", true, "hashedPass", "salt", Instant.now(), false, newParentGroupId, emptyList(), emptyList()
)
val avroGroup = Group(
UUID.randomUUID().toString(),
0,
ChangeDetails(Instant.now()),
"groupName",
newParentGroupId,
emptyList(),
emptyList()
)

val future = mock<CompletableFuture<PermissionManagementResponse>>()
whenever(future.getOrThrow(defaultTimeout)).thenReturn(PermissionManagementResponse(avroUser))
whenever(permissionManagementCache.getUser(loginName)).thenReturn(avroUser)
whenever(permissionManagementCache.getGroup(newParentGroupId)).thenReturn(avroGroup)

val requestCaptor = argumentCaptor<PermissionManagementRequest>()
whenever(rpcSender.sendRequest(requestCaptor.capture())).thenReturn(future)

val changeUserParentIdDto = ChangeUserParentIdDto("requestedBy", loginName, newParentGroupId)
val result = manager.changeUserParentGroup(changeUserParentIdDto)

val capturedPermissionManagementRequest = requestCaptor.firstValue
assertEquals("requestedBy", capturedPermissionManagementRequest.requestUserId)
assertNull(capturedPermissionManagementRequest.virtualNodeId)

val capturedChangeUserParentGroupIdRequest = capturedPermissionManagementRequest.request as ChangeUserParentGroupIdRequest
assertEquals(loginName, capturedChangeUserParentGroupIdRequest.loginName)
assertEquals(newParentGroupId, capturedChangeUserParentGroupIdRequest.newParentGroupId)

assertEquals("userId", result.id)
assertEquals(loginName, result.loginName)
assertEquals(newParentGroupId, result.parentGroup)
assertTrue(result.properties.isEmpty())
assertTrue(result.roles.isEmpty())
}

@Test
fun `changeUserPasswordSelf fails if the new password is the same as the existing one`() {
whenever(permissionManagementCache.getUser("loginname123")).thenReturn(avroUser)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package net.corda.libs.permissions.manager

import net.corda.libs.permissions.manager.request.AddPropertyToUserRequestDto
import net.corda.libs.permissions.manager.request.AddRoleToUserRequestDto
import net.corda.libs.permissions.manager.request.ChangeUserParentIdDto
import net.corda.libs.permissions.manager.request.ChangeUserPasswordDto
import net.corda.libs.permissions.manager.request.CreateUserRequestDto
import net.corda.libs.permissions.manager.request.DeleteUserRequestDto
Expand Down Expand Up @@ -34,6 +35,11 @@ interface PermissionUserManager {
*/
fun deleteUser(deleteUserRequestDto: DeleteUserRequestDto): UserResponseDto

/**
* Change the parent group of a user in the RBAC Permission System.
*/
fun changeUserParentGroup(changeUserParentGroupIdDto: ChangeUserParentIdDto): UserResponseDto

/**
* Change a user's own password.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package net.corda.libs.permissions.manager.request

data class ChangeUserParentIdDto(
/**
* ID of the user making the request.
*/
val requestedBy: String,
/**
* Login name of the User to change.
*/
val loginName: String,
/**
* ID of the new parent Group.
*/
val newParentGroupId: String?
)
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import net.corda.data.permissions.management.role.CreateRoleRequest
import net.corda.data.permissions.management.role.RemovePermissionFromRoleRequest
import net.corda.data.permissions.management.user.AddPropertyToUserRequest
import net.corda.data.permissions.management.user.AddRoleToUserRequest
import net.corda.data.permissions.management.user.ChangeUserParentGroupIdRequest
import net.corda.data.permissions.management.user.ChangeUserPasswordRequest
import net.corda.data.permissions.management.user.CreateUserRequest
import net.corda.data.permissions.management.user.DeleteUserRequest
Expand Down Expand Up @@ -66,6 +67,12 @@ class PermissionStorageWriterProcessorImpl(
permissionStorageReader.publishNewRole(avroRole)
avroRole
}
is ChangeUserParentGroupIdRequest -> {
val avroUser = userWriter.changeUserParentGroup(permissionRequest, request.requestUserId)
permissionStorageReader.publishUpdatedUser(avroUser)
permissionStorageReader.reconcilePermissionSummaries()
avroUser
}
is ChangeUserPasswordRequest -> {
val avroUser = userWriter.changeUserPassword(permissionRequest, request.requestUserId)
permissionStorageReader.publishUpdatedUser(avroUser)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package net.corda.libs.permissions.storage.writer.impl.user

import net.corda.data.permissions.management.user.AddPropertyToUserRequest
import net.corda.data.permissions.management.user.AddRoleToUserRequest
import net.corda.data.permissions.management.user.ChangeUserParentGroupIdRequest
import net.corda.data.permissions.management.user.ChangeUserPasswordRequest
import net.corda.data.permissions.management.user.CreateUserRequest
import net.corda.data.permissions.management.user.DeleteUserRequest
Expand Down Expand Up @@ -29,6 +30,14 @@ interface UserWriter {
*/
fun deleteUser(request: DeleteUserRequest, requestUserId: String): AvroUser

/**
* Change the parent group of a User entity and return its Avro representation.
*
* @param request ChangeUserParentGroupIdRequest containing the information of the User to change.
* @param requestUserId ID of the user who made the request.
*/
fun changeUserParentGroup(request: ChangeUserParentGroupIdRequest, requestUserId: String): AvroUser

/**
* Change the password field of a User entity and return its Avro representation.
*
Expand Down
Loading

0 comments on commit 7c19c15

Please sign in to comment.