| |
@@ -0,0 +1,105 @@
|
| |
+ # Copyright (C) 2016 Ipsilon project Contributors, for license see COPYING
|
| |
+
|
| |
+ from ipsilon.util.data import AuditStore
|
| |
+ from ipsilon.util.log import Log
|
| |
+ from ipsilon.util.user import UserSession
|
| |
+ import datetime
|
| |
+
|
| |
+
|
| |
+ class Audit(Log):
|
| |
+ def __init__(self):
|
| |
+ self._store = AuditStore()
|
| |
+ self._handlers = {}
|
| |
+
|
| |
+ def audit(self, provider_type, provider_name, event_type, **event_attrs):
|
| |
+ """
|
| |
+ Log an audit entry in the audit database.
|
| |
+
|
| |
+ provider_type, provider_name, and event_type are all strings.
|
| |
+
|
| |
+ Callers can store arbitrary data in the audit record (so long as it can
|
| |
+ be serialised into JSON format) by passing keyword arguments to the
|
| |
+ method. To render this data into a readable format, the caller can
|
| |
+ implement a get_audit_display method and register with the audit
|
| |
+ handler. See the register_provider method below.
|
| |
+
|
| |
+ There are two "special" attributes:
|
| |
+ 'user' is the name of the user causing the event. Many callers to
|
| |
+ this function will already have a User or UserSession object to
|
| |
+ hand, so passing this value in can save some processing. It if
|
| |
+ isn't provided, the function will fetch the data itself.
|
| |
+ 'timestamp' can be used to override the timestamp related to the
|
| |
+ event. If it is not specified, the current time will be used.
|
| |
+ """
|
| |
+ if 'user' in event_attrs:
|
| |
+ user = event_attrs['user']
|
| |
+ del event_attrs['user']
|
| |
+ else:
|
| |
+ us = UserSession()
|
| |
+ user = us.user
|
| |
+ if 'timestamp' in event_attrs:
|
| |
+ timestamp = event_attrs['timestamp']
|
| |
+ del event_attrs['timestamp']
|
| |
+ else:
|
| |
+ timestamp = datetime.datetime.now()
|
| |
+
|
| |
+ self._store.add_record(timestamp, user, provider_type, provider_name,
|
| |
+ event_type, event_attrs)
|
| |
+
|
| |
+ def fetch_records(self, user=None, limit=None, age=None):
|
| |
+ """
|
| |
+ Returns audit records from the audit database. By default, it will
|
| |
+ return all records in the store, which probably isn't what you want.
|
| |
+
|
| |
+ Output can be restricted to records relating to a specific user by
|
| |
+ specifying the username as the argument. The limit argument restricts
|
| |
+ the output to the most recent LIMIT records. age allows the
|
| |
+ restriction of the output to records in the most recent AGE hours.
|
| |
+
|
| |
+ Records are returned as a list, newest record first. Each record is a
|
| |
+ dict with the following keys:
|
| |
+ 'ts' is the event timestamp, as a DateTime.
|
| |
+ 'user' is the username related to the event.
|
| |
+ 'session' is the CherryPy session ID of the event, and can be used to
|
| |
+ trace the activities of a single login session.
|
| |
+ 'ip' is the source IP address of the request that triggered the audit
|
| |
+ event.
|
| |
+ 'provtype' is the provider type specified by the audit caller.
|
| |
+ 'provname' is the provider name specified by the audit caller.
|
| |
+ 'eventtype' is the event type.
|
| |
+ 'eventattrs' is a dict containing any extra attributes passed to the
|
| |
+ audit call.
|
| |
+ 'event' is generated by the appropriate event rendering function, if
|
| |
+ one was registered. Otherwise it will have a default value of
|
| |
+ 'Unknown event'.
|
| |
+ """
|
| |
+ records = self._store.fetch_records(user, limit, age)
|
| |
+ for r in records:
|
| |
+ r['event'] = 'Unknown event'
|
| |
+ if r['provtype'] in self._handlers:
|
| |
+ if r['provname'] in self._handlers[r['provtype']]:
|
| |
+ r['event'] = self._handlers[r['provtype']][r['provname']](
|
| |
+ r['eventtype'], r['eventattrs'])
|
| |
+ return records
|
| |
+
|
| |
+ def register_provider(self, provider_type, provider_name, handler):
|
| |
+ """
|
| |
+ Registers a handler function to render audit entries into human-
|
| |
+ readable display form. The handler must be a callable matching the
|
| |
+ following signature:
|
| |
+
|
| |
+ render_event_method(event_type, event_attrs)
|
| |
+
|
| |
+ event_type is the type string passed in the original audit call. Any
|
| |
+ extra data passed to audit via keyword args is passed to the rendering
|
| |
+ function in the event_attrs dict. The render function should return a
|
| |
+ string.
|
| |
+ """
|
| |
+ if not callable(handler):
|
| |
+ raise ValueError("Handler is not callable, or doesn't have a "
|
| |
+ "get_audit_display method.")
|
| |
+
|
| |
+ if provider_type not in self._handlers:
|
| |
+ self._handlers[provider_type] = {}
|
| |
+
|
| |
+ self._handlers[provider_type][provider_name] = handler
|
| |
I would prefer it if you don't use a raw SqlQuery, I'm working on getting rid of them in the rest of the code :).
Is there any chance you can either use the standard select type?
If not, I would like it if you could abstract this away in the Query or BaseStore layer.