Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mvj 234 gdpr api #774

Merged
merged 4 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion forms/models/form.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from django.db.models.fields.json import JSONField
from django.utils.translation import gettext_lazy as _
from enumfields import EnumField
from helsinki_gdpr.models import SerializableMixin

from users.models import User

Expand Down Expand Up @@ -206,7 +207,7 @@ def get_attachment_file_upload_to(instance, filename):
)


class Attachment(models.Model):
class Attachment(SerializableMixin, models.Model):
name = models.CharField(max_length=255)
attachment = models.FileField(upload_to=get_attachment_file_upload_to)
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Time created"))
Expand All @@ -216,6 +217,13 @@ class Attachment(models.Model):
field = models.ForeignKey(Field, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE)

# GDPR API
serialize_fields = (
{"name": "name"},
{"name": "attachment"},
{"name": "created_at"},
)

recursive_get_related_skip_relations = [
"user",
]
Expand Down
Empty file added gdpr/__init__.py
Empty file.
22 changes: 22 additions & 0 deletions gdpr/tests/test_gdpr_permission.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from helusers.authz import UserAuthorization
from requests import Request

from gdpr.views import AmrPermission, MvjGDPRAPIView


def test_amr_permission_has_permission():
request = Request()
request.auth = UserAuthorization(user=None, api_token_payload={"amr": ["suomi_fi"]})
view = MvjGDPRAPIView()
permission = AmrPermission()
assert permission.has_permission(request, view) is True


def test_amr_permission_has_not_permission():
request = Request()
request.auth = UserAuthorization(
user=None, api_token_payload={"amr": ["helsinki_tunnus"]}
)
view = MvjGDPRAPIView()
permission = AmrPermission()
assert permission.has_permission(request, view) is False
9 changes: 9 additions & 0 deletions gdpr/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from users.models import User


def get_user(user: User) -> User:
juho-kettunen-nc marked this conversation as resolved.
Show resolved Hide resolved
"""
Get the user provider, as User is the GDPR API root model. GDPR API implementation
by default attempts to get it from the root models user attribute.
"""
return user
17 changes: 17 additions & 0 deletions gdpr/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from helsinki_gdpr.views import GDPRAPIView
from rest_framework.permissions import BasePermission
from rest_framework.request import Request


class AmrPermission(BasePermission):
"""Authentication Method Reference (amr)"""

def has_permission(self, request: Request, _view: "MvjGDPRAPIView") -> bool:
amrs: list[str] = request.auth.data.get("amr", [])
allowed_amr = "suomi_fi"
has_allowed_amr = allowed_amr in amrs
return has_allowed_amr


class MvjGDPRAPIView(GDPRAPIView):
permission_classes = GDPRAPIView.permission_classes + [AmrPermission]
14 changes: 14 additions & 0 deletions mvj/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,12 @@ def get_git_revision_hash():
LASKE_PAYMENTS_DIRECTORY=(str, ""),
LASKE_PAYMENTS_KEY_TYPE=(str, ""),
LASKE_PAYMENTS_KEY=(bytes, ""),
GDPR_API_URL_PATTERN=(str, "v1/profiles/<uuid:uuid>"),
GDPR_API_MODEL=(str, "users.User"),
GDPR_API_MODEL_LOOKUP=(str, "uuid"),
GDPR_API_QUERY_SCOPE=(str, ""),
GDPR_API_DELETE_SCOPE=(str, ""),
GDPR_API_USER_PROVIDER=(str, ""),
)

env_file = project_root(".env")
Expand Down Expand Up @@ -325,6 +331,14 @@ def get_git_revision_hash():
"REQUIRE_API_SCOPE_FOR_AUTHENTICATION": env.bool("TOKEN_AUTH_REQUIRE_SCOPE_PREFIX"),
}

# https://github.com/City-of-Helsinki/helsinki-profile-gdpr-api
GDPR_API_URL_PATTERN = env("GDPR_API_URL_PATTERN")
GDPR_API_MODEL = env("GDPR_API_MODEL")
GDPR_API_MODEL_LOOKUP = env("GDPR_API_MODEL_LOOKUP")
GDPR_API_QUERY_SCOPE = env("GDPR_API_QUERY_SCOPE")
GDPR_API_DELETE_SCOPE = env("GDPR_API_DELETE_SCOPE")
GDPR_API_USER_PROVIDER = env("GDPR_API_USER_PROVIDER")

LASKE_VALUES = {
"distribution_channel": "10",
"division": "10",
Expand Down
35 changes: 27 additions & 8 deletions mvj/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
MeetingMemoViewset,
TargetStatusViewset,
)
from gdpr.views import MvjGDPRAPIView
from leasing.api_functions import CalculateIncreaseWith360DayCalendar
from leasing.report.viewset import ReportViewSet
from leasing.views import CloudiaProxy, VirreProxy, ktj_proxy
Expand Down Expand Up @@ -364,28 +365,46 @@
CalculateIncreaseWith360DayCalendar.as_view(),
),
]

gdpr_urls = [
path(
getattr(
settings,
"GDPR_API_URL_PATTERN",
"v1/profiles/<uuid:pk>",
),
MvjGDPRAPIView.as_view(),
name="gdpr_v1",
)
]
additional_pub_api_paths = [
path(
"direct_reservation_to_favourite/<str:uuid>/",
DirectReservationToFavourite.as_view(),
name="pub_direct_reservation_to_favourite",
),
path("plot_search_ui/", PlotSearchUIDataView.as_view(), name="pub_plot_search_ui"),
# Enables oidc backchannel logout, requires setting `HELUSERS_BACK_CHANNEL_LOGOUT_ENABLED = True`
# to be useful
path("helauth/", include("helusers.urls")),
# GDPR API
path(
"gdpr-api/",
include(
(
gdpr_urls,
"gdpr", # Namespace
),
),
),
]

api_urls = router.urls + additional_api_paths

# Path: v1/pub/
pub_api_urls = [
path(
"pub/",
include(
pub_router.urls
+ additional_pub_api_paths
# Enables oidc backchannel logout, requires setting `HELUSERS_BACK_CHANNEL_LOGOUT_ENABLED = True`
# to be useful
+ [path("helauth/", include("helusers.urls"))]
),
include(pub_router.urls + additional_pub_api_paths),
)
]
credit_integration_urls = [
Expand Down
16 changes: 14 additions & 2 deletions plotsearch/models/plot_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from django.utils.translation import gettext_lazy as _
from django.utils.translation import pgettext_lazy
from enumfields import EnumField
from helsinki_gdpr.models import SerializableMixin
from rest_framework.serializers import ValidationError
from safedelete.models import SOFT_DELETE, SafeDeleteModel

Expand Down Expand Up @@ -482,7 +483,7 @@ class AreaSearchStatus(models.Model):
]


class AreaSearch(models.Model):
class AreaSearch(SerializableMixin, models.Model):
# In Finnish: aluehaku

geometry = gmodels.MultiPolygonField(
Expand Down Expand Up @@ -566,6 +567,14 @@ def lessor_name(self):
blank=True,
)

# GDPR API
serialize_fields = (
{"name": "address"},
{"name": "received_date"},
# plotsearch.AreaSearchAttachment
juho-kettunen-nc marked this conversation as resolved.
Show resolved Hide resolved
{"name": "area_search_attachments"},
)

recursive_get_related_skip_relations = [
"related_plot_applications",
"areasearchattachment",
Expand Down Expand Up @@ -598,7 +607,7 @@ def get_area_search_attachment_upload_to(instance, filename):
)


class AreaSearchAttachment(NameModel):
class AreaSearchAttachment(SerializableMixin, NameModel):
attachment = models.FileField(
upload_to=get_area_search_attachment_upload_to, null=True, blank=True
)
Expand All @@ -615,6 +624,9 @@ class AreaSearchAttachment(NameModel):
blank=True,
)

# GDPR API
serialize_fields = ({"name": "attachment"}, {"name": "created_at"})

recursive_get_related_skip_relations = [
"user",
]
Expand Down
1 change: 1 addition & 0 deletions requirements.in
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ djangorestframework~=3.15.2
djangorestframework-gis~=1.1.0
drf-yasg[validation]~=1.21.8
docxtpl~=0.18.0
helsinki-profile-gdpr-api~=0.2.0
oracledb~=2.1.0
psycopg[binary]~=3.2.3
pysftp~=0.2.9
Expand Down
17 changes: 16 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ attrs==23.2.0
# jsonschema
# referencing
# zeep
authlib==1.3.2
# via drf-oidc-auth
babel==2.15.0
# via docxcompose
bcrypt==4.1.3
Expand All @@ -46,6 +48,8 @@ click==8.1.7
# via pyhanko
cryptography==42.0.7
# via
# authlib
# drf-oidc-auth
# oracledb
# paramiko
# pyhanko
Expand Down Expand Up @@ -77,7 +81,9 @@ django==4.2.16
# django-stubs-ext
# django-xhtml2pdf
# djangorestframework
# drf-oidc-auth
# drf-yasg
# helsinki-profile-gdpr-api
django-admin-rangefilter==0.13.2
# via -r requirements.in
django-anymail==12.0
Expand All @@ -99,7 +105,9 @@ django-filter==24.3
# -r requirements.in
# djangorestframework-gis
django-helusers==0.13.0
# via -r requirements.in
# via
# -r requirements.in
# helsinki-profile-gdpr-api
django-model-utils==5.0.0
# via -r requirements.in
django-modeltranslation==0.19.10
Expand Down Expand Up @@ -128,17 +136,23 @@ djangorestframework==3.15.2
# via
# -r requirements.in
# djangorestframework-gis
# drf-oidc-auth
# drf-yasg
# helsinki-profile-gdpr-api
djangorestframework-gis==1.1
# via -r requirements.in
docxcompose==1.4.0
# via docxtpl
docxtpl==0.18.0
# via -r requirements.in
drf-oidc-auth==3.0.0
# via helsinki-profile-gdpr-api
drf-yasg[validation]==1.21.8
# via -r requirements.in
ecdsa==0.19.0
# via python-jose
helsinki-profile-gdpr-api==0.2.0
# via -r requirements.in
html5lib==1.1
# via xhtml2pdf
idna==3.7
Expand Down Expand Up @@ -251,6 +265,7 @@ requests==2.32.3
# -r requirements.in
# django-anymail
# django-helusers
# drf-oidc-auth
# pyhanko
# pyhanko-certvalidator
# requests-file
Expand Down
15 changes: 14 additions & 1 deletion users/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,26 @@
from django.core.cache import cache
from django.db import models, transaction
from django.utils.translation import gettext_lazy as _
from helsinki_gdpr.models import SerializableMixin
from helusers.models import AbstractUser, ADGroupMapping
from rest_framework.authtoken.models import Token


class User(AbstractUser):
class User(AbstractUser, SerializableMixin):
service_units = models.ManyToManyField("leasing.ServiceUnit", related_name="users")

# GDPR API, meant for PlotSearch app users
serialize_fields = (
{"name": "uuid"},
{"name": "first_name"},
{"name": "last_name"},
{"name": "email"},
# forms.Attachment
{"name": "attachment"},
# plotsearch.AreaSearch
{"name": "areasearch_set"},
)

recursive_get_related_skip_relations = [
"auth_token",
"logentry",
Expand Down