Skip to content

Commit

Permalink
Log to sentry on GDPR failure
Browse files Browse the repository at this point in the history
Only log when exceptions are raised: e.g. auth or permissions fail.
Does not log about known errors like "has open payments".
This should make it easier to debug possible failures in the GDPR API.
  • Loading branch information
matti-lamppu committed Sep 24, 2024
1 parent 4ed377b commit 6cb74eb
Show file tree
Hide file tree
Showing 2 changed files with 150 additions and 4 deletions.
11 changes: 11 additions & 0 deletions api/gdpr/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

from users.anonymisation import anonymize_user_data, can_user_be_anonymized
from users.models import ProfileUser
from utils.sentry import SentryLogger

if TYPE_CHECKING:
from promise import Promise
Expand Down Expand Up @@ -100,3 +101,13 @@ def delete(self, request: Request, *args: Any, **kwargs: Any) -> Response:
transaction.set_rollback(True)

return response

def handle_exception(self, exc: Exception) -> Response:
try:
response: Response = super().handle_exception(exc)
except Exception as error:
SentryLogger.log_exception(error, details="Uncaught error in GDPR API")
raise

SentryLogger.log_message("GDPR API query failed.", details=response.data)
return response
143 changes: 139 additions & 4 deletions tests/test_gdpr_api/test_gdpr_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@
from dateutil.relativedelta import relativedelta
from django.urls import reverse
from django.utils import timezone
from rest_framework.exceptions import ErrorDetail

from merchants.enums import OrderStatus
from reservations.enums import ReservationStateChoice
from tests.factories import ApplicationFactory, PaymentOrderFactory, ReservationFactory, UserFactory
from tests.helpers import patch_method
from utils.sentry import SentryLogger

from .helpers import get_gdpr_auth_header, patch_oidc_config

Expand All @@ -25,6 +28,9 @@
]


# QUERY


def test_query_user_data__simple(api_client, settings):
user = UserFactory.create()

Expand Down Expand Up @@ -360,6 +366,7 @@ def test_query_user_data__full(api_client, settings):
}


@patch_method(SentryLogger.log_message)
def test_query_user_data__user_not_found(api_client, settings):
user = UserFactory.create()

Expand All @@ -374,7 +381,44 @@ def test_query_user_data__user_not_found(api_client, settings):
assert response.status_code == 404, response.data
assert response.data == {"detail": "No ProfileUser matches the given query."}

assert SentryLogger.log_message.call_count == 1
assert SentryLogger.log_message.call_args.args == ("GDPR API query failed.",)
assert SentryLogger.log_message.call_args.kwargs == {
"details": {
"detail": ErrorDetail(
string="No ProfileUser matches the given query.",
code="not_found",
)
}
}


@patch_method(SentryLogger.log_message)
def test_query_user_data__not_authenticated(api_client, settings):
user = UserFactory.create()

settings.GDPR_API_QUERY_SCOPE = "gdprquery"

url = reverse("gdpr_v1", kwargs={"uuid": str(user.uuid)})
with patch_oidc_config():
response = api_client.get(url)

assert response.status_code == 401, response.data
assert response.data == {"detail": "Authentication credentials were not provided."}

assert SentryLogger.log_message.call_count == 1
assert SentryLogger.log_message.call_args.args == ("GDPR API query failed.",)
assert SentryLogger.log_message.call_args.kwargs == {
"details": {
"detail": ErrorDetail(
string="Authentication credentials were not provided.",
code="not_authenticated",
)
}
}


@patch_method(SentryLogger.log_message)
def test_query_user_data__wrong_scope(api_client, settings):
user = UserFactory.create()

Expand All @@ -389,6 +433,49 @@ def test_query_user_data__wrong_scope(api_client, settings):
assert response.status_code == 403, response.data
assert response.data == {"detail": "You do not have permission to perform this action."}

assert SentryLogger.log_message.call_count == 1
assert SentryLogger.log_message.call_args.args == ("GDPR API query failed.",)
assert SentryLogger.log_message.call_args.kwargs == {
"details": {
"detail": ErrorDetail(
string="You do not have permission to perform this action.",
code="permission_denied",
)
}
}


@patch_method(SentryLogger.log_message)
def test_query_user_data__insufficient_loa(api_client, settings):
user = UserFactory.create(username="foo")

settings.GDPR_API_QUERY_SCOPE = "gdprquery"
auth_header = get_gdpr_auth_header(user, scopes=[settings.GDPR_API_QUERY_SCOPE], loa="low")
api_client.credentials(HTTP_AUTHORIZATION=auth_header)

url = reverse("gdpr_v1", kwargs={"uuid": str(user.uuid)})
with patch_oidc_config():
response = api_client.get(url)

assert response.status_code == 403, response.data
assert response.data == {"detail": "You do not have permission to perform this action."}
user.refresh_from_db()
assert user.username == "foo"

assert SentryLogger.log_message.call_count == 1
assert SentryLogger.log_message.call_args.args == ("GDPR API query failed.",)
assert SentryLogger.log_message.call_args.kwargs == {
"details": {
"detail": ErrorDetail(
string="You do not have permission to perform this action.",
code="permission_denied",
)
}
}


# DELETE


def test_delete_user_data__should_anonymize(api_client, settings):
user = UserFactory.create(username="foo")
Expand Down Expand Up @@ -528,6 +615,7 @@ def test_delete_user_data__dont_anonymize_if_open_applications(api_client, setti
assert user.username == "foo"


@patch_method(SentryLogger.log_message)
def test_delete_user_data__cannot_anonymize_other_users_data(api_client, settings):
user = UserFactory.create(username="foo")
other_user = UserFactory.create(username="bar")
Expand All @@ -545,7 +633,19 @@ def test_delete_user_data__cannot_anonymize_other_users_data(api_client, setting
other_user.refresh_from_db()
assert other_user.username == "bar"

assert SentryLogger.log_message.call_count == 1
assert SentryLogger.log_message.call_args.args == ("GDPR API query failed.",)
assert SentryLogger.log_message.call_args.kwargs == {
"details": {
"detail": ErrorDetail(
string="You do not have permission to perform this action.",
code="permission_denied",
)
}
}


@patch_method(SentryLogger.log_message)
def test_delete_user_data__not_authenticated(api_client, settings):
user = UserFactory.create(username="foo")

Expand All @@ -560,7 +660,19 @@ def test_delete_user_data__not_authenticated(api_client, settings):
user.refresh_from_db()
assert user.username == "foo"

assert SentryLogger.log_message.call_count == 1
assert SentryLogger.log_message.call_args.args == ("GDPR API query failed.",)
assert SentryLogger.log_message.call_args.kwargs == {
"details": {
"detail": ErrorDetail(
string="Authentication credentials were not provided.",
code="not_authenticated",
)
}
}


@patch_method(SentryLogger.log_message)
def test_delete_user_data__wrong_scope(api_client, settings):
user = UserFactory.create(username="foo")

Expand All @@ -577,23 +689,46 @@ def test_delete_user_data__wrong_scope(api_client, settings):
user.refresh_from_db()
assert user.username == "foo"

assert SentryLogger.log_message.call_count == 1
assert SentryLogger.log_message.call_args.args == ("GDPR API query failed.",)
assert SentryLogger.log_message.call_args.kwargs == {
"details": {
"detail": ErrorDetail(
string="You do not have permission to perform this action.",
code="permission_denied",
)
}
}

def test_query_user_data__insufficient_loa(api_client, settings):

@patch_method(SentryLogger.log_message)
def test_delete_user_data__insufficient_loa(api_client, settings):
user = UserFactory.create(username="foo")

settings.GDPR_API_QUERY_SCOPE = "gdprquery"
auth_header = get_gdpr_auth_header(user, scopes=[settings.GDPR_API_QUERY_SCOPE], loa="low")
settings.GDPR_API_DELETE_SCOPE = "gdprdelete"
auth_header = get_gdpr_auth_header(user, scopes=[settings.GDPR_API_DELETE_SCOPE], loa="low")
api_client.credentials(HTTP_AUTHORIZATION=auth_header)

url = reverse("gdpr_v1", kwargs={"uuid": str(user.uuid)})
with patch_oidc_config():
response = api_client.get(url)
response = api_client.delete(url)

assert response.status_code == 403, response.data
assert response.data == {"detail": "You do not have permission to perform this action."}
user.refresh_from_db()
assert user.username == "foo"

assert SentryLogger.log_message.call_count == 1
assert SentryLogger.log_message.call_args.args == ("GDPR API query failed.",)
assert SentryLogger.log_message.call_args.kwargs == {
"details": {
"detail": ErrorDetail(
string="You do not have permission to perform this action.",
code="permission_denied",
)
}
}


@pytest.mark.parametrize("dry_run", ["true", "True", "TRUE", "1", 1, True])
def test_delete_user_data__dont_anonymize_if_dryrun(api_client, settings, dry_run):
Expand Down

0 comments on commit 6cb74eb

Please sign in to comment.