Skip to content

Add audit logs - #647

Open
varmar05 wants to merge 8 commits into
developfrom
audit_logs
Open

Add audit logs#647
varmar05 wants to merge 8 commits into
developfrom
audit_logs

Conversation

@varmar05

@varmar05 varmar05 commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

Introduce a pluggable audit module with NullSink default and emit() API.

Wire SQLAlchemy listeners for user and project lifecycle events, explicit emits for auth and sync endpoints (login, password, access grants/revocations, version push, soft/hard delete, restore).

Add actor_context()/request_context() helpers, device_id capture, scope_id for workspace-scoped filtering, and target_type auto-derivation.

Introduce a pluggable audit module with NullSink default and emit() API.

Wire SQLAlchemy listeners for user and project lifecycle events,
explicit emits for auth and sync endpoints (login, password, access
grants/revocations, version push, soft/hard delete, restore).

Add actor_context()/request_context() helpers, device_id capture, scope_id
for workspace-scoped filtering, and target_type auto-derivation.

This is groundwork for adding more events and to be extended in EE with custom sinks, query API, and retention policy.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coveralls

coveralls commented Jul 1, 2026

Copy link
Copy Markdown

Coverage Report for CI Build 30524900752

Coverage increased (+0.3%) to 92.496%

Details

  • Coverage increased (+0.3%) from the base build.
  • Patch coverage: 13 uncovered changes across 4 files (550 of 563 lines covered, 97.69%).
  • 2 coverage regressions across 1 file.

Uncovered Changes

File Changed Covered %
server/mergin/audit/listeners.py 46 41 89.13%
server/mergin/utils.py 12 8 66.67%
server/mergin/auth/listeners.py 26 24 92.31%
server/mergin/tests/test_audit_events.py 266 264 99.25%
Total (25 files) 563 550 97.69%

Coverage Regressions

2 previously-covered lines in 1 file lost coverage.

File Lines Losing Coverage Coverage
server/mergin/sync/utils.py 2 85.4%

Coverage Stats

Coverage Status
Relevant Lines: 10807
Covered Lines: 9996
Line Coverage: 92.5%
Coverage Strength: 0.92 hits per line

💛 - Coveralls

varmar05 and others added 6 commits July 6, 2026 16:45
Implements structured audit event emission for all relevant user and project actions.
Update AuditEvent dataclass with new target columns: project_id, workspace_id and user_id.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…events

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Also simplify test_get_projects_by_uuids to avoid the fake-workspace-id hack.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@varmar05
varmar05 marked this pull request as ready for review July 30, 2026 07:25
@varmar05
varmar05 requested review from MarcelGeo and harminius and removed request for MarcelGeo July 30, 2026 07:25
… events

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@varmar05
varmar05 changed the base branch from master to develop July 30, 2026 08:03

@harminius harminius left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Some minor comments.

.with_entities(User.id, User.email)
.first()
)
return (found.email, found.id) if found else (None, None)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

how about return ... else (login_str, None) to know at least something?

)
return "", 200
else:
abort(403, "You do not have permissions")

Copy link
Copy Markdown
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 this?
we do log login fail..

Comment on lines +581 to +596

if old_active and not user.active:
emit(
AuthEventType.USER_DEACTIVATED,
**actor_context(),
user_id=user.id,
target_email=user.email,
)
elif not old_active and user.active:
emit(
AuthEventType.USER_RESTORED,
**actor_context(),
user_id=user.id,
target_email=user.email,
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

we don't log superadmin assignment?

db.session.add(user)
db.session.commit()

if old_active and not user.active:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

should we re-querry the user?

Comment on lines +603 to +608
emit(
AuthEventType.USER_MARKED_FOR_DELETION,
**actor_context(),
user_id=user.id,
target_email=user.email,
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

shall we emit after the action?

**actor_context(),
project_id=project.id,
workspace_id=project.workspace_id,
target_email=requester.email if requester else None,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

we should get the actor email when they are authenticated, no?
(applies also for the following emits)

project_id=project.id,
workspace_id=project.workspace_id,
target_email=requester.email if requester else None,
role=permission,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

the project name could be useful here

Comment on lines 296 to +297
project.delete()
db.session.info.pop("audit_skip_project_update", None)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

it would be safer to put his in try: and finally: to avoid stale info message, but that's rather a theoretical risk

project_id=project.id,
workspace_id=project.workspace_id,
target_email=user.email if user else None,
role=removed_role.value,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

again, the project name

project_id=p.id,
workspace_id=p.workspace_id,
project_name=f"{p.workspace.name}/{p.name}",
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

could we add info it was done by this celery task?

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants