Skip to content

Commit 6ab04b3

Browse files
authored
Added permission check for objecten api objects (#1875)
* Added permission check for objects * Set default permission check to true * Running without authorization for object * Fixed tests * Added warning when permission check is disabled * Fixed logging import * Added return type
1 parent 33779f2 commit 6ab04b3

File tree

17 files changed

+383
-81
lines changed

17 files changed

+383
-81
lines changed

app/gzac/.env.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ CATALOGI_API_URL=http://localhost:8001/catalogi/api/v1/
1414
DOCUMENTEN_API_URL=http://localhost:8001/documenten/api/v1/
1515
NOTIFICATIES_API_URL=http://localhost:8002/api/v1/
1616
OBJECTEN_API_URL=http://localhost:8010/api/v2/
17-
OBJECTTYPEN_API_URL=http://localhost:8011/api/v1/
17+
OBJECTTYPEN_API_URL=http://localhost:8011/api/v2/
1818

1919
OPEN_ZAAK_CLIENT_ID=valtimo_client
2020
OPEN_ZAAK_CLIENT_SECRET=e09b8bc5-5831-4618-ab28-41411304309d

app/gzac/src/main/resources/config/application.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,8 @@ valtimo:
250250
polling.rate: "PT1M"
251251

252252
authorization:
253+
objectenapi:
254+
enabled: true
253255
dashboard:
254256
enabled: true
255257
zgwDocuments:
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"changesetId": "object-admin-v1",
3+
"permissions": [
4+
{
5+
"resourceType": "com.ritense.objectenapi.security.Object",
6+
"action": "create",
7+
"roleKey": "ROLE_ADMIN"
8+
},
9+
{
10+
"resourceType": "com.ritense.objectenapi.security.Object",
11+
"action": "modify",
12+
"roleKey": "ROLE_ADMIN"
13+
},
14+
{
15+
"resourceType": "com.ritense.objectenapi.security.Object",
16+
"action": "view",
17+
"roleKey": "ROLE_ADMIN"
18+
},
19+
{
20+
"resourceType": "com.ritense.objectenapi.security.Object",
21+
"action": "view_list",
22+
"roleKey": "ROLE_ADMIN"
23+
},
24+
{
25+
"resourceType": "com.ritense.objectenapi.security.Object",
26+
"action": "delete",
27+
"roleKey": "ROLE_ADMIN"
28+
}
29+
]
30+
}

zgw/object-management/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ dockerCompose {
3030
}
3131

3232
dependencies {
33+
implementation project(':authorization')
3334
implementation project(':plugin')
3435
implementation project(':contract')
3536
implementation project(":web")

zgw/object-management/src/main/kotlin/com/ritense/objectmanagement/service/ObjectManagementFacade.kt

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package com.ritense.objectmanagement.service
1818

1919
import com.fasterxml.jackson.databind.JsonNode
20+
import com.ritense.authorization.AuthorizationContext.Companion.runWithoutAuthorization
2021
import com.ritense.objectenapi.ObjectenApiPlugin
2122
import com.ritense.objectenapi.client.ObjectRecord
2223
import com.ritense.objectenapi.client.ObjectRequest
@@ -231,20 +232,20 @@ class ObjectManagementFacade(
231232
val objectUrl = accessObject.objectenApiPlugin.getObjectUrl(uuid)
232233

233234
logger.trace { "Getting object $objectUrl" }
234-
return accessObject.objectenApiPlugin.getObject(objectUrl)
235+
return runWithoutAuthorization { accessObject.objectenApiPlugin.getObject(objectUrl) }
235236
}
236237

237238
private fun findObjectByUuidAndIndex(accessObject: ObjectManagementAccessObject, uuid: UUID, index: Int): ObjectRecord {
238239
logger.debug { "Find object by uuid and index accessObject=$accessObject uuid=$uuid index=$index" }
239240
val objectUrl = accessObject.objectenApiPlugin.getObjectUrl(uuid)
240241

241242
logger.trace { "Getting object $objectUrl" }
242-
return accessObject.objectenApiPlugin.getObjectRecord(objectUrl, index)
243+
return runWithoutAuthorization { accessObject.objectenApiPlugin.getObjectRecord(objectUrl, index) }
243244
}
244245

245246
private fun findObjectByUri(accessObject: ObjectManagementAccessObject, objectUrl: URI): ObjectWrapper {
246247
logger.debug { "Getting object $objectUrl" }
247-
return accessObject.objectenApiPlugin.getObject(objectUrl)
248+
return runWithoutAuthorization { accessObject.objectenApiPlugin.getObject(objectUrl) }
248249
}
249250

250251
private fun findObjectsPaged(
@@ -255,26 +256,28 @@ class ObjectManagementFacade(
255256
pageNumber: Int,
256257
pageSize: Int
257258
): ObjectsList {
258-
return if (!searchString.isNullOrBlank()) {
259-
logger.debug { "Getting object page for object type $objectName with search string $searchString" }
260-
261-
accessObject.objectenApiPlugin.getObjectsByObjectTypeIdWithSearchParams(
262-
accessObject.objectTypenApiPlugin.url,
263-
accessObject.objectManagement.objecttypeId,
264-
searchString,
265-
ordering,
266-
PageRequest.of(pageNumber, pageSize)
267-
)
268-
} else {
269-
logger.debug { "Getting object page for object type $objectName" }
270-
271-
accessObject.objectenApiPlugin.getObjectsByObjectTypeId(
272-
accessObject.objectTypenApiPlugin.url,
273-
accessObject.objectenApiPlugin.url,
274-
accessObject.objectManagement.objecttypeId,
275-
ordering,
276-
PageRequest.of(pageNumber, pageSize)
277-
)
259+
return runWithoutAuthorization {
260+
if (!searchString.isNullOrBlank()) {
261+
logger.debug { "Getting object page for object type $objectName with search string $searchString" }
262+
263+
accessObject.objectenApiPlugin.getObjectsByObjectTypeIdWithSearchParams(
264+
accessObject.objectTypenApiPlugin.url,
265+
accessObject.objectManagement.objecttypeId,
266+
searchString,
267+
ordering,
268+
PageRequest.of(pageNumber, pageSize)
269+
)
270+
} else {
271+
logger.debug { "Getting object page for object type $objectName" }
272+
273+
accessObject.objectenApiPlugin.getObjectsByObjectTypeId(
274+
accessObject.objectTypenApiPlugin.url,
275+
accessObject.objectenApiPlugin.url,
276+
accessObject.objectManagement.objecttypeId,
277+
ordering,
278+
PageRequest.of(pageNumber, pageSize)
279+
)
280+
}
278281
}
279282
}
280283

zgw/object-management/src/test/kotlin/com/ritense/objectmanagement/service/ObjectManagementServiceIntTest.kt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package com.ritense.objectmanagement.service
1919
import com.fasterxml.jackson.databind.JsonNode
2020
import com.fasterxml.jackson.databind.ObjectMapper
2121
import com.fasterxml.jackson.databind.node.ObjectNode
22+
import com.ritense.authorization.AuthorizationContext.Companion.runWithoutAuthorization
2223
import com.ritense.objectenapi.client.Comparator.EQUAL_TO
2324
import com.ritense.objectenapi.client.ObjectSearchParameter
2425
import com.ritense.objectmanagement.BaseIntegrationTest
@@ -242,9 +243,9 @@ internal class ObjectManagementServiceIntTest : BaseIntegrationTest() {
242243
val searchWithConfigRequest =
243244
SearchWithConfigRequest(listOf(otherFilters))
244245

245-
val objects = objectManagementService.getObjectsWithSearchParams(
246+
val objects = runWithoutAuthorization{ objectManagementService.getObjectsWithSearchParams(
246247
searchWithConfigRequest, objectManagement.id, PageRequest.of(0, 10)
247-
)
248+
)}
248249

249250
assertThat(objects.content.size).isEqualTo(1)
250251
assertThat(objects.first().items[0].key).isEqualTo("property1")
@@ -294,11 +295,11 @@ internal class ObjectManagementServiceIntTest : BaseIntegrationTest() {
294295
val objectManagement = objectManagementService.getByTitle("My Object Management")!!
295296
val searchParameters = listOf(ObjectSearchParameter("property1", EQUAL_TO, "henk"))
296297

297-
val objects = objectManagementService.getObjectsWithSearchParams(
298+
val objects = runWithoutAuthorization { objectManagementService.getObjectsWithSearchParams(
298299
objectManagement,
299300
searchParameters,
300301
PageRequest.of(0, 10)
301-
)
302+
)}
302303

303304
assertThat(objects.content.size).isEqualTo(1)
304305
assertThat(objects.first().url).isEqualTo(URI("https://example.com/123"))

zgw/objecten-api/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ dockerCompose {
3030
}
3131

3232
dependencies {
33+
implementation project(':authorization')
3334
implementation project(':plugin')
3435
implementation project(':contract')
3536
implementation project(':document')

zgw/objecten-api/src/main/kotlin/com/ritense/objectenapi/ObjectenApiAutoConfiguration.kt

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@
1717
package com.ritense.objectenapi
1818

1919
import com.fasterxml.jackson.databind.ObjectMapper
20+
import com.ritense.authorization.AuthorizationService
2021
import com.ritense.form.service.FormDefinitionService
2122
import com.ritense.objectenapi.client.ObjectenApiClient
2223
import com.ritense.objectenapi.listener.ZaakObjectListener
2324
import com.ritense.objectenapi.management.ErrorObjectManagementInfoProvider
2425
import com.ritense.objectenapi.management.ObjectManagementInfoProvider
26+
import com.ritense.objectenapi.security.ObjectSpecificationFactory
2527
import com.ritense.objectenapi.security.ObjectenApiHttpSecurityConfigurer
2628
import com.ritense.objectenapi.service.ZaakObjectDataResolver
2729
import com.ritense.objectenapi.service.ZaakObjectService
@@ -32,6 +34,8 @@ import com.ritense.outbox.OutboxService
3234
import com.ritense.plugin.service.PluginService
3335
import com.ritense.processdocument.service.ProcessDocumentService
3436
import com.ritense.zakenapi.ZaakUrlProvider
37+
import mu.KotlinLogging
38+
import org.springframework.beans.factory.annotation.Value
3539
import org.springframework.boot.autoconfigure.AutoConfiguration
3640
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
3741
import org.springframework.context.annotation.Bean
@@ -57,12 +61,21 @@ class ObjectenApiAutoConfiguration {
5761
fun objectenApiClient(
5862
restClientBuilder: RestClient.Builder,
5963
outboxService: OutboxService,
60-
objectMapper: ObjectMapper
61-
) = ObjectenApiClient(
62-
restClientBuilder,
63-
outboxService,
64-
objectMapper
65-
)
64+
objectMapper: ObjectMapper,
65+
authorizationService: AuthorizationService,
66+
@Value("\${valtimo.authorization.objectenapi.enabled:true}") authorizationEnabled: Boolean
67+
): ObjectenApiClient {
68+
if (!authorizationEnabled) {
69+
logger.warn { "Objecten API authorization is disabled. This is a potential security issue. The option to disable this will be removed with Valtimo 13." }
70+
}
71+
return ObjectenApiClient(
72+
restClientBuilder,
73+
outboxService,
74+
objectMapper,
75+
authorizationService,
76+
authorizationEnabled
77+
)
78+
}
6679

6780
@Bean
6881
fun objectenApiPluginFactory(
@@ -131,4 +144,14 @@ class ObjectenApiAutoConfiguration {
131144
fun errorObjectManagementInfoProvider(): ObjectManagementInfoProvider {
132145
return ErrorObjectManagementInfoProvider()
133146
}
147+
148+
@Bean
149+
@ConditionalOnMissingBean(ObjectSpecificationFactory::class)
150+
fun objectSpecificationFactory(): ObjectSpecificationFactory {
151+
return ObjectSpecificationFactory()
152+
}
153+
154+
companion object {
155+
val logger = KotlinLogging.logger {}
156+
}
134157
}

0 commit comments

Comments
 (0)