Skip to content

Conversation

@MaciekBaron
Copy link
Contributor

@MaciekBaron MaciekBaron commented Dec 18, 2025

What is the context of this PR?

This PR addresses ticket CMS 906 and expands the activity logs to cover more actions and content types.

Note that this change introduces a new dependency, namely django-crum, in order to expose user details when using signals.

How to review

Perform one of the following actions and confirm that the activity was logged in site history:

  • Edit a bundle
  • Add a page to a bundle
  • Remove a page from a bundle
  • Add a team to a bundle
  • Remove a team from a bundle
  • Change bundle schedule
  • Add a dataset
  • Remove a dataset
  • Add/edit/remove an image
  • Add/edit/update a document
  • Download a CSV from a preview

Follow-up Actions

Confirm that using the django-crum module is allowed for the project.

@MaciekBaron MaciekBaron added the New Feature Adds new functionality. label Dec 22, 2025
@MaciekBaron MaciekBaron force-pushed the feature/CMS-906-audit-logging branch from e34734b to b194bb0 Compare December 22, 2025 11:28
@MaciekBaron MaciekBaron marked this pull request as ready for review December 22, 2025 14:21
@MaciekBaron MaciekBaron requested a review from a team as a code owner December 22, 2025 14:21
pyproject.toml Outdated
cryptography = "^45.0.4"
django-pglock = "^1.7.2"
wagtail-schema-org = "^4.1.1"
django-crum = "^0.7.9"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a quick note, while I do a quick pass, as you called out in stand-up, I will come back to this. Personally, I'm not a fan of this, especially using thread locals for such behaviour, and the fact that it hasn't been maintained for over six years. I think we should revisit the approach. What options have been considered?.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, images and documents do create log entries, but only in specific scenarios - specifically, when using the non-multi version of the forms.

This means that the form at /admin/images/multiple/add/ does not create a log entry when adding an image. Meanwhile, /admin/images/add/ will (albeit you can only upload one image there). The default form you are taken to is the multi one.

Likewise if you mass delete images, that won't trigger the deletion log entries. It will however create the entries if you delete one image using the delete dropdown option.

I have removed django-crud for now and added tests to show the existing behaviour in: 58c6bb1

I have consulted this with the Wagtail team and they seem to believe that the fact that the log entries are created in the first place is because of a refactor done in 2014, following the work done on wagtail/wagtail#7550 and performing a full audit log of all image/document actions wasn't part of a feature plan.

We have several things we could do here:

  • Drop django-crud and instead write our own thread-local approach which we maintain (shouldn't be too complicated code-wide)
  • Raise an issue in the Wagtail repo to look into implementing full logging for images and documents, so that we don't just have a partial implementation
  • Force users to go through a single image upload form, and disable mass deletion of images via the admin panel

Copy link
Contributor

@MebinAbraham MebinAbraham Jan 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you considered https://docs.python.org/3/library/contextvars.html? I'm not sure if threading.local is safe or future-proof for us; we might use a non-sync worker in the future or have async calls. Flask, which under the hood uses Werkzeug, moved away from threading.local to ContextVar also. However, even with both options, there could be gaps where we use thread pools.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed, if we have no other strong reason to add support for it apart from the multi-image/document upload path, we can consider disabling that functionality and revisit it in the future.

Copy link
Contributor

@MebinAbraham MebinAbraham left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have done an initial pass (not checked all events we need to log yet), and overall it looks good.

One key gap is the need for a ChooserView mixin or similar mechanism. We should ensure that any chooser displaying sensitive information elsewhere in the system is logged. While I don't have a comprehensive list immediately available, some areas that require attention include:

  • Logging chooser activity for headline figures. For example, when headline figures are listed in a chooser on a Topic page, we should log an action since users are viewing unpublished values.
  • Listing of images or documents, since their titles may contain sensitive information.

We should also review other choosers to identify any additional instances where sensitive information may be exposed and ensure appropriate auditing is in place.

action="content.chart_download",
instance=page,
data={
"chart_title": chart_title,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't want to log anything that may contain sensitive info.

chart_title = chart_data.get("title")

# Log the chart download for audit trail
log(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to check whether we need to log the request along with the same event log.

Comment on lines +258 to +264
for team_id in added:
team = Team.objects.get(id=team_id)
log(action="bundles.team_added", instance=instance, data={"team_name": team.name})

for team_id in removed:
team = Team.objects.get(id=team_id)
log(action="bundles.team_removed", instance=instance, data={"team_name": team.name})
Copy link
Contributor

@MebinAbraham MebinAbraham Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One for discussion.
This makes things somewhat nice, but the site history record has a record for each page (and other parts of bundle). Ideally, it would be easier to have a single record for such an event. It also makes tracing this via logs much simpler.

I would like us to consider either:

  • One log action for each change, such as one for the team, one for the page, etc.
  • One log action per bundle save (essentially a diff of bundles) - easier for tracing via logs.

Any specific reason for the current approach (other than formatting issue in site history view?). Let's discuss before potentially making any changes.

audit_logger.info(
instance.action,
extra={
"event": instance.action,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is already the event, so this is essentially a duplicate. Any reason this was done?

"event": "bundles.page_added", "data": {"event": "bundles.page_added"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was so that the message and the data both contain the action, for JSON parsing. As this was literally the same (as you rightly pointed out), I have changed that in this commit, but happy to remove it from the data if we feel it is redundant: b712391

Comment on lines 94 to 101
chart_title = log_entry.data.get("chart_title")
chart_id = log_entry.data.get("chart_id", "unknown")
message = "Downloaded chart CSV for chart "

if chart_title:
message += chart_title
else:
message += f"with ID {chart_id}"
Copy link
Contributor

@MebinAbraham MebinAbraham Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Title here would have been fine as only approved persons have access to audit events in the CMS, it was just in sys logs, we can't emit these. I guess we pass it in data, which is used here, but also in the log output, unless we had a mechanism to pass sensitive content to be used by here and not used by audit_logger.info. Thoughts?

@sanjeevz3009 sanjeevz3009 self-requested a review January 27, 2026 16:37
@MaciekBaron
Copy link
Contributor Author

@MebinAbraham logging of viewed headline figures in choosers added here: 081b796

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

New Feature Adds new functionality.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants