Skip to content

Commit

Permalink
Merge pull request #1406 from open-zaak/feature/1365-auth-filter
Browse files Browse the repository at this point in the history
Feature/1365 auth filter
  • Loading branch information
annashamray committed Jul 17, 2023
2 parents a6df695 + 7ddfa55 commit e55ea1c
Show file tree
Hide file tree
Showing 5 changed files with 294 additions and 26 deletions.
47 changes: 46 additions & 1 deletion src/openzaak/components/besluiten/tests/test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@

from rest_framework import status
from rest_framework.test import APITestCase
from vng_api_common.constants import ComponentTypes
from vng_api_common.authorizations.models import Autorisatie
from vng_api_common.constants import ComponentTypes, VertrouwelijkheidsAanduiding
from vng_api_common.tests import AuthCheckMixin, reverse

from openzaak.components.catalogi.tests.factories import BesluitTypeFactory
Expand All @@ -23,6 +24,9 @@
BESLUITTYPE_EXTERNAL = (
"https://externe.catalogus.nl/api/v1/besluiten/b71f72ef-198d-44d8-af64-ae1932df830a"
)
BESLUITTYPE_EXTERNAL2 = (
"https://externe.catalogus.nl/api/v1/besluiten/77792ada-3a7b-45a4-9239-f2532b61ad35"
)


class BesluitScopeForbiddenTests(AuthCheckMixin, APITestCase):
Expand Down Expand Up @@ -203,6 +207,47 @@ def test_besluit_list(self):
results[0]["besluittype"], f"http://testserver{reverse(self.besluittype)}"
)

def test_besluit_list_internal_and_external_with_filtering(self):
Autorisatie.objects.create(
applicatie=self.applicatie,
component=self.component,
scopes=self.scopes or [],
zaaktype="",
informatieobjecttype="",
besluittype=BESLUITTYPE_EXTERNAL,
max_vertrouwelijkheidaanduiding=VertrouwelijkheidsAanduiding.openbaar,
)

# Should show up
BesluitFactory.create(
besluittype=self.besluittype, verantwoordelijke_organisatie="000000000"
)
BesluitFactory.create(
besluittype=BESLUITTYPE_EXTERNAL, verantwoordelijke_organisatie="000000000"
)

# Should not show up due to filtering
BesluitFactory.create(
besluittype=self.besluittype, verantwoordelijke_organisatie="123456789"
)
# Should not show up due to lacking permissions
BesluitFactory.create(
besluittype=BESLUITTYPE_EXTERNAL2, verantwoordelijke_organisatie="000000000"
)
url = reverse("besluit-list")

response = self.client.get(url, {"verantwoordelijkeOrganisatie": "000000000"})

self.assertEqual(response.status_code, status.HTTP_200_OK)

results = response.data["results"]

self.assertEqual(len(results), 2)
self.assertEqual(results[0]["besluittype"], BESLUITTYPE_EXTERNAL)
self.assertEqual(
results[1]["besluittype"], f"http://testserver{reverse(self.besluittype)}"
)

def test_besluit_retrieve(self):
besluit1 = BesluitFactory.create(besluittype=self.besluittype)
besluit2 = BesluitFactory.create(besluittype=BESLUITTYPE_EXTERNAL)
Expand Down
64 changes: 63 additions & 1 deletion src/openzaak/components/documenten/query/django.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from django_loose_fk.virtual_models import ProxyMixin
from vng_api_common.constants import VertrouwelijkheidsAanduiding
from vng_api_common.scopes import Scope

from openzaak.components.besluiten.models import BesluitInformatieObject
from openzaak.components.zaken.models import ZaakInformatieObject
Expand Down Expand Up @@ -50,7 +51,46 @@ class InformatieobjectAuthorizationsFilterMixin(LooseFkAuthorizationsFilterMixin
def prefix(self):
return ""

def build_queryset(self, filters) -> models.QuerySet:
def build_queryset(self, local_filters, external_filters) -> models.QuerySet:
_local_filters = models.Q()
for k, v in local_filters.items():
_local_filters &= models.Q(**{k: v})

_external_filters = models.Q()
for k, v in external_filters.items():
_external_filters &= models.Q(**{k: v})

order_case = VertrouwelijkheidsAanduiding.get_order_expression(
"vertrouwelijkheidaanduiding"
)
annotations = {"_va_order": order_case}

if self.authorizations_lookup:
# If the current queryset is not an InformatieObjectQuerySet, first
# retrieve the canonical IDs of EnkelvoudigInformatieObjects
# for which the user is authorized and then return the objects
# related to those EnkelvoudigInformatieObjectCanonicals
model = apps.get_model("documenten", "EnkelvoudigInformatieObject")
if settings.CMIS_ENABLED:
filtered = model.objects.annotate(**annotations).filter(
_local_filters | _external_filters
)
else:
filtered = (
model.objects.annotate(**annotations)
.filter(_local_filters | _external_filters)
.values("canonical")
)
queryset = self.filter(informatieobject__in=filtered)
# bring it all together now to build the resulting queryset
else:
queryset = self.annotate(**annotations).filter(
_local_filters | _external_filters
)

return queryset

def build_queryset_cmis(self, filters) -> models.QuerySet:
order_case = VertrouwelijkheidsAanduiding.get_order_expression(
"vertrouwelijkheidaanduiding"
)
Expand All @@ -77,6 +117,28 @@ def build_queryset(self, filters) -> models.QuerySet:

return queryset

def ids_by_auth(self, scope, authorizations, local=True) -> models.QuerySet:
filters = self.get_filters(scope, authorizations, local)
queryset = self.build_queryset_cmis(filters)
return queryset.values_list("pk", flat=True)

def filter_for_authorizations(
self, scope: Scope, authorizations: models.QuerySet
) -> models.QuerySet:
if not settings.CMIS_ENABLED:
return super().filter_for_authorizations(scope, authorizations)

# todo implement error if no loose-fk field

authorizations_local, authorizations_external = self.get_authorizations(
scope, authorizations
)

ids_local = self.ids_by_auth(scope, authorizations_local, local=True)
ids_external = self.ids_by_auth(scope, authorizations_external, local=False)
queryset = self.filter(pk__in=ids_local.union(ids_external))
return queryset


class InformatieobjectQuerySet(
InformatieobjectAuthorizationsFilterMixin, models.QuerySet
Expand Down
58 changes: 58 additions & 0 deletions src/openzaak/components/documenten/tests/test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from rest_framework import status
from rest_framework.test import APITestCase
from vng_api_common.authorizations.models import Autorisatie
from vng_api_common.constants import ComponentTypes, VertrouwelijkheidsAanduiding
from vng_api_common.tests import AuthCheckMixin, reverse

Expand All @@ -24,6 +25,7 @@
from .factories import EnkelvoudigInformatieObjectFactory, GebruiksrechtenFactory

IOTYPE_EXTERNAL = "https://externe.catalogus.nl/api/v1/informatieobjecttypen/b71f72ef-198d-44d8-af64-ae1932df830a"
IOTYPE_EXTERNAL2 = "https://externe.catalogus.nl/api/v1/informatieobjecttypen/a7634cc6-b312-4d75-ba4d-a12e1fdb1dee"


class InformatieObjectScopeForbiddenTests(AuthCheckMixin, APITestCase):
Expand Down Expand Up @@ -386,6 +388,62 @@ def test_eio_list(self):
f"http://testserver{reverse(self.informatieobjecttype)}",
)

def test_eio_list_internal_and_external_with_filtering(self):
Autorisatie.objects.create(
applicatie=self.applicatie,
component=self.component,
scopes=self.scopes or [],
zaaktype="",
informatieobjecttype=IOTYPE_EXTERNAL,
besluittype="",
max_vertrouwelijkheidaanduiding=VertrouwelijkheidsAanduiding.openbaar,
)

EnkelvoudigInformatieObjectFactory.create(
informatieobjecttype=self.informatieobjecttype,
vertrouwelijkheidaanduiding=VertrouwelijkheidsAanduiding.openbaar,
bronorganisatie="000000000",
)
EnkelvoudigInformatieObjectFactory.create(
informatieobjecttype=IOTYPE_EXTERNAL,
vertrouwelijkheidaanduiding=VertrouwelijkheidsAanduiding.openbaar,
bronorganisatie="000000000",
)

# Should not show up due to filtering
EnkelvoudigInformatieObjectFactory.create(
informatieobjecttype=self.informatieobjecttype,
vertrouwelijkheidaanduiding=VertrouwelijkheidsAanduiding.openbaar,
bronorganisatie="123456789",
)
# Should not show up due to lacking permissions
EnkelvoudigInformatieObjectFactory.create(
informatieobjecttype=IOTYPE_EXTERNAL,
vertrouwelijkheidaanduiding=VertrouwelijkheidsAanduiding.geheim,
bronorganisatie="000000000",
)
EnkelvoudigInformatieObjectFactory.create(
informatieobjecttype=IOTYPE_EXTERNAL2,
vertrouwelijkheidaanduiding=VertrouwelijkheidsAanduiding.openbaar,
bronorganisatie="000000000",
)
url = reverse("enkelvoudiginformatieobject-list")

response = self.client.get(url, {"bronorganisatie": "000000000"})

self.assertEqual(response.status_code, status.HTTP_200_OK)

results = response.data["results"]

self.assertEqual(len(results), 2)
self.assertEqual(
results[0]["informatieobjecttype"],
f"http://testserver{reverse(self.informatieobjecttype)}",
)
self.assertEqual(
results[1]["informatieobjecttype"], IOTYPE_EXTERNAL,
)

def test_eio_retreive(self):
eio1 = EnkelvoudigInformatieObjectFactory.create(
informatieobjecttype=self.informatieobjecttype,
Expand Down
93 changes: 91 additions & 2 deletions src/openzaak/components/zaken/tests/test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from mozilla_django_oidc_db.models import OpenIDConnectConfig
from rest_framework import status
from rest_framework.test import APITestCase
from vng_api_common.authorizations.models import Autorisatie
from vng_api_common.constants import ComponentTypes, VertrouwelijkheidsAanduiding
from vng_api_common.tests import AuthCheckMixin, reverse

Expand Down Expand Up @@ -741,21 +742,109 @@ def setUpTestData(cls):
cls.zaaktype = ZaakTypeFactory.create()
super().setUpTestData()

def test_zaak_list(self):
def test_zaak_list_internal_and_external(self):
external_zaaktype1 = "https://externe.catalogus.nl/api/v1/zaaktypen/b71f72ef-198d-44d8-af64-ae1932df830a"
external_zaaktype2 = "https://externe.catalogus.nl/api/v1/zaaktypen/d530aa07-3e4e-42ff-9be8-3247b3a6e7e3"

Autorisatie.objects.create(
applicatie=self.applicatie,
component=self.component,
scopes=self.scopes or [],
zaaktype=external_zaaktype1,
informatieobjecttype="",
besluittype="",
max_vertrouwelijkheidaanduiding=VertrouwelijkheidsAanduiding.geheim,
)

# Should show up
ZaakFactory.create(
zaaktype=self.zaaktype,
vertrouwelijkheidaanduiding=VertrouwelijkheidsAanduiding.openbaar,
)
ZaakFactory.create(
zaaktype="https://externe.catalogus.nl/api/v1/zaaktypen/b71f72ef-198d-44d8-af64-ae1932df830a",
zaaktype=external_zaaktype1,
vertrouwelijkheidaanduiding=VertrouwelijkheidsAanduiding.openbaar,
)

# Should not show up, because there should be no overlap between the local and
# external filters
ZaakFactory.create(
zaaktype=self.zaaktype,
vertrouwelijkheidaanduiding=VertrouwelijkheidsAanduiding.geheim,
)
ZaakFactory.create(
zaaktype=external_zaaktype1,
vertrouwelijkheidaanduiding=VertrouwelijkheidsAanduiding.zeer_geheim,
)
ZaakFactory.create(
zaaktype=external_zaaktype2,
vertrouwelijkheidaanduiding=VertrouwelijkheidsAanduiding.zeer_geheim,
)
url = reverse("zaak-list")

response = self.client.get(url, **ZAAK_READ_KWARGS)

self.assertEqual(response.status_code, status.HTTP_200_OK)

results = response.data["results"]
self.assertEqual(len(results), 2)
self.assertEqual(results[0]["zaaktype"], external_zaaktype1)
self.assertEqual(
results[1]["zaaktype"], f"http://testserver{reverse(self.zaaktype)}"
)

def test_zaak_list_with_filtering(self):
"""
Assert that filtering still works when a non superuser application is used
"""
external_zaaktype1 = "https://externe.catalogus.nl/api/v1/zaaktypen/b71f72ef-198d-44d8-af64-ae1932df830a"
external_zaaktype2 = "https://externe.catalogus.nl/api/v1/zaaktypen/d530aa07-3e4e-42ff-9be8-3247b3a6e7e3"

Autorisatie.objects.create(
applicatie=self.applicatie,
component=self.component,
scopes=self.scopes or [],
zaaktype=external_zaaktype1,
informatieobjecttype="",
besluittype="",
max_vertrouwelijkheidaanduiding=VertrouwelijkheidsAanduiding.geheim,
)

# Should show up
ZaakFactory.create(
zaaktype=self.zaaktype,
bronorganisatie="000000000",
vertrouwelijkheidaanduiding=VertrouwelijkheidsAanduiding.openbaar,
)

# Should not show up due to filtering
ZaakFactory.create(
zaaktype=external_zaaktype1,
bronorganisatie="736160221",
vertrouwelijkheidaanduiding=VertrouwelijkheidsAanduiding.openbaar,
)

# Should not show up due to lacking permissions
ZaakFactory.create(
zaaktype=self.zaaktype,
vertrouwelijkheidaanduiding=VertrouwelijkheidsAanduiding.geheim,
)
ZaakFactory.create(
zaaktype=external_zaaktype1,
vertrouwelijkheidaanduiding=VertrouwelijkheidsAanduiding.zeer_geheim,
)
ZaakFactory.create(
zaaktype=external_zaaktype2,
vertrouwelijkheidaanduiding=VertrouwelijkheidsAanduiding.zeer_geheim,
)
url = reverse("zaak-list")

response = self.client.get(
url, {"bronorganisatie": "000000000"}, **ZAAK_READ_KWARGS
)

self.assertEqual(response.status_code, status.HTTP_200_OK)

results = response.data["results"]
self.assertEqual(len(results), 1)
self.assertEqual(
Expand Down

0 comments on commit e55ea1c

Please sign in to comment.