CVE-2025-48881
Description
Valtimo is a platform for Business Process Automation. In versions starting from 11.0.0.RELEASE to 11.3.3.RELEASE and 12.0.0.RELEASE to 12.12.0.RELEASE, all objects for which an object-management configuration exists can be listed, viewed, edited, created or deleted by unauthorised users. If object-urls are exposed via other channels, the contents of these objects can be viewed independent of object-management configurations. This issue has been patched in version 12.13.0.RELEASE. A workaround for this issue involves overriding the endpoint security as defined in ObjectenApiHttpSecurityConfigurer and ObjectManagementHttpSecurityConfigurer. Depending on the implementation, this could result in loss of functionality.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
com.ritense.valtimo:objecten-apiMaven | >= 11.0.0.RELEASE, <= 11.3.3.RELEASE | — |
com.ritense.valtimo:object-managementMaven | >= 11.0.0.RELEASE, <= 11.3.3.RELEASE | — |
com.ritense.valtimo:object-managementMaven | >= 12.0.0.RELEASE, < 12.13.0.RELEASE | 12.13.0.RELEASE |
com.ritense.valtimo:objecten-apiMaven | >= 12.0.0.RELEASE, < 12.13.0.RELEASE | 12.13.0.RELEASE |
Affected products
1Patches
2c1b6271160d16ab04b30d3daAdded permission check for objecten api objects (#1875)
17 files changed · +383 −81
app/gzac/.env.properties+1 −1 modified@@ -14,7 +14,7 @@ CATALOGI_API_URL=http://localhost:8001/catalogi/api/v1/ DOCUMENTEN_API_URL=http://localhost:8001/documenten/api/v1/ NOTIFICATIES_API_URL=http://localhost:8002/api/v1/ OBJECTEN_API_URL=http://localhost:8010/api/v2/ -OBJECTTYPEN_API_URL=http://localhost:8011/api/v1/ +OBJECTTYPEN_API_URL=http://localhost:8011/api/v2/ OPEN_ZAAK_CLIENT_ID=valtimo_client OPEN_ZAAK_CLIENT_SECRET=e09b8bc5-5831-4618-ab28-41411304309d
app/gzac/src/main/resources/config/application.yml+2 −0 modified@@ -250,6 +250,8 @@ valtimo: polling.rate: "PT1M" authorization: + objectenapi: + enabled: true dashboard: enabled: true zgwDocuments:
app/gzac/src/main/resources/config/pbac/object.permission.json+30 −0 added@@ -0,0 +1,30 @@ +{ + "changesetId": "object-admin-v1", + "permissions": [ + { + "resourceType": "com.ritense.objectenapi.security.Object", + "action": "create", + "roleKey": "ROLE_ADMIN" + }, + { + "resourceType": "com.ritense.objectenapi.security.Object", + "action": "modify", + "roleKey": "ROLE_ADMIN" + }, + { + "resourceType": "com.ritense.objectenapi.security.Object", + "action": "view", + "roleKey": "ROLE_ADMIN" + }, + { + "resourceType": "com.ritense.objectenapi.security.Object", + "action": "view_list", + "roleKey": "ROLE_ADMIN" + }, + { + "resourceType": "com.ritense.objectenapi.security.Object", + "action": "delete", + "roleKey": "ROLE_ADMIN" + } + ] +}
zgw/objecten-api/build.gradle+1 −0 modified@@ -30,6 +30,7 @@ dockerCompose { } dependencies { + implementation project(':authorization') implementation project(':plugin') implementation project(':contract') implementation project(':document')
zgw/objecten-api/src/main/kotlin/com/ritense/objectenapi/client/ObjectenApiClient.kt+88 −1 modified@@ -17,13 +17,17 @@ package com.ritense.objectenapi.client import com.fasterxml.jackson.databind.ObjectMapper +import com.ritense.authorization.AuthorizationService +import com.ritense.authorization.request.EntityAuthorizationRequest import com.ritense.objectenapi.ObjectenApiAuthentication import com.ritense.objectenapi.event.ObjectCreated import com.ritense.objectenapi.event.ObjectDeleted import com.ritense.objectenapi.event.ObjectPatched import com.ritense.objectenapi.event.ObjectUpdated import com.ritense.objectenapi.event.ObjectViewed import com.ritense.objectenapi.event.ObjectsListed +import com.ritense.objectenapi.security.Object +import com.ritense.objectenapi.security.ObjectActionProvider import com.ritense.outbox.OutboxService import org.springframework.data.domain.Pageable import org.springframework.http.HttpStatus @@ -35,7 +39,9 @@ import java.net.URI class ObjectenApiClient( private val restClientBuilder: RestClient.Builder, private val outboxService: OutboxService, - private val objectMapper: ObjectMapper + private val objectMapper: ObjectMapper, + private val authorizationService: AuthorizationService, + private val authorizationEnabled: Boolean, ) { fun getObject( @@ -48,6 +54,16 @@ class ObjectenApiClient( .retrieve() .body<ObjectWrapper>()!! + if (authorizationEnabled) { + authorizationService.requirePermission( + EntityAuthorizationRequest( + Object::class.java, + ObjectActionProvider.VIEW, + Object() + ) + ) + } + outboxService.send { ObjectViewed( result.url.toString(), @@ -74,6 +90,16 @@ class ObjectenApiClient( .retrieve() .body<ObjectRecord>()!! + if (authorizationEnabled) { + authorizationService.requirePermission( + EntityAuthorizationRequest( + Object::class.java, + ObjectActionProvider.VIEW, + Object() + ) + ) + } + outboxService.send { ObjectViewed( objectUrl.toString(), @@ -112,6 +138,16 @@ class ObjectenApiClient( .retrieve() .body<ObjectsList>()!! + if (authorizationEnabled) { + authorizationService.requirePermission( + EntityAuthorizationRequest( + Object::class.java, + ObjectActionProvider.VIEW_LIST, + Object() + ) + ) + } + outboxService.send { ObjectsListed( objectMapper.valueToTree(result.results) @@ -151,6 +187,16 @@ class ObjectenApiClient( .retrieve() .body<ObjectsList>()!! + if (authorizationEnabled) { + authorizationService.requirePermission( + EntityAuthorizationRequest( + Object::class.java, + ObjectActionProvider.VIEW_LIST, + Object() + ) + ) + } + outboxService.send { ObjectsListed( objectMapper.valueToTree(result.results) @@ -164,6 +210,16 @@ class ObjectenApiClient( objectsApiUrl: URI, objectRequest: ObjectRequest ): ObjectWrapper { + if (authorizationEnabled) { + authorizationService.requirePermission( + EntityAuthorizationRequest( + Object::class.java, + ObjectActionProvider.CREATE, + Object() + ) + ) + } + val result = buildRestClient(authentication, objectsApiUrl.toASCIIString()) .post() .uri("objects") @@ -187,6 +243,17 @@ class ObjectenApiClient( objectUrl: URI, objectRequest: ObjectRequest ): ObjectWrapper { + + if (authorizationEnabled) { + authorizationService.requirePermission( + EntityAuthorizationRequest( + Object::class.java, + ObjectActionProvider.MODIFY, + Object() + ) + ) + } + val result = buildRestClient(authentication) .patch() .uri(objectUrl) @@ -209,6 +276,16 @@ class ObjectenApiClient( objectUrl: URI, objectRequest: ObjectRequest ): ObjectWrapper { + if (authorizationEnabled) { + authorizationService.requirePermission( + EntityAuthorizationRequest( + Object::class.java, + ObjectActionProvider.MODIFY, + Object() + ) + ) + } + val result = buildRestClient(authentication) .put() .uri(objectUrl) @@ -227,6 +304,16 @@ class ObjectenApiClient( } fun deleteObject(authentication: ObjectenApiAuthentication, objectUrl: URI): HttpStatus { + if (authorizationEnabled) { + authorizationService.requirePermission( + EntityAuthorizationRequest( + Object::class.java, + ObjectActionProvider.DELETE, + Object() + ) + ) + } + val result = buildRestClient(authentication) .delete() .uri(objectUrl)
zgw/objecten-api/src/main/kotlin/com/ritense/objectenapi/listener/ZaakObjectListener.kt+22 −19 modified@@ -19,6 +19,7 @@ package com.ritense.objectenapi.listener import com.fasterxml.jackson.databind.node.NullNode import com.fasterxml.jackson.databind.node.ObjectNode import com.fasterxml.jackson.databind.node.ValueNode +import com.ritense.authorization.AuthorizationContext.Companion.runWithoutAuthorization import com.ritense.objectenapi.ObjectenApiPlugin import com.ritense.objectenapi.client.ObjectRecord import com.ritense.objectenapi.client.ObjectRequest @@ -40,26 +41,28 @@ class ZaakObjectListener( ) { @EventListener(ExternalDataSubmittedEvent::class) fun handle(event: ExternalDataSubmittedEvent) { - event.data[ZaakObjectConstants.ZAAKOBJECT_PREFIX]?.let { zaakObjectMap -> - logger.debug { "Received external data for zaak object, updating the following properties: ${zaakObjectMap.keys.joinToString() }" } - zaakObjectMap.entries.map { entry -> - RequestedField( - entry.key, entry.value - ) - }.groupBy { requestedField -> - requestedField.objectType - }.forEach { objectTypeGroup -> - val zaakObject = zaakObjectService.getZaakObjectOfTypeByName(event.documentId, objectTypeGroup.key) - objectTypeGroup.value.forEach { requestedField -> - logger.trace { "Updating field ${requestedField.path} with value ${requestedField.value} in object '${zaakObject.url}' of type '${objectTypeGroup.key}'" } - // For each requestedField update the value in the zaakObject record data - val startPath = requestedField.path.substring(1) - val newValueNode = getValueNode(requestedField.value) - findAndReplaceJsonPath(zaakObject.record.data!! as ObjectNode, startPath, newValueNode) + runWithoutAuthorization { + event.data[ZaakObjectConstants.ZAAKOBJECT_PREFIX]?.let { zaakObjectMap -> + logger.debug { "Received external data for zaak object, updating the following properties: ${zaakObjectMap.keys.joinToString()}" } + zaakObjectMap.entries.map { entry -> + RequestedField( + entry.key, entry.value + ) + }.groupBy { requestedField -> + requestedField.objectType + }.forEach { objectTypeGroup -> + val zaakObject = zaakObjectService.getZaakObjectOfTypeByName(event.documentId, objectTypeGroup.key) + objectTypeGroup.value.forEach { requestedField -> + logger.trace { "Updating field ${requestedField.path} with value ${requestedField.value} in object '${zaakObject.url}' of type '${objectTypeGroup.key}'" } + // For each requestedField update the value in the zaakObject record data + val startPath = requestedField.path.substring(1) + val newValueNode = getValueNode(requestedField.value) + findAndReplaceJsonPath(zaakObject.record.data!! as ObjectNode, startPath, newValueNode) + } + + // The zaakObject.record.data has now been updated with the new values, update the object in the objecten api + updateObject(zaakObject.url, event.documentId, objectTypeGroup.key, zaakObject.record) } - - // The zaakObject.record.data has now been updated with the new values, update the object in the objecten api - updateObject(zaakObject.url, event.documentId, objectTypeGroup.key, zaakObject.record) } } }
zgw/objecten-api/src/main/kotlin/com/ritense/objectenapi/ObjectenApiAutoConfiguration.kt+29 −6 modified@@ -17,11 +17,13 @@ package com.ritense.objectenapi import com.fasterxml.jackson.databind.ObjectMapper +import com.ritense.authorization.AuthorizationService import com.ritense.form.service.FormDefinitionService import com.ritense.objectenapi.client.ObjectenApiClient import com.ritense.objectenapi.listener.ZaakObjectListener import com.ritense.objectenapi.management.ErrorObjectManagementInfoProvider import com.ritense.objectenapi.management.ObjectManagementInfoProvider +import com.ritense.objectenapi.security.ObjectSpecificationFactory import com.ritense.objectenapi.security.ObjectenApiHttpSecurityConfigurer import com.ritense.objectenapi.service.ZaakObjectDataResolver import com.ritense.objectenapi.service.ZaakObjectService @@ -32,6 +34,8 @@ import com.ritense.outbox.OutboxService import com.ritense.plugin.service.PluginService import com.ritense.processdocument.service.ProcessDocumentService import com.ritense.zakenapi.ZaakUrlProvider +import mu.KotlinLogging +import org.springframework.beans.factory.annotation.Value import org.springframework.boot.autoconfigure.AutoConfiguration import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.context.annotation.Bean @@ -57,12 +61,21 @@ class ObjectenApiAutoConfiguration { fun objectenApiClient( restClientBuilder: RestClient.Builder, outboxService: OutboxService, - objectMapper: ObjectMapper - ) = ObjectenApiClient( - restClientBuilder, - outboxService, - objectMapper - ) + objectMapper: ObjectMapper, + authorizationService: AuthorizationService, + @Value("\${valtimo.authorization.objectenapi.enabled:true}") authorizationEnabled: Boolean + ): ObjectenApiClient { + if (!authorizationEnabled) { + logger.warn { "Objecten API authorization is disabled. This is a potential security issue. The option to disable this will be removed with Valtimo 13." } + } + return ObjectenApiClient( + restClientBuilder, + outboxService, + objectMapper, + authorizationService, + authorizationEnabled + ) + } @Bean fun objectenApiPluginFactory( @@ -131,4 +144,14 @@ class ObjectenApiAutoConfiguration { fun errorObjectManagementInfoProvider(): ObjectManagementInfoProvider { return ErrorObjectManagementInfoProvider() } + + @Bean + @ConditionalOnMissingBean(ObjectSpecificationFactory::class) + fun objectSpecificationFactory(): ObjectSpecificationFactory { + return ObjectSpecificationFactory() + } + + companion object { + val logger = KotlinLogging.logger {} + } }
zgw/objecten-api/src/main/kotlin/com/ritense/objectenapi/security/ObjectActionProvider.kt+34 −0 added@@ -0,0 +1,34 @@ +/* + * Copyright 2015-2025 Ritense BV, the Netherlands. + * + * Licensed under EUPL, Version 1.2 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ritense.objectenapi.security + +import com.ritense.authorization.Action +import com.ritense.authorization.ResourceActionProvider + +class ObjectActionProvider : ResourceActionProvider<Object> { + override fun getAvailableActions(): List<Action<Object>> { + return listOf(VIEW, VIEW_LIST, CREATE, MODIFY, DELETE) + } + + companion object { + var VIEW = Action<Object>(Action.VIEW) + var VIEW_LIST = Action<Object>(Action.VIEW_LIST) + var CREATE = Action<Object>(Action.CREATE) + var MODIFY = Action<Object>(Action.MODIFY) + var DELETE = Action<Object>(Action.DELETE) + } +}
zgw/objecten-api/src/main/kotlin/com/ritense/objectenapi/security/Object.kt+22 −0 added@@ -0,0 +1,22 @@ +/* + * Copyright 2015-2025 Ritense BV, the Netherlands. + * + * Licensed under EUPL, Version 1.2 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ritense.objectenapi.security + +/* + * This class is a placeholder for an Objecten API Object used in permission checks. + */ +class Object() \ No newline at end of file
zgw/objecten-api/src/main/kotlin/com/ritense/objectenapi/security/ObjectSpecificationFactory.kt+40 −0 added@@ -0,0 +1,40 @@ +/* + * Copyright 2015-2025 Ritense BV, the Netherlands. + * + * Licensed under EUPL, Version 1.2 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ritense.objectenapi.security + +import com.ritense.authorization.permission.Permission +import com.ritense.authorization.request.AuthorizationRequest +import com.ritense.authorization.specification.AuthorizationSpecification +import com.ritense.authorization.specification.AuthorizationSpecificationFactory +import com.ritense.valtimo.contract.annotation.SkipComponentScan +import org.springframework.stereotype.Component + +@Component +@SkipComponentScan +class ObjectSpecificationFactory : AuthorizationSpecificationFactory<Object> { + + override fun create( + request: AuthorizationRequest<Object>, + permissions: List<Permission> + ): AuthorizationSpecification<Object> { + return ObjectSpecification(request, permissions) + } + + override fun canCreate(request: AuthorizationRequest<*>, permissions: List<Permission>): Boolean { + return Object::class.java == request.resourceType + } +} \ No newline at end of file
zgw/objecten-api/src/main/kotlin/com/ritense/objectenapi/security/ObjectSpecification.kt+44 −0 added@@ -0,0 +1,44 @@ +/* + * Copyright 2015-2025 Ritense BV, the Netherlands. + * + * Licensed under EUPL, Version 1.2 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ritense.objectenapi.security + +import com.ritense.authorization.permission.Permission +import com.ritense.authorization.request.AuthorizationRequest +import com.ritense.authorization.specification.AuthorizationSpecification +import jakarta.persistence.criteria.AbstractQuery +import jakarta.persistence.criteria.CriteriaBuilder +import jakarta.persistence.criteria.Predicate +import jakarta.persistence.criteria.Root + +class ObjectSpecification( + authRequest: AuthorizationRequest<Object>, + permissions: List<Permission>, +) : AuthorizationSpecification<Object>(authRequest, permissions) { + + override fun toPredicate( + root: Root<Object>, + query: AbstractQuery<*>, + criteriaBuilder: CriteriaBuilder + ): Predicate { + throw NotImplementedError() + } + + override fun identifierToEntity(identifier: String): Object { + throw NotImplementedError() + } +} +
zgw/objecten-api/src/main/kotlin/com/ritense/objectenapi/service/ZaakObjectDataResolver.kt+9 −5 modified@@ -27,6 +27,7 @@ import com.fasterxml.jackson.databind.node.JsonNodeType.NUMBER import com.fasterxml.jackson.databind.node.JsonNodeType.OBJECT import com.fasterxml.jackson.databind.node.JsonNodeType.POJO import com.fasterxml.jackson.databind.node.JsonNodeType.STRING +import com.ritense.authorization.AuthorizationContext.Companion.runWithoutAuthorization import com.ritense.objectenapi.service.ZaakObjectConstants.Companion.ZAAKOBJECT_PREFIX import com.ritense.objectenapi.service.ZaakObjectValueResolverFactory.Companion import com.ritense.valtimo.contract.form.DataResolvingContext @@ -56,11 +57,14 @@ class ZaakObjectDataResolver( }.groupBy { it.objectType }.forEach{ objectTypeGroup -> - val zaakObject = zaakObjectService.getZaakObjectOfTypeByName( - dataResolvingContext.documentId, objectTypeGroup.key) - val dataAsJsonNode = objectMapper.valueToTree<JsonNode>(zaakObject.record.data) - objectTypeGroup.value.forEach { - results[it.variableName] = extractValue(dataAsJsonNode.at(it.path)) + runWithoutAuthorization { + val zaakObject = zaakObjectService.getZaakObjectOfTypeByName( + dataResolvingContext.documentId, objectTypeGroup.key + ) + val dataAsJsonNode = objectMapper.valueToTree<JsonNode>(zaakObject.record.data) + objectTypeGroup.value.forEach { + results[it.variableName] = extractValue(dataAsJsonNode.at(it.path)) + } } }
zgw/objecten-api/src/main/kotlin/com/ritense/objectenapi/service/ZaakObjectValueResolverFactory.kt+3 −1 modified@@ -18,6 +18,7 @@ package com.ritense.objectenapi.service import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.ObjectMapper +import com.ritense.authorization.AuthorizationContext.Companion.runWithoutAuthorization import com.ritense.document.domain.impl.JsonSchemaDocument import com.ritense.logging.withLoggingContext import com.ritense.processdocument.domain.impl.CamundaProcessInstanceId @@ -67,8 +68,9 @@ class ZaakObjectValueResolverFactory( private fun getZaakData(requestedValue: String, documentId: String): Any? { return withLoggingContext(JsonSchemaDocument::class, documentId) { val requestedData = ZaakObjectDataResolver.RequestedData(requestedValue) - val zaakObject = + val zaakObject = runWithoutAuthorization { zaakObjectService.getZaakObjectOfTypeByName(UUID.fromString(documentId), requestedData.objectType) + } val dataAsJsonNode = objectMapper.valueToTree<JsonNode>(zaakObject.record.data) val node = dataAsJsonNode.at(requestedData.path) return@withLoggingContext if (node == null || node.isMissingNode || node.isNull) {
zgw/objecten-api/src/test/kotlin/com/ritense/objectenapi/client/ObjectenApiClientTest.kt+26 −21 modified@@ -22,6 +22,7 @@ import com.fasterxml.jackson.databind.node.TextNode import com.fasterxml.jackson.module.kotlin.readValue import com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath import com.jayway.jsonpath.matchers.JsonPathMatchers.hasNoJsonPath +import com.ritense.authorization.AuthorizationService import com.ritense.objectenapi.ObjectenApiAuthentication import com.ritense.objectenapi.event.ObjectCreated import com.ritense.objectenapi.event.ObjectDeleted @@ -46,6 +47,7 @@ import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.assertThrows import org.mockito.Mockito import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.mock import org.mockito.kotlin.reset import org.mockito.kotlin.times import org.mockito.kotlin.verify @@ -73,13 +75,16 @@ internal class ObjectenApiClientTest { lateinit var restClientBuilder: RestClient.Builder + lateinit var authorizationService: AuthorizationService + @BeforeAll fun setUp() { mockApi = MockWebServer() mockApi.start() objectMapper = MapperSingleton.get() - outboxService = Mockito.mock(OutboxService::class.java) + outboxService = mock() restClientBuilder = RestClient.builder() + authorizationService = mock() } @BeforeEach @@ -94,7 +99,7 @@ internal class ObjectenApiClientTest { @Test fun `should send get single object request and parse response`() { - val client = ObjectenApiClient(restClientBuilder, outboxService, objectMapper) + val client = ObjectenApiClient(restClientBuilder, outboxService, objectMapper, authorizationService, false) val responseBody = """ { @@ -161,7 +166,7 @@ internal class ObjectenApiClientTest { @Test fun `should send get single objectrecord request and parse response`() { - val client = ObjectenApiClient(restClientBuilder, outboxService, objectMapper) + val client = ObjectenApiClient(restClientBuilder, outboxService, objectMapper, authorizationService, false) val responseBody = """ { @@ -222,7 +227,7 @@ internal class ObjectenApiClientTest { @Test fun `should send outbox message on retrieving object`() { - val client = ObjectenApiClient(restClientBuilder, outboxService, objectMapper) + val client = ObjectenApiClient(restClientBuilder, outboxService, objectMapper, authorizationService, false) val responseBody = """ { @@ -277,7 +282,7 @@ internal class ObjectenApiClientTest { @Test fun `should not send outbox message on failing to retrieve object`() { - val client = ObjectenApiClient(restClientBuilder, outboxService, objectMapper) + val client = ObjectenApiClient(restClientBuilder, outboxService, objectMapper, authorizationService, false) mockApi.enqueue(mockResponse("").setResponseCode(400)) @@ -299,7 +304,7 @@ internal class ObjectenApiClientTest { @Test fun `should get objectslist`() { - val client = ObjectenApiClient(restClientBuilder, outboxService, objectMapper) + val client = ObjectenApiClient(restClientBuilder, outboxService, objectMapper, authorizationService, false) val responseBody = """ { @@ -376,7 +381,7 @@ internal class ObjectenApiClientTest { @Test fun `should send outbox message when getting objects by object type url`() { - val client = ObjectenApiClient(restClientBuilder, outboxService, objectMapper) + val client = ObjectenApiClient(restClientBuilder, outboxService, objectMapper, authorizationService, false) val responseBody = """ { @@ -440,7 +445,7 @@ internal class ObjectenApiClientTest { @Test fun `should not send outbox message when failing to get objects by object type url`() { - val client = ObjectenApiClient(restClientBuilder, outboxService, objectMapper) + val client = ObjectenApiClient(restClientBuilder, outboxService, objectMapper, authorizationService, false) val eventCapture = argumentCaptor<Supplier<BaseEvent>>() @@ -466,7 +471,7 @@ internal class ObjectenApiClientTest { @Test fun `should send outbox message when getting objects by object type url with search params`() { - val client = ObjectenApiClient(restClientBuilder, outboxService, objectMapper) + val client = ObjectenApiClient(restClientBuilder, outboxService, objectMapper, authorizationService, false) val responseBody = """ { @@ -531,7 +536,7 @@ internal class ObjectenApiClientTest { @Test fun `should not send outbox message when failing to get objects by object type url with search params`() { - val client = ObjectenApiClient(restClientBuilder, outboxService, objectMapper) + val client = ObjectenApiClient(restClientBuilder, outboxService, objectMapper, authorizationService, false) val eventCapture = argumentCaptor<Supplier<BaseEvent>>() @@ -559,7 +564,7 @@ internal class ObjectenApiClientTest { @Test fun `should send outbox message on creating object`() { - val client = ObjectenApiClient(restClientBuilder, outboxService, objectMapper) + val client = ObjectenApiClient(restClientBuilder, outboxService, objectMapper, authorizationService, false) val responseBody = """ { @@ -624,7 +629,7 @@ internal class ObjectenApiClientTest { @Test fun `should not send outbox message on failing to create object`() { - val client = ObjectenApiClient(restClientBuilder, outboxService, objectMapper) + val client = ObjectenApiClient(restClientBuilder, outboxService, objectMapper, authorizationService, false) mockApi.enqueue(mockResponse("").setResponseCode(400)) @@ -656,7 +661,7 @@ internal class ObjectenApiClientTest { @Test fun `should send post request when creating object`() { - val client = ObjectenApiClient(restClientBuilder, outboxService, objectMapper) + val client = ObjectenApiClient(restClientBuilder, outboxService, objectMapper, authorizationService, false) val responseBody = """ { @@ -714,7 +719,7 @@ internal class ObjectenApiClientTest { @Test fun `should send post request with uuid when uuid has been provided when creating object`() { - val client = ObjectenApiClient(restClientBuilder, outboxService, objectMapper) + val client = ObjectenApiClient(restClientBuilder, outboxService, objectMapper, authorizationService, false) val responseBody = """ { @@ -774,7 +779,7 @@ internal class ObjectenApiClientTest { @Test fun `should send patch request`() { - val client = ObjectenApiClient(restClientBuilder, outboxService, objectMapper) + val client = ObjectenApiClient(restClientBuilder, outboxService, objectMapper, authorizationService, false) val responseBody = """ { @@ -865,7 +870,7 @@ internal class ObjectenApiClientTest { @Test fun `should send outbox message on patching object`() { - val client = ObjectenApiClient(restClientBuilder, outboxService, objectMapper) + val client = ObjectenApiClient(restClientBuilder, outboxService, objectMapper, authorizationService, false) val responseBody = """ { @@ -930,7 +935,7 @@ internal class ObjectenApiClientTest { @Test fun `should not send outbox message on failing to patch object`() { - val client = ObjectenApiClient(restClientBuilder, outboxService, objectMapper) + val client = ObjectenApiClient(restClientBuilder, outboxService, objectMapper, authorizationService, false) val eventCapture = argumentCaptor<Supplier<BaseEvent>>() @@ -962,7 +967,7 @@ internal class ObjectenApiClientTest { @Test fun `should send outbox message on updating object`() { - val client = ObjectenApiClient(restClientBuilder, outboxService, objectMapper) + val client = ObjectenApiClient(restClientBuilder, outboxService, objectMapper, authorizationService, false) val responseBody = """ { @@ -1027,7 +1032,7 @@ internal class ObjectenApiClientTest { @Test fun `should not send outbox message on failing to update object`() { - val client = ObjectenApiClient(restClientBuilder, outboxService, objectMapper) + val client = ObjectenApiClient(restClientBuilder, outboxService, objectMapper, authorizationService, false) val eventCapture = argumentCaptor<Supplier<BaseEvent>>() @@ -1059,7 +1064,7 @@ internal class ObjectenApiClientTest { @Test fun `should send outbox message on deleting object`() { - val client = ObjectenApiClient(restClientBuilder, outboxService, objectMapper) + val client = ObjectenApiClient(restClientBuilder, outboxService, objectMapper, authorizationService, false) val eventCapture = argumentCaptor<Supplier<BaseEvent>>() @@ -1084,7 +1089,7 @@ internal class ObjectenApiClientTest { @Test fun `should not send outbox message on failing to delete object`() { - val client = ObjectenApiClient(restClientBuilder, outboxService, objectMapper) + val client = ObjectenApiClient(restClientBuilder, outboxService, objectMapper, authorizationService, false) val eventCapture = argumentCaptor<Supplier<BaseEvent>>()
zgw/object-management/build.gradle+1 −0 modified@@ -30,6 +30,7 @@ dockerCompose { } dependencies { + implementation project(':authorization') implementation project(':plugin') implementation project(':contract') implementation project(":web")
zgw/object-management/src/main/kotlin/com/ritense/objectmanagement/service/ObjectManagementFacade.kt+26 −23 modified@@ -17,6 +17,7 @@ package com.ritense.objectmanagement.service import com.fasterxml.jackson.databind.JsonNode +import com.ritense.authorization.AuthorizationContext.Companion.runWithoutAuthorization import com.ritense.objectenapi.ObjectenApiPlugin import com.ritense.objectenapi.client.ObjectRecord import com.ritense.objectenapi.client.ObjectRequest @@ -231,20 +232,20 @@ class ObjectManagementFacade( val objectUrl = accessObject.objectenApiPlugin.getObjectUrl(uuid) logger.trace { "Getting object $objectUrl" } - return accessObject.objectenApiPlugin.getObject(objectUrl) + return runWithoutAuthorization { accessObject.objectenApiPlugin.getObject(objectUrl) } } private fun findObjectByUuidAndIndex(accessObject: ObjectManagementAccessObject, uuid: UUID, index: Int): ObjectRecord { logger.debug { "Find object by uuid and index accessObject=$accessObject uuid=$uuid index=$index" } val objectUrl = accessObject.objectenApiPlugin.getObjectUrl(uuid) logger.trace { "Getting object $objectUrl" } - return accessObject.objectenApiPlugin.getObjectRecord(objectUrl, index) + return runWithoutAuthorization { accessObject.objectenApiPlugin.getObjectRecord(objectUrl, index) } } private fun findObjectByUri(accessObject: ObjectManagementAccessObject, objectUrl: URI): ObjectWrapper { logger.debug { "Getting object $objectUrl" } - return accessObject.objectenApiPlugin.getObject(objectUrl) + return runWithoutAuthorization { accessObject.objectenApiPlugin.getObject(objectUrl) } } private fun findObjectsPaged( @@ -255,26 +256,28 @@ class ObjectManagementFacade( pageNumber: Int, pageSize: Int ): ObjectsList { - return if (!searchString.isNullOrBlank()) { - logger.debug { "Getting object page for object type $objectName with search string $searchString" } - - accessObject.objectenApiPlugin.getObjectsByObjectTypeIdWithSearchParams( - accessObject.objectTypenApiPlugin.url, - accessObject.objectManagement.objecttypeId, - searchString, - ordering, - PageRequest.of(pageNumber, pageSize) - ) - } else { - logger.debug { "Getting object page for object type $objectName" } - - accessObject.objectenApiPlugin.getObjectsByObjectTypeId( - accessObject.objectTypenApiPlugin.url, - accessObject.objectenApiPlugin.url, - accessObject.objectManagement.objecttypeId, - ordering, - PageRequest.of(pageNumber, pageSize) - ) + return runWithoutAuthorization { + if (!searchString.isNullOrBlank()) { + logger.debug { "Getting object page for object type $objectName with search string $searchString" } + + accessObject.objectenApiPlugin.getObjectsByObjectTypeIdWithSearchParams( + accessObject.objectTypenApiPlugin.url, + accessObject.objectManagement.objecttypeId, + searchString, + ordering, + PageRequest.of(pageNumber, pageSize) + ) + } else { + logger.debug { "Getting object page for object type $objectName" } + + accessObject.objectenApiPlugin.getObjectsByObjectTypeId( + accessObject.objectTypenApiPlugin.url, + accessObject.objectenApiPlugin.url, + accessObject.objectManagement.objecttypeId, + ordering, + PageRequest.of(pageNumber, pageSize) + ) + } } }
zgw/object-management/src/test/kotlin/com/ritense/objectmanagement/service/ObjectManagementServiceIntTest.kt+5 −4 modified@@ -19,6 +19,7 @@ package com.ritense.objectmanagement.service import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.node.ObjectNode +import com.ritense.authorization.AuthorizationContext.Companion.runWithoutAuthorization import com.ritense.objectenapi.client.Comparator.EQUAL_TO import com.ritense.objectenapi.client.ObjectSearchParameter import com.ritense.objectmanagement.BaseIntegrationTest @@ -242,9 +243,9 @@ internal class ObjectManagementServiceIntTest : BaseIntegrationTest() { val searchWithConfigRequest = SearchWithConfigRequest(listOf(otherFilters)) - val objects = objectManagementService.getObjectsWithSearchParams( + val objects = runWithoutAuthorization{ objectManagementService.getObjectsWithSearchParams( searchWithConfigRequest, objectManagement.id, PageRequest.of(0, 10) - ) + )} assertThat(objects.content.size).isEqualTo(1) assertThat(objects.first().items[0].key).isEqualTo("property1") @@ -294,11 +295,11 @@ internal class ObjectManagementServiceIntTest : BaseIntegrationTest() { val objectManagement = objectManagementService.getByTitle("My Object Management")!! val searchParameters = listOf(ObjectSearchParameter("property1", EQUAL_TO, "henk")) - val objects = objectManagementService.getObjectsWithSearchParams( + val objects = runWithoutAuthorization { objectManagementService.getObjectsWithSearchParams( objectManagement, searchParameters, PageRequest.of(0, 10) - ) + )} assertThat(objects.content.size).isEqualTo(1) assertThat(objects.first().url).isEqualTo(URI("https://example.com/123"))
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4- github.com/advisories/GHSA-965r-9cg9-g42pghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-48881ghsaADVISORY
- github.com/valtimo-platform/valtimo-backend-libraries/commit/6ab04b30d3dab816bfea32d40ba50e5dd4517272nvdWEB
- github.com/valtimo-platform/valtimo-backend-libraries/security/advisories/GHSA-965r-9cg9-g42pnvdWEB
News mentions
0No linked articles in our index yet.