diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/authorization/EnabledAuthorizationService.kt b/search-service/src/main/kotlin/com/egm/stellio/search/authorization/EnabledAuthorizationService.kt index 1fc26a3aa..12e5bf971 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/authorization/EnabledAuthorizationService.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/authorization/EnabledAuthorizationService.kt @@ -103,8 +103,8 @@ class EnabledAuthorizationService( sub, accessRights, entitiesQuery.typeSelection, - entitiesQuery.paginationQuery.limit, - entitiesQuery.paginationQuery.offset + entitiesQuery.ids, + entitiesQuery.paginationQuery ).bind() // for each entity user is admin of, retrieve the full details of rights other users have on it @@ -133,7 +133,8 @@ class EnabledAuthorizationService( val count = entityAccessRightsService.getSubjectAccessRightsCount( sub, accessRights, - entitiesQuery.typeSelection + entitiesQuery.typeSelection, + entitiesQuery.ids ).bind() Pair(count, entitiesAccessControlWithSubjectRights) diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/authorization/EntityAccessRightsService.kt b/search-service/src/main/kotlin/com/egm/stellio/search/authorization/EntityAccessRightsService.kt index 885b282ee..c52f4dca8 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/authorization/EntityAccessRightsService.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/authorization/EntityAccessRightsService.kt @@ -6,9 +6,7 @@ import com.egm.stellio.search.authorization.EntityAccessRights.SubjectRightInfo import com.egm.stellio.search.service.EntityPayloadService import com.egm.stellio.search.util.* import com.egm.stellio.shared.config.ApplicationProperties -import com.egm.stellio.shared.model.APIException -import com.egm.stellio.shared.model.AccessDeniedException -import com.egm.stellio.shared.model.ResourceNotFoundException +import com.egm.stellio.shared.model.* import com.egm.stellio.shared.util.* import com.egm.stellio.shared.util.AccessRight.* import com.egm.stellio.shared.util.AuthContextModel.AUTH_TERM_CLIENT_ID @@ -163,9 +161,9 @@ class EntityAccessRightsService( suspend fun getSubjectAccessRights( sub: Option, accessRights: List, - type: String? = null, - limit: Int, - offset: Int + type: EntityTypeSelection? = null, + ids: Set? = null, + paginationQuery: PaginationQuery, ): Either> = either { val subjectUuids = subjectReferentialService.getSubjectAndGroupsUUID(sub).bind() val isStellioAdmin = subjectReferentialService.hasStellioAdminRole(subjectUuids).bind() @@ -177,15 +175,16 @@ class EntityAccessRightsService( FROM entity_access_rights ear LEFT JOIN entity_payload ep ON ear.entity_id = ep.entity_id WHERE ${if (isStellioAdmin) "1 = 1" else "subject_id IN (:subject_uuids)" } - ${if (accessRights.isNotEmpty()) " AND access_right in (:access_rights)" else ""} + ${if (accessRights.isNotEmpty()) " AND access_right IN (:access_rights)" else ""} ${if (!type.isNullOrEmpty()) " AND ${buildTypeQuery(type)}" else ""} + ${if (!ids.isNullOrEmpty()) " AND ear.entity_id IN (:entities_ids)" else ""} ORDER BY entity_id LIMIT :limit OFFSET :offset; """.trimIndent() ) - .bind("limit", limit) - .bind("offset", offset) + .bind("limit", paginationQuery.limit) + .bind("offset", paginationQuery.offset) .let { if (!isStellioAdmin) it.bind("subject_uuids", subjectUuids) @@ -196,6 +195,11 @@ class EntityAccessRightsService( it.bind("access_rights", accessRights.map { it.attributeName }) else it } + .let { + if (!ids.isNullOrEmpty()) + it.bind("entities_ids", ids) + else it + } .allToMappedList { rowToEntityAccessControl(it, isStellioAdmin) } .groupBy { it.id } // a user may have multiple rights on a given entity (e.g., through groups memberships) @@ -214,7 +218,8 @@ class EntityAccessRightsService( suspend fun getSubjectAccessRightsCount( sub: Option, accessRights: List, - type: String? = null + type: EntityTypeSelection? = null, + ids: Set? = null ): Either = either { val subjectUuids = subjectReferentialService.getSubjectAndGroupsUUID(sub).bind() val isStellioAdmin = subjectReferentialService.hasStellioAdminRole(subjectUuids).bind() @@ -226,8 +231,9 @@ class EntityAccessRightsService( FROM entity_access_rights ear LEFT JOIN entity_payload ep ON ear.entity_id = ep.entity_id WHERE ${if (isStellioAdmin) "1 = 1" else "subject_id IN (:subject_uuids)" } - ${if (accessRights.isNotEmpty()) " AND access_right in (:access_rights)" else ""} + ${if (accessRights.isNotEmpty()) " AND access_right IN (:access_rights)" else ""} ${if (!type.isNullOrEmpty()) " AND ${buildTypeQuery(type)}" else ""} + ${if (!ids.isNullOrEmpty()) " AND ear.entity_id IN (:entities_ids)" else ""} """.trimIndent() ) .let { @@ -240,6 +246,11 @@ class EntityAccessRightsService( it.bind("access_rights", accessRights.map { it.attributeName }) else it } + .let { + if (!ids.isNullOrEmpty()) + it.bind("entities_ids", ids) + else it + } .oneToResult { toInt(it["count"]) } .bind() } diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/authorization/EnabledAuthorizationServiceTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/authorization/EnabledAuthorizationServiceTests.kt index 3f6efb5d8..e9f5a8182 100644 --- a/search-service/src/test/kotlin/com/egm/stellio/search/authorization/EnabledAuthorizationServiceTests.kt +++ b/search-service/src/test/kotlin/com/egm/stellio/search/authorization/EnabledAuthorizationServiceTests.kt @@ -335,7 +335,9 @@ class EnabledAuthorizationServiceTests { right = AccessRight.R_CAN_WRITE ) ).right() - coEvery { entityAccessRightsService.getSubjectAccessRightsCount(any(), any(), any()) } returns Either.Right(1) + coEvery { + entityAccessRightsService.getSubjectAccessRightsCount(any(), any(), any(), any()) + } returns Either.Right(1) coEvery { entityAccessRightsService.getAccessRightsForEntities(any(), any()) } returns emptyMap>>().right() @@ -380,7 +382,9 @@ class EnabledAuthorizationServiceTests { right = AccessRight.R_CAN_WRITE ) ).right() - coEvery { entityAccessRightsService.getSubjectAccessRightsCount(any(), any(), any()) } returns Either.Right(1) + coEvery { + entityAccessRightsService.getSubjectAccessRightsCount(any(), any(), any(), any()) + } returns Either.Right(1) coEvery { entityAccessRightsService.getAccessRightsForEntities(any(), any()) } returns mapOf( diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/authorization/EntityAccessRightsServiceTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/authorization/EntityAccessRightsServiceTests.kt index cd245d9e4..2b392a9fa 100644 --- a/search-service/src/test/kotlin/com/egm/stellio/search/authorization/EntityAccessRightsServiceTests.kt +++ b/search-service/src/test/kotlin/com/egm/stellio/search/authorization/EntityAccessRightsServiceTests.kt @@ -6,6 +6,7 @@ import com.egm.stellio.search.model.EntityPayload import com.egm.stellio.search.service.EntityPayloadService import com.egm.stellio.search.support.WithTimescaleContainer import com.egm.stellio.shared.model.AccessDeniedException +import com.egm.stellio.shared.model.PaginationQuery import com.egm.stellio.shared.util.* import com.egm.stellio.shared.util.AuthContextModel.AUTHORIZATION_COMPOUND_CONTEXT import com.egm.stellio.shared.util.AuthContextModel.AUTH_TERM_NAME @@ -260,8 +261,7 @@ class EntityAccessRightsServiceTests : WithTimescaleContainer { entityAccessRightsService.getSubjectAccessRights( Some(subjectUuid), emptyList(), - limit = 100, - offset = 0 + paginationQuery = PaginationQuery(limit = 100, offset = 0) ).shouldSucceedWith { assertEquals(1, it.size) val entityAccessControl = it[0] @@ -292,8 +292,7 @@ class EntityAccessRightsServiceTests : WithTimescaleContainer { entityAccessRightsService.getSubjectAccessRights( Some(subjectUuid), emptyList(), - limit = 100, - offset = 0 + paginationQuery = PaginationQuery(limit = 100, offset = 0) ).shouldSucceedWith { assertEquals(2, it.size) it.forEach { entityAccessControl -> @@ -325,8 +324,7 @@ class EntityAccessRightsServiceTests : WithTimescaleContainer { Some(subjectUuid), emptyList(), BEEHIVE_TYPE, - 100, - 0 + paginationQuery = PaginationQuery(limit = 100, offset = 0) ).shouldSucceedWith { assertEquals(1, it.size) val entityAccessControl = it[0] @@ -343,6 +341,72 @@ class EntityAccessRightsServiceTests : WithTimescaleContainer { } } + @Test + fun `it should get all entities an user has access to wrt ids`() = runTest { + val entityId03 = "urn:ngsi-ld:Entity:03".toUri() + + createEntityPayload(entityId01, setOf(BEEHIVE_TYPE), AUTH_READ) + createEntityPayload(entityId02, setOf(BEEHIVE_TYPE)) + createEntityPayload(entityId03, setOf(APIARY_TYPE)) + entityAccessRightsService.setRoleOnEntity(subjectUuid, entityId01, AccessRight.R_CAN_WRITE).shouldSucceed() + entityAccessRightsService.setRoleOnEntity(subjectUuid, entityId03, AccessRight.R_CAN_WRITE).shouldSucceed() + entityAccessRightsService.setRoleOnEntity(UUID.randomUUID().toString(), entityId02, AccessRight.R_CAN_WRITE) + .shouldSucceed() + + entityAccessRightsService.getSubjectAccessRights( + Some(subjectUuid), + emptyList(), + null, + setOf(entityId01, entityId02), + paginationQuery = PaginationQuery(limit = 100, offset = 0) + ).shouldSucceedWith { + assertEquals(1, it.size) + assertEquals(entityId01, it[0].id) + } + + entityAccessRightsService.getSubjectAccessRightsCount( + Some(subjectUuid), + emptyList(), + BEEHIVE_TYPE, + setOf(entityId01, entityId03) + ).shouldSucceedWith { + assertEquals(1, it) + } + } + + @Test + fun `it should get all entities an user has access to wrt ids and types`() = runTest { + val entityId03 = "urn:ngsi-ld:Entity:03".toUri() + + createEntityPayload(entityId01, setOf(BEEHIVE_TYPE), AUTH_READ) + createEntityPayload(entityId02, setOf(BEEHIVE_TYPE)) + createEntityPayload(entityId03, setOf(APIARY_TYPE)) + entityAccessRightsService.setRoleOnEntity(subjectUuid, entityId01, AccessRight.R_CAN_WRITE).shouldSucceed() + entityAccessRightsService.setRoleOnEntity(subjectUuid, entityId03, AccessRight.R_CAN_WRITE).shouldSucceed() + entityAccessRightsService.setRoleOnEntity(UUID.randomUUID().toString(), entityId02, AccessRight.R_CAN_WRITE) + .shouldSucceed() + + entityAccessRightsService.getSubjectAccessRights( + Some(subjectUuid), + emptyList(), + BEEHIVE_TYPE, + setOf(entityId01, entityId03), + paginationQuery = PaginationQuery(limit = 100, offset = 0) + ).shouldSucceedWith { + assertEquals(1, it.size) + assertEquals(entityId01, it[0].id) + } + + entityAccessRightsService.getSubjectAccessRightsCount( + Some(subjectUuid), + emptyList(), + BEEHIVE_TYPE, + setOf(entityId01, entityId03) + ).shouldSucceedWith { + assertEquals(1, it) + } + } + @Test fun `it should get all entities an user has access to wrt access rights`() = runTest { val entityId03 = "urn:ngsi-ld:Entity:03".toUri() @@ -358,8 +422,7 @@ class EntityAccessRightsServiceTests : WithTimescaleContainer { entityAccessRightsService.getSubjectAccessRights( Some(subjectUuid), listOf(AccessRight.R_CAN_WRITE), - limit = 100, - offset = 0 + paginationQuery = PaginationQuery(limit = 100, offset = 0) ).shouldSucceedWith { assertEquals(1, it.size) val entityAccessControl = it[0] @@ -388,8 +451,7 @@ class EntityAccessRightsServiceTests : WithTimescaleContainer { Some(subjectUuid), emptyList(), BEEHIVE_TYPE, - 100, - 0 + paginationQuery = PaginationQuery(limit = 100, offset = 0) ).shouldSucceedWith { assertEquals(1, it.size) val entityAccessControl = it[0] diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/service/QueryServiceTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/service/QueryServiceTests.kt index 71d6b7c89..6855c0204 100644 --- a/search-service/src/test/kotlin/com/egm/stellio/search/service/QueryServiceTests.kt +++ b/search-service/src/test/kotlin/com/egm/stellio/search/service/QueryServiceTests.kt @@ -25,8 +25,6 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import org.springframework.test.context.ActiveProfiles import java.net.URI -import java.time.Instant -import java.time.ZoneOffset import java.time.ZonedDateTime @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = [QueryService::class]) @@ -49,7 +47,7 @@ class QueryServiceTests { @MockkBean private lateinit var temporalEntityAttributeService: TemporalEntityAttributeService - private val now = Instant.now().atZone(ZoneOffset.UTC) + private val now = ngsiLdDateTime() private val entityUri = "urn:ngsi-ld:BeeHive:TESTC".toUri()