Skip to content
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

CORE-20721: Add RBAC Group rest resource #6233

Merged
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package net.corda.libs.permissions.endpoints.v1.group.impl

import net.corda.libs.permissions.endpoints.common.PermissionEndpointEventHandler
import net.corda.libs.permissions.endpoints.common.withPermissionManager
import net.corda.libs.permissions.endpoints.v1.converter.convertToDto
import net.corda.libs.permissions.endpoints.v1.converter.convertToEndpointType
import net.corda.libs.permissions.endpoints.v1.group.GroupEndpoint
import net.corda.libs.permissions.endpoints.v1.group.types.CreateGroupType
import net.corda.libs.permissions.endpoints.v1.group.types.GroupContentResponseType
import net.corda.libs.permissions.endpoints.v1.group.types.GroupResponseType
import net.corda.libs.permissions.manager.request.AddRoleToGroupRequestDto
import net.corda.libs.permissions.manager.request.ChangeGroupParentIdDto
import net.corda.libs.permissions.manager.request.DeleteGroupRequestDto
import net.corda.libs.permissions.manager.request.RemoveRoleFromGroupRequestDto
import net.corda.libs.platform.PlatformInfoProvider
import net.corda.lifecycle.Lifecycle
import net.corda.lifecycle.LifecycleCoordinatorFactory
import net.corda.lifecycle.createCoordinator
import net.corda.permissions.management.PermissionManagementService
import net.corda.rest.PluggableRestResource
import net.corda.rest.exception.ExceptionDetails
import net.corda.rest.exception.ResourceNotFoundException
import net.corda.rest.response.ResponseEntity
import net.corda.rest.security.CURRENT_REST_CONTEXT
import org.osgi.service.component.annotations.Activate
import org.osgi.service.component.annotations.Component
import org.osgi.service.component.annotations.Reference
import org.slf4j.Logger
import org.slf4j.LoggerFactory

@Component(service = [PluggableRestResource::class])
class GroupEndpointImpl @Activate constructor(
@Reference(service = LifecycleCoordinatorFactory::class)
private val coordinatorFactory: LifecycleCoordinatorFactory,
@Reference(service = PermissionManagementService::class)
private val permissionManagementService: PermissionManagementService,
@Reference(service = PlatformInfoProvider::class)
private val platformInfoProvider: PlatformInfoProvider
) : GroupEndpoint, PluggableRestResource<GroupEndpoint>, Lifecycle {

private companion object {
val logger: Logger = LoggerFactory.getLogger(this::class.java.enclosingClass)
}

override val targetInterface: Class<GroupEndpoint> = GroupEndpoint::class.java

override val protocolVersion get() = platformInfoProvider.localWorkerPlatformVersion

private val coordinator = coordinatorFactory.createCoordinator<GroupEndpoint>(
PermissionEndpointEventHandler("GroupEndpoint")
)

override fun createGroup(createGroupType: CreateGroupType): ResponseEntity<GroupResponseType> {
val principal = getRestThreadLocalContext()

val createGroupResult = withPermissionManager(permissionManagementService.permissionManager, logger) {
createGroup(createGroupType.convertToDto(principal))
}

return ResponseEntity.created(createGroupResult.convertToEndpointType())
}

override fun changeParentGroup(groupId: String, newParentGroupId: String): ResponseEntity<GroupResponseType> {
val principal = getRestThreadLocalContext()

val groupResponseDto = withPermissionManager(permissionManagementService.permissionManager, logger) {
try {
changeParentGroup(ChangeGroupParentIdDto(principal, groupId, 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(groupResponseDto.convertToEndpointType())
}

override fun addRole(groupId: String, roleId: String): ResponseEntity<GroupResponseType> {
val principal = getRestThreadLocalContext()

val result = withPermissionManager(permissionManagementService.permissionManager, logger) {
addRoleToGroup(AddRoleToGroupRequestDto(principal, groupId, roleId))
}
return ResponseEntity.updated(result.convertToEndpointType())
}

override fun removeRole(groupId: String, roleId: String): ResponseEntity<GroupResponseType> {
val principal = getRestThreadLocalContext()

val result = withPermissionManager(permissionManagementService.permissionManager, logger) {
removeRoleFromGroup(RemoveRoleFromGroupRequestDto(principal, groupId, roleId))
}
return ResponseEntity.updated(result.convertToEndpointType())
}

override fun getGroupContent(groupId: String): GroupContentResponseType {
val groupContentResponseDto = withPermissionManager(permissionManagementService.permissionManager, logger) {
getGroupContent(groupId)
} ?: throw ResourceNotFoundException("Group", groupId)

return groupContentResponseDto.convertToEndpointType()
}

override fun deleteGroup(groupId: String): ResponseEntity<GroupResponseType> {
val principal = getRestThreadLocalContext()

val groupResponseDto = withPermissionManager(permissionManagementService.permissionManager, logger) {
deleteGroup(DeleteGroupRequestDto(principal, groupId))
}

return ResponseEntity.deleted(groupResponseDto.convertToEndpointType())
}

private fun getRestThreadLocalContext(): String {
val restContext = CURRENT_REST_CONTEXT.get()
return restContext.principal
}

override val isRunning: Boolean
get() = coordinator.isRunning

override fun start() {
coordinator.start()
}

override fun stop() {
coordinator.stop()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
package net.corda.libs.permissions.endpoints.v1.group.impl

import net.corda.libs.permissions.endpoints.v1.group.types.CreateGroupType
import net.corda.libs.permissions.manager.PermissionManager
import net.corda.libs.permissions.manager.request.AddRoleToGroupRequestDto
import net.corda.libs.permissions.manager.request.CreateGroupRequestDto
import net.corda.libs.permissions.manager.request.DeleteGroupRequestDto
import net.corda.libs.permissions.manager.request.RemoveRoleFromGroupRequestDto
import net.corda.libs.permissions.manager.response.GroupContentResponseDto
import net.corda.libs.permissions.manager.response.GroupResponseDto
import net.corda.libs.platform.PlatformInfoProvider
import net.corda.lifecycle.LifecycleCoordinatorFactory
import net.corda.permissions.management.PermissionManagementService
import net.corda.rest.ResponseCode
import net.corda.rest.security.CURRENT_REST_CONTEXT
import net.corda.rest.security.RestAuthContext
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
import java.time.Instant
import java.util.UUID

internal class GroupEndpointImplTest {

private val now = Instant.now()
private val parentGroup = UUID.randomUUID().toString()

private val createGroupType = CreateGroupType(
"groupName1",
parentGroup
)

private val groupResponseDto = GroupResponseDto(
"uuid",
now,
0,
"groupName1",
parentGroup,
emptyList(),
emptyList(),
)

private val groupContentResponseDto = GroupContentResponseDto(
"uuid",
now,
"groupName1",
parentGroup,
emptyList(),
emptyList(),
emptySet(),
emptySet()
)

private val permissionManager = mock<PermissionManager>()
private val permissionService = mock<PermissionManagementService>().also {
whenever(it.permissionManager).thenReturn(permissionManager)
}

private val platformInfoProvider = mock<PlatformInfoProvider>().also {
whenever(it.localWorkerPlatformVersion).thenReturn(1)
}

private val lifecycleCoordinatorFactory = mock<LifecycleCoordinatorFactory>()

private val endpoint = GroupEndpointImpl(lifecycleCoordinatorFactory, permissionService, platformInfoProvider)

@BeforeEach
fun beforeEach() {
val authContext = mock<RestAuthContext>().apply {
whenever(principal).thenReturn("aRestUser")
}
CURRENT_REST_CONTEXT.set(authContext)
}

@Test
fun `create a group successfully`() {
val createGroupDtoCapture = argumentCaptor<CreateGroupRequestDto>()
whenever(permissionManager.createGroup(createGroupDtoCapture.capture())).thenReturn(groupResponseDto)

val response = endpoint.createGroup(createGroupType)
val responseType = response.responseBody

assertEquals(ResponseCode.CREATED, response.responseCode)
assertNotNull(responseType)
assertEquals("uuid", responseType.id)
assertEquals(now, responseType.updateTimestamp)
assertEquals("groupName1", responseType.name)
assertEquals(parentGroup, responseType.parentGroupId)

val capturedDto = createGroupDtoCapture.firstValue
assertEquals("groupName1", capturedDto.groupName)
assertEquals(parentGroup, capturedDto.parentGroupId)
}

@Test
fun `get a group successfully`() {
whenever(permissionManager.getGroupContent(any())).thenReturn(groupContentResponseDto)

val groupId = "uuid"
val responseType = endpoint.getGroupContent(groupId)

assertNotNull(responseType)
assertEquals("uuid", responseType.id)
assertEquals(now, responseType.updateTimestamp)
assertEquals("groupName1", responseType.name)
assertEquals(parentGroup, responseType.parentGroupId)
}

@Test
fun `add role to group`() {
val addRoleDtoCapture = argumentCaptor<AddRoleToGroupRequestDto>()
whenever(permissionManager.addRoleToGroup(addRoleDtoCapture.capture())).thenReturn(groupResponseDto)

val groupId = "uuid"
val roleId = "roleId1"
val response = endpoint.addRole(groupId, roleId)
val responseType = response.responseBody

assertEquals(ResponseCode.OK, response.responseCode)
assertNotNull(responseType)
assertEquals("uuid", responseType.id)
assertEquals(now, responseType.updateTimestamp)
assertEquals("groupName1", responseType.name)
assertEquals(parentGroup, responseType.parentGroupId)

val capturedDto = addRoleDtoCapture.firstValue
assertEquals(groupId, capturedDto.groupId)
assertEquals(roleId, capturedDto.roleId)
}

@Test
fun `remove role from group`() {
val removeRoleDtoCapture = argumentCaptor<RemoveRoleFromGroupRequestDto>()
whenever(permissionManager.removeRoleFromGroup(removeRoleDtoCapture.capture())).thenReturn(groupResponseDto)

val groupId = "uuid"
val roleId = "roleId1"
val response = endpoint.removeRole(groupId, roleId)
val responseType = response.responseBody

assertEquals(ResponseCode.OK, response.responseCode)
assertNotNull(responseType)
assertEquals("uuid", responseType.id)
assertEquals(now, responseType.updateTimestamp)
assertEquals("groupName1", responseType.name)
assertEquals(parentGroup, responseType.parentGroupId)

val capturedDto = removeRoleDtoCapture.firstValue
assertEquals(groupId, capturedDto.groupId)
assertEquals(roleId, capturedDto.roleId)
}

@Test
fun `delete a group`() {
val deleteGroupDtoCapture = argumentCaptor<DeleteGroupRequestDto>()
whenever(permissionManager.deleteGroup(deleteGroupDtoCapture.capture())).thenReturn(groupResponseDto)

val groupId = "uuid"
val response = endpoint.deleteGroup(groupId)
val responseType = response.responseBody

assertEquals(ResponseCode.OK, response.responseCode)
assertNotNull(responseType)
assertEquals("uuid", responseType.id)
assertEquals(now, responseType.updateTimestamp)
assertEquals("groupName1", responseType.name)
assertEquals(parentGroup, responseType.parentGroupId)

val capturedDto = deleteGroupDtoCapture.firstValue
assertEquals(groupId, capturedDto.groupId)
}
}
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.14-beta+
cordaApiVersion=5.3.0.14-alpha-1719570549597

disruptorVersion=3.4.4
felixConfigAdminVersion=1.9.26
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
@Export
package net.corda.libs.permissions.endpoints.v1.group;

import org.osgi.annotation.bundle.Export;
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
@Export
package net.corda.libs.permissions.endpoints.v1.group.types;

import org.osgi.annotation.bundle.Export;
Loading