Skip to content

Commit

Permalink
Add logging of creation and updates to a verification request in audi…
Browse files Browse the repository at this point in the history
…t log
  • Loading branch information
mrchrisadams committed May 3, 2024
1 parent 3ecf98f commit a3acf8c
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 4 deletions.
26 changes: 26 additions & 0 deletions apps/accounts/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
from django import template as dj_template
from django.conf import settings
from django.contrib import admin, messages
from django.contrib.admin.models import CHANGE
from django.contrib.auth.admin import Group, GroupAdmin, UserAdmin
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import PermissionDenied
from django.core.mail import send_mail
from django.db import models
Expand Down Expand Up @@ -1448,6 +1450,25 @@ def send_approval_email(
template_html="emails/verification_request_approved.html",
)

def log_message(
self,
provider_request: ProviderRequest,
request: HttpRequest,
message_type: int,
message: str,
):
"""
Log the approval, rejection or otherwise of a provider request
"""
LogEntry.objects.log_action(
user_id=request.user.id,
content_type_id=ContentType.objects.get_for_model(provider_request).pk,
object_id=provider_request.pk,
object_repr=str(provider_request),
action_flag=message_type,
change_message=message,
)

@admin.action(description="Approve", permissions=["change"])
def mark_approved(self, request, queryset):
for provider_request in queryset:
Expand All @@ -1456,6 +1477,8 @@ def mark_approved(self, request, queryset):
# so we can send the appropriate email
existing_provider = provider_request.provider
hp = provider_request.approve()
self.log_message(provider_request, request, CHANGE, "Approved")

hp_href = reverse(
"greenweb_admin:accounts_hostingprovider_change", args=[hp.id]
)
Expand All @@ -1482,6 +1505,7 @@ def mark_rejected(self, request, queryset):
for provider_request in queryset:
provider_request.status = ProviderRequestStatus.REJECTED.value
provider_request.save()
self.log_message(provider_request, request, CHANGE, "Rejected")

message = mark_safe(
f"""
Expand All @@ -1504,6 +1528,7 @@ def mark_removed(self, request, queryset):
for provider_request in queryset:
provider_request.status = ProviderRequestStatus.REMOVED.value
provider_request.save()
self.log_message(provider_request, request, CHANGE, "Removed")

message = mark_safe(
f"""
Expand All @@ -1522,6 +1547,7 @@ def mark_open(self, request, queryset):
for provider_request in queryset:
provider_request.status = ProviderRequestStatus.OPEN.value
provider_request.save()
self.log_message(provider_request, request, CHANGE, "Changes Requested")

message = mark_safe(
f"""
Expand Down
72 changes: 72 additions & 0 deletions apps/accounts/tests/test_provider_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import pytest
from django import urls
from django.conf import settings
from django.contrib.admin.models import ADDITION, CHANGE, LogEntry
from django.contrib.contenttypes.models import ContentType
from django.contrib.messages.storage.fallback import FallbackStorage
from django.contrib.sessions.middleware import SessionMiddleware
from django.core.exceptions import ValidationError
Expand Down Expand Up @@ -340,6 +342,15 @@ def test_wizard_view_happy_path(
created_pr = models.ProviderRequest.objects.get(id=pr.id)
assert created_pr.status == models.ProviderRequestStatus.PENDING_REVIEW

# and: we have logged the creation in the history for this provider request
# to give us an audit trail
log_message = LogEntry.objects.get(object_id=created_pr.id)

assert log_message.user == user
assert log_message.action_flag == ADDITION
assert ContentType.objects.get_for_model(pr).pk == log_message.content_type_id
assert log_message.change_message == "Provider request created for review"


def _create_provider_request(client, form_data) -> HttpResponse:
"""
Expand Down Expand Up @@ -2228,3 +2239,64 @@ def test_email_request_email_confirmation_is_sent(
# And does the html as well?
html_content = email.alternatives[0][0]
assert email_copy in html_content


@pytest.mark.django_db
@pytest.mark.parametrize(
"staff_action,expected_status",
(
("approved", "Approved"),
("rejected", "Rejected"),
("removed", "Removed"),
("open", "Changes Requested"),
),
)
@override_flag("provider_request", active=True)
def test_staff_review_is_logged(
hosting_provider_with_sample_user,
greenweb_staff_user,
provider_request_factory,
rf,
staff_action,
expected_status,
):
"""
When a staff member reviews a provider request, and rejects, approves or
otherwise makes a decision, about the validity we should see this logged
in the audit log
"""

# Given: a provider request created by a hosting provider
pr = provider_request_factory.create(provider=hosting_provider_with_sample_user)
ProviderRequestLocationFactory.create(request=pr)

# And: a staff user logged into the admin, viewing the verification request
pr_admin = ac_admin.ProviderRequest(
models.ProviderRequest, admin_site.greenweb_admin
)
admin_update_path = reverse(
"greenweb_admin:accounts_providerrequest_change", args=[pr.id]
)
req = rf.get(admin_update_path)
req.user = greenweb_staff_user

# we need to add a session middleware to the request
# without this attempts to place a message in the request
# for the staff user will fail in this test
middleware = SessionMiddleware()
middleware.process_request(req)
messages = FallbackStorage(req)
req._messages = messages

queryset = models.ProviderRequest.objects.filter(id=pr.id)

# When: the staff member makes a decision on the request's validity
getattr(pr_admin, f"mark_{staff_action}")(req, queryset)

# Then: we should see the entry in the audit log for the request
log_message = LogEntry.objects.get(object_id=pr.id)

assert ContentType.objects.get_for_model(pr).pk == log_message.content_type_id
assert log_message.user == greenweb_staff_user
assert log_message.action_flag == CHANGE
assert log_message.change_message == expected_status
35 changes: 31 additions & 4 deletions apps/accounts/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
from dal import autocomplete
from django.conf import settings
from django.contrib import messages
from django.contrib.admin.models import LogEntry, ADDITION

from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.models import Group
from django.contrib.contenttypes.models import ContentType
from django.contrib.sites.shortcuts import get_current_site
from django.core.files.storage import DefaultStorage
from django.db.models.query import QuerySet
Expand Down Expand Up @@ -52,6 +55,7 @@
)
from .permissions import manage_provider
from .utils import send_email
from django.http import HttpRequest

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -279,6 +283,26 @@ class Steps(Enum):
Steps.PREVIEW.value: "provider_registration/preview.html",
}

def log_creation(
self,
provider_request: ProviderRequest,
request: HttpRequest,
message_type: int = ADDITION,
message: str = "Provider request created for review",
):
"""
Log the creation of a new ProviderRequest instance
"""

LogEntry.objects.log_action(
user_id=request.user.id,
content_type_id=ContentType.objects.get_for_model(provider_request).pk,
object_id=provider_request.pk,
object_repr=str(provider_request),
action_flag=ADDITION,
change_message=message,
)

def dispatch(self, request, *args, **kwargs):
"""
This view is re-used for 3 different use cases:
Expand Down Expand Up @@ -452,6 +476,9 @@ def _process_formset(formset, request):
pr.status = ProviderRequestStatus.PENDING_REVIEW.value
pr.save()

# log the creation in the history of this request so we have an accessble audit trail
self.log_creation(pr, self.request)

# send an email notification to the author and green web staff
self._send_notification_email(pr)

Expand Down Expand Up @@ -548,14 +575,14 @@ def _send_notification_email(self, provider_request: ProviderRequest):

# For a new provider request, use this subject line
subject = (
f"Your Green Web Dataset verification request: "
f"{mark_safe(provider_request.name)}"
f"Your Green Web Dataset verification request: "
f"{mark_safe(provider_request.name)}"
)

if provider_request.provider:
ctx["provider"] = provider_request.provider
# For an update to an existing provider, use this subject line
subject=(
subject = (
f"Your Green Web Dataset update request: "
f"{mark_safe(provider_request.name)}"
)
Expand Down

0 comments on commit a3acf8c

Please sign in to comment.