54 lines
2.4 KiB
Python
54 lines
2.4 KiB
Python
"""Audit administrative financial grant changes with the actual request actor.
|
|
Only selected non-secret fields and payments permissions are captured."""
|
|
from django.apps import apps
|
|
from django.db import OperationalError, ProgrammingError
|
|
from .models import PaymentAuditEvent
|
|
|
|
TARGETS = {'accounts/user': 'accounts.User', 'auth/group': 'auth.Group',
|
|
'staff/organizationalunit': 'staff.OrganizationalUnit', 'staff/unitmembership': 'staff.UnitMembership',
|
|
'staff/staffprofile': 'staff.StaffProfile'}
|
|
|
|
|
|
def access_snapshot(obj):
|
|
fields = ['is_active','account_type','user_id','unit_id','ticket_scope','role','primary_unit_id']
|
|
result = {field:getattr(obj,field) for field in fields if hasattr(obj,field)}
|
|
for name in ['permissions','user_permissions']:
|
|
relation = getattr(obj,name,None)
|
|
if relation is not None:
|
|
result[name] = sorted(relation.filter(content_type__app_label='payments').values_list('codename',flat=True))
|
|
if hasattr(obj,'groups'):
|
|
result['groups'] = sorted(obj.groups.filter(permissions__content_type__app_label='payments').values_list('pk',flat=True).distinct())
|
|
return result
|
|
|
|
|
|
class FinancialGrantAuditMiddleware:
|
|
def __init__(self,get_response):
|
|
self.get_response=get_response
|
|
|
|
def __call__(self,request):
|
|
target = self.target(request)
|
|
before = access_snapshot(target) if target else None
|
|
response = self.get_response(request)
|
|
if target and response.status_code < 400:
|
|
try:
|
|
target.refresh_from_db()
|
|
after=access_snapshot(target)
|
|
if before != after:
|
|
from .services import audit
|
|
audit(request.user,'FINANCIAL_ACCESS_CHANGED',target,{'before':before,'after':after})
|
|
except target.DoesNotExist:
|
|
from .services import audit
|
|
audit(request.user,'FINANCIAL_ACCESS_REMOVED',target,{'before':before})
|
|
return response
|
|
|
|
def target(self,request):
|
|
if request.method != 'POST' or not request.user.is_authenticated or not request.path.startswith('/x5admin/'):
|
|
return None
|
|
parts=request.path.strip('/').split('/')
|
|
if len(parts)<5 or not parts[3].isdecimal():
|
|
return None
|
|
label=TARGETS.get('/'.join(parts[1:3]))
|
|
if not label:
|
|
return None
|
|
return apps.get_model(label).objects.filter(pk=parts[3]).first()
|