Browse Source

initial prototype

pull/3/head
Brett Langdon 6 years ago
commit
8336517fc5
No known key found for this signature in database GPG Key ID: E6600FB894DB3D19
7 changed files with 291 additions and 0 deletions
  1. +7
    -0
      .gitignore
  2. +12
    -0
      README.md
  3. +17
    -0
      setup.py
  4. +4
    -0
      sysaudit/__init__.py
  5. +118
    -0
      sysaudit/_csysaudit.c
  6. +39
    -0
      sysaudit/audit.py
  7. +94
    -0
      sysaudit/extras.py

+ 7
- 0
.gitignore View File

@ -0,0 +1,7 @@
*.so
*.py[co]
build/
ext/
dist/
*.egg-info
__pycache__

+ 12
- 0
README.md View File

@ -0,0 +1,12 @@
sysaudit
========
Backport module of [sys.audit](https://docs.python.org/3.8/library/sys.html#sys.audit)
and [sys.addaudithook](https://docs.python.org/3.8/library/sys.html#sys.addaudithook)
from Python 3.8.
This module provides the audit hooking mechanisms and some helpers to help
library developers usage of `sys.audit`.
**Note:** This module does _not_ backport any of the built-in
[audit events](https://docs.python.org/3.8/library/audit_events.html#audit-events).

+ 17
- 0
setup.py View File

@ -0,0 +1,17 @@
from setuptools import setup
from distutils.core import Extension
setup(
name="sysaudit",
version="0.1.0",
description="Backport module for sys.audit and sys.addaudithook from Python 3.8",
author="Brett Langdon",
author_email="me@brett.is",
url="https://github.com/brettlangdon/sysaudit",
ext_modules=[
Extension(
"sysaudit.csysaudit", sources=["sysaudit/_csysaudit.c"], optional=True
),
],
packages=["sysaudit"],
)

+ 4
- 0
sysaudit/__init__.py View File

@ -0,0 +1,4 @@
__all__ = ["audit", "addaudithook", "extras"]
from .audit import audit, addaudithook
from . import extras

+ 118
- 0
sysaudit/_csysaudit.c View File

@ -0,0 +1,118 @@
#include "Python.h"
struct CsysauditState {
PyObject* hooks;
};
#define csysaudit_state(o) ((struct CsysauditState*)PyModule_GetState(o))
static PyObject* csysaudit_audit(PyObject* self, PyObject* const *args, Py_ssize_t argc) {
if (argc == 0) {
PyErr_SetString(PyExc_TypeError, "audit() missing 1 required positional argument: 'event'");
return NULL;
}
PyObject* return_value = NULL;
PyObject* event_name = NULL;
PyTupleObject* event_args = NULL;
PyObject* hooks = NULL;
PyObject* hook = NULL;
event_name = args[0];
if (!event_name) {
PyErr_SetString(PyExc_TypeError, "expected str for argument 'event'");
goto exit;
}
if (!PyUnicode_Check(event_name)) {
PyErr_Format(PyExc_TypeError, "expected str for argument 'event', not %.200s",
Py_TYPE(event_name)->tp_name);
goto exit;
}
event_args = (PyTupleObject*)PyTuple_New(argc - 1);
if (!event_args) {
goto exit;
}
PyObject** dst = event_args->ob_item;
for (Py_ssize_t i = 1; i < argc; i++) {
PyObject* item = args[i];
Py_INCREF(item);
dst[i - 1] = item;
}
hooks = PyObject_GetIter(csysaudit_state(self)->hooks);
if (!hooks) {
goto exit;
}
while ((hook = PyIter_Next(hooks)) != NULL) {
PyObject *o;
o = PyObject_CallFunctionObjArgs(hook, event_name, event_args, NULL);
if (!o) {
break;
}
Py_DECREF(o);
Py_CLEAR(hook);
}
Py_INCREF(Py_None);
return_value = Py_None;
exit:
Py_XDECREF(hook);
Py_XDECREF(hooks);
Py_XDECREF(event_args);
return return_value;
};
static PyObject* csysaudit_addaudithook(PyObject* self, PyObject* const *args, Py_ssize_t nargs, PyObject* kwnames) {
PyObject* hook;
if (nargs == 0) {
PyErr_SetString(PyExc_TypeError, "addaudithook() missing 1 required positional argument: 'hook'");
return NULL;
}
hook = args[0];
if (PyList_Append(csysaudit_state(self)->hooks, hook) < 0) {
return NULL;
}
Py_RETURN_NONE;
};
static PyMethodDef csysaudit_methods[] = {
{"audit", (PyCFunction)(void(*)(void))csysaudit_audit, METH_FASTCALL, PyDoc_STR("") },
{"addaudithook", (PyCFunction)(void(*)(void))csysaudit_addaudithook, METH_FASTCALL, PyDoc_STR("") },
{ NULL, NULL }
};
PyDoc_VAR(csysaudit_doc) = PyDoc_STR("");
static struct PyModuleDef csysaudit = {
PyModuleDef_HEAD_INIT,
"sysaudit.csysaudit",
csysaudit_doc,
sizeof(struct CsysauditState),
csysaudit_methods,
NULL,
NULL,
NULL,
NULL
};
PyObject* PyInit_csysaudit() {
PyObject* res = PyModule_Create(&csysaudit);
if (!res) return NULL;
csysaudit_state(res)->hooks = PyList_New(0);
return res;
};

+ 39
- 0
sysaudit/audit.py View File

@ -0,0 +1,39 @@
__all__ = ["audit", "addaudithook"]
import sys
# Python 3.8+
# DEV: We could check `sys.version_info >= (3, 8)`, but if auditing ever gets
# back ported we want to take advantage of that
if hasattr(sys, "audit") and hasattr(sys, "addaudithook"):
audit = sys.audit
addaudithook = sys.addaudithook
else:
try:
from .csysaudit import audit, addaudithook
except ImportError:
_hooks = list()
def audit(event, *args):
global _hooks
# Grab a copy of hooks so we don't need to lock here
hooks = _hooks.copy()
for hook in hooks:
hook(event, args)
def addaudithook(callback):
global _hooks
# https://docs.python.org/3.8/library/sys.html#sys.addaudithook
# Raise an auditing event `sys.addaudithook` with no arguments.
# If any existing hooks raise an exception derived from RuntimeError,
# the new hook will not be added and the exception suppressed.
# As a result, callers cannot assume that their hook has been added
# unless they control all existing hooks.
try:
audit("sys.addaudithook")
except RuntimeError:
return
if callback not in _hooks:
_hooks.append(callback)

+ 94
- 0
sysaudit/extras.py View File

@ -0,0 +1,94 @@
__all__ = ["audithook", "auditfunc", "auditmanager", "auditwsgi"]
import contextlib
import functools
from .audit import audit, addaudithook
def audithook(*events):
def dec(func):
def hook(event, args):
if event in events:
func(*args)
addaudithook(hook)
return func
return dec
def auditfunc(event_prefix):
started_event = "{}.started".format(event_prefix)
finished_event = "{}.finished".format(event_prefix)
def dec(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
ret = None
exc = None
try:
audit(started_event, args, kwargs)
ret = func(*args, **kwargs)
return ret
except Exception as e:
exc = e
raise
finally:
audit(finished_event, ret, exc)
return wrapper
return dec
@contextlib.contextmanager
def auditmanager(event_prefix, *args):
started_event = "{}.started".format(event_prefix)
finished_event = "{}.finished".format(event_prefix)
audit(started_event, *args)
exc = None
try:
yield
except Exception as e:
exc = e
raise
finally:
audit(finished_event, exc)
def auditwsgi(app, event_prefix="wsgi"):
request_event = "{}.request.started".format(event_prefix)
finished_event = "{}.request.finished".format(event_prefix)
start_response_started_event = "{}.start_response.started".format(event_prefix)
start_response_finished_event = "{}.start_response.finished".format(event_prefix)
def wsgi(environ, start_response):
audit(request_event, environ, start_response)
def audit_start_response(status, headers):
audit(start_response_started_event, status, headers)
ret = None
exc = None
try:
ret = start_response(status, headers)
return ret
except Exception as e:
exc = e
raise
finally:
audit(start_response_finished_event, ret, exc)
ret = None
exc = None
try:
ret = app(environ, audit_start_response)
return ret
except Exception as e:
exc = e
raise
finally:
audit(finished_event, ret, exc)
return wsgi

Loading…
Cancel
Save