Skip to content

Commit

Permalink
👔(dashboard) disallow modifications to consents with REVOKED status.
Browse files Browse the repository at this point in the history
This ensures compliance with consent workflow rules.
- implemented restrictions to block updates on consents with a REVOKED status.
- updated tests to reflect the new behavior
- updated changelogs accordingly.
  • Loading branch information
ssorin committed Jan 20, 2025
1 parent 51d44be commit 9acaaa6
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 35 deletions.
1 change: 1 addition & 0 deletions src/dashboard/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ and this project adheres to
- add consent form to manage consents of one or many entities
- add admin integration for Entity, DeliveryPoint and Consent
- add mass admin action (make revoked) for consents
- block the updates of all new data if a consent has the status `REVOKED`
- block the updates of all new data if a consent has the status `VALIDATED`
- allow selected consent fields update if status changes from `VALIDATED` to `REVOKED`
- block the deletion of consent if it has the status `VALIDATED` or `REVOKED`
Expand Down
6 changes: 4 additions & 2 deletions src/dashboard/apps/consent/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,6 @@ def _is_update_allowed(self) -> bool:
- REVOKED
- can be updated without restriction
todo: add restriction: REVOKED consent cannot be modified
"""
ALLOWED_UPDATE_FIELDS = {"status", "revoked_at", "updated_at"}

Expand All @@ -129,7 +128,10 @@ def _is_update_allowed(self) -> bool:
loaded_status = self._loaded_values.get("status") # type: ignore[attr-defined]
updated_status = self.status

if loaded_status == VALIDATED:
if loaded_status == REVOKED:
raise ConsentWorkflowError(_("Revoked consent cannot be modified."))

elif loaded_status == VALIDATED:
if updated_status != REVOKED:
raise ConsentWorkflowError(
_('Validated consent can only be changed to the status "revoked".')
Expand Down
103 changes: 70 additions & 33 deletions src/dashboard/apps/consent/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,8 @@ def test_is_update_allowed():
- VALIDATED to AWAITING is not allowed.
- VALIDATED to REVOKED with not-allowed fields is not allowed.
- VALIDATED to REVOKED with allowed fields is allowed.
- REVOKED to AWAITING is allowed.
- REVOKED to VALIDATED is allowed.
- REVOKED to AWAITING is not allowed.
- REVOKED to VALIDATED is not allowed.
- Create new consent is allowed.
"""
from apps.consent.models import Consent
Expand Down Expand Up @@ -167,15 +167,23 @@ def test_is_update_allowed():
consent = ConsentFactory(status=REVOKED)
consent = Consent.objects.get(id=consent.id) # refresh the state in memory
consent.status = AWAITING
assert consent._is_update_allowed() is True
with pytest.raises(
ConsentWorkflowError,
match="Revoked consent cannot be modified.",
):
consent._is_update_allowed()

# update from REVOKED to VALIDATED
consent = ConsentFactory(status=REVOKED)
consent = Consent.objects.get(id=consent.id) # refresh the state in memory
consent.status = VALIDATED
assert consent._is_update_allowed() is True
with pytest.raises(
ConsentWorkflowError,
match="Revoked consent cannot be modified.",
):
consent._is_update_allowed()

# create a new consent
# create new consent is allowed.
dl = DeliveryPointFactory()
consent = Consent(delivery_point=dl, status=AWAITING)
assert consent._state.adding is True
Expand Down Expand Up @@ -281,33 +289,47 @@ def test_update_validated_consent_status():
def test_update_revoked_consent_status():
"""Tests updating a revoked consent status.
- REVOKED to AWAITING is authorized.
- REVOKED to VALIDATED is authorized.
- REVOKED with mixed fields is authorized.
- REVOKED to AWAITING is not authorized.
- REVOKED to VALIDATED is not authorized.
- REVOKED with mixed fields is not authorized.
"""
from apps.consent.models import Consent

# update the status from REVOKED to AWAITING
consent = ConsentFactory(status=REVOKED)
consent = Consent.objects.get(id=consent.id) # refresh the state in memory
consent.status = AWAITING
consent.save()
assert consent.status == AWAITING
with pytest.raises(
ConsentWorkflowError,
match="Revoked consent cannot be modified.",
):
consent.save()

consent.refresh_from_db()
assert consent.status == REVOKED

# update the status from REVOKED to VALIDATED
consent = ConsentFactory(status=REVOKED)
consent = Consent.objects.get(id=consent.id) # refresh the state in memory
consent.status = VALIDATED
consent.save()
assert consent.status == VALIDATED
with pytest.raises(
ConsentWorkflowError,
match="Revoked consent cannot be modified.",
):
consent.save()

consent.refresh_from_db()
assert consent.status == REVOKED

# update status with mixed fields
consent = ConsentFactory(status=REVOKED)
consent = Consent.objects.get(id=consent.id) # refresh the state in memory
consent.end = FAKE_TIME
consent.save()
consent.end = None
with pytest.raises(
ConsentWorkflowError,
match="Revoked consent cannot be modified.",
):
consent.save()

consent.refresh_from_db()
assert consent.status == REVOKED
assert consent.end == FAKE_TIME
assert consent.end is not None


@pytest.mark.django_db
Expand Down Expand Up @@ -379,27 +401,42 @@ def test_clean_revoked_consent_status():
# update the status from REVOKED to AWAITING
consent = ConsentFactory(status=REVOKED)
consent = Consent.objects.get(id=consent.id) # refresh the state in memory
assert consent.status == REVOKED
consent.status = AWAITING
consent.clean()
consent.save()
assert consent.status == AWAITING
with pytest.raises(
ConsentWorkflowError,
match="Revoked consent cannot be modified.",
):
consent.save()
consent.clean()

consent.refresh_from_db()
assert consent.status == REVOKED

# update the status from REVOKED to VALIDATED
consent = ConsentFactory(status=REVOKED)
consent = Consent.objects.get(id=consent.id) # refresh the state in memory
consent.status = VALIDATED
consent.clean()
consent.save()
assert consent.status == VALIDATED
with pytest.raises(
ConsentWorkflowError,
match="Revoked consent cannot be modified.",
):
consent.save()
consent.clean()

consent.refresh_from_db()
assert consent.status == REVOKED

# update status with mixed fields
consent = ConsentFactory(status=REVOKED)
consent = Consent.objects.get(id=consent.id) # refresh the state in memory
consent.end = FAKE_TIME
consent.clean()
consent.save()
consent.end = None
with pytest.raises(
ConsentWorkflowError,
match="Revoked consent cannot be modified.",
):
consent.save()
consent.clean()

consent.refresh_from_db()
assert consent.status == REVOKED
assert consent.end == FAKE_TIME
assert consent.end is not None


@pytest.mark.django_db
Expand Down

0 comments on commit 9acaaa6

Please sign in to comment.