[Kimchi-devel] [PATCH] [Wok 2/4] Log user requests

Lucio Correia luciojhc at linux.vnet.ibm.com
Fri Feb 26 18:56:28 UTC 2016


Signed-off-by: Lucio Correia <luciojhc at linux.vnet.ibm.com>
---
 src/wok/control/base.py | 81 +++++++++++++++++++++++++++++++++++++++++++++----
 src/wok/root.py         | 28 +++++++++++++++++
 src/wok/server.py       |  6 ++++
 src/wok/utils.py        | 12 ++++++++
 4 files changed, 121 insertions(+), 6 deletions(-)

diff --git a/src/wok/control/base.py b/src/wok/control/base.py
index 363fd60..f44771e 100644
--- a/src/wok/control/base.py
+++ b/src/wok/control/base.py
@@ -32,6 +32,15 @@ from wok.control.utils import validate_params
 from wok.exception import InvalidOperation, InvalidParameter
 from wok.exception import MissingParameter, NotFoundError
 from wok.exception import OperationFailed, UnauthorizedError, WokException
+from wok.reqlogger import RequestRecord
+from wok.utils import get_plugin_from_request
+
+
+LOG_DISABLED_METHODS = ['GET']
+
+# Default request log messages
+COLLECTION_DEFAULT_LOG = "request on collection"
+RESOURCE_DEFAULT_LOG = "request on resource"
 
 
 class Resource(object):
@@ -58,6 +67,7 @@ class Resource(object):
         self.model_args = (ident,)
         self.role_key = None
         self.admin_methods = []
+        self.log_map = {}
 
     def _redirect(self, action_result, code=303):
         uri_params = []
@@ -102,7 +112,8 @@ class Resource(object):
     def _generate_action_handler_base(self, action_name, render_fn,
                                       destructive=False, action_args=None):
         def wrapper(*args, **kwargs):
-            validate_method(('POST'), self.role_key, self.admin_methods)
+            method = 'POST'
+            validate_method((method), self.role_key, self.admin_methods)
             try:
                 self.lookup()
                 if not self.is_authorized():
@@ -137,6 +148,17 @@ class Resource(object):
                 raise cherrypy.HTTPError(500, e.message)
             except WokException, e:
                 raise cherrypy.HTTPError(500, e.message)
+            finally:
+                params = {}
+                if model_args:
+                    params = {'ident': str(model_args[0])}
+
+                RequestRecord(
+                    self.getRequestMessage(method, action_name) % params,
+                    app=get_plugin_from_request(),
+                    req=method,
+                    user=cherrypy.session.get(USER_NAME, 'N/A')
+                ).log()
 
         wrapper.__name__ = action_name
         wrapper.exposed = True
@@ -162,6 +184,18 @@ class Resource(object):
             raise cherrypy.HTTPError(500, e.message)
         except InvalidOperation, e:
             raise cherrypy.HTTPError(400, e.message)
+        finally:
+            method = 'DELETE'
+            params = {}
+            if self.model_args:
+                params = {'ident': str(self.model_args[0])}
+
+            RequestRecord(
+                self.getRequestMessage(method, 'default') % params,
+                app=get_plugin_from_request(),
+                req=method,
+                user=cherrypy.session.get(USER_NAME, 'N/A')
+            ).log()
 
     @cherrypy.expose
     def index(self, *args, **kargs):
@@ -203,14 +237,23 @@ class Resource(object):
         return user_name in users or len(set(user_groups) & set(groups)) > 0
 
     def update(self, *args, **kargs):
+        params = parse_request()
+
         try:
             update = getattr(self.model, model_fn(self, 'update'))
         except AttributeError:
             e = InvalidOperation('WOKAPI0003E', {'resource':
                                                  get_class_name(self)})
             raise cherrypy.HTTPError(405, e.message)
+        finally:
+            method = 'PUT'
+            RequestRecord(
+                self.getRequestMessage(method) % params,
+                app=get_plugin_from_request(),
+                req=method,
+                user=cherrypy.session.get(USER_NAME, 'N/A')
+            ).log()
 
-        params = parse_request()
         validate_params(params, self, 'update')
 
         args = list(self.model_args) + [params]
@@ -222,6 +265,13 @@ class Resource(object):
     def get(self):
         return wok.template.render(get_class_name(self), self.data)
 
+    def getRequestMessage(self, method, action='default'):
+        """
+        Provide customized user activity log message in inherited classes
+        through log_map attribute.
+        """
+        return self.log_map.get(method, {}).get(action, RESOURCE_DEFAULT_LOG)
+
     @property
     def data(self):
         """
@@ -273,6 +323,7 @@ class Collection(object):
         self.model_args = []
         self.role_key = None
         self.admin_methods = []
+        self.log_map = {}
 
     def create(self, params, *args):
         try:
@@ -341,18 +392,28 @@ class Collection(object):
         data = self.filter_data(resources, fields_filter)
         return wok.template.render(get_class_name(self), data)
 
+    def getRequestMessage(self, method):
+        """
+        Provide customized user activity log message in inherited classes
+        through log_map attribute.
+        """
+        return self.log_map.get(method, {}).get('default',
+                                                COLLECTION_DEFAULT_LOG)
+
     @cherrypy.expose
     def index(self, *args, **kwargs):
+        params = {}
         method = validate_method(('GET', 'POST'),
                                  self.role_key, self.admin_methods)
 
         try:
             if method == 'GET':
-                filter_params = cherrypy.request.params
-                validate_params(filter_params, self, 'get_list')
-                return self.get(filter_params)
+                params = cherrypy.request.params
+                validate_params(params, self, 'get_list')
+                return self.get(params)
             elif method == 'POST':
-                return self.create(parse_request(), *args)
+                params = parse_request()
+                return self.create(params, *args)
         except InvalidOperation, e:
             raise cherrypy.HTTPError(400, e.message)
         except InvalidParameter, e:
@@ -365,6 +426,14 @@ class Collection(object):
             raise cherrypy.HTTPError(500, e.message)
         except WokException, e:
             raise cherrypy.HTTPError(500, e.message)
+        finally:
+            if method not in LOG_DISABLED_METHODS:
+                RequestRecord(
+                    self.getRequestMessage(method) % params,
+                    app=get_plugin_from_request(),
+                    req=method,
+                    user=cherrypy.session.get(USER_NAME, 'N/A')
+                ).log()
 
 
 class AsyncCollection(Collection):
diff --git a/src/wok/root.py b/src/wok/root.py
index 174a9b9..7a5fe9c 100644
--- a/src/wok/root.py
+++ b/src/wok/root.py
@@ -32,6 +32,16 @@ from wok.control import sub_nodes
 from wok.control.base import Resource
 from wok.control.utils import parse_request
 from wok.exception import MissingParameter, OperationFailed
+from wok.reqlogger import RequestRecord
+from wok.utils import get_plugin_from_request
+
+
+ROOT_REQUESTS = {
+    'POST': {
+        'login': "User '%(username)s' login",
+        'logout': "User '%(username)s' logout",
+    },
+}
 
 
 class Root(Resource):
@@ -129,6 +139,7 @@ class WokRoot(Root):
         self.paths = wok_paths
         self.domain = 'wok'
         self.messages = messages
+        self.log_map = ROOT_REQUESTS
 
     @cherrypy.expose
     def login(self, *args):
@@ -144,10 +155,27 @@ class WokRoot(Root):
             user_info = auth.login(username, password)
         except OperationFailed:
             raise cherrypy.HTTPError(401)
+        finally:
+            method = 'POST'
+            RequestRecord(
+                self.getRequestMessage(method, 'login') % params,
+                app=get_plugin_from_request(),
+                req=method,
+                user=cherrypy.session.get(auth.USER_NAME, 'N/A')
+            ).log()
 
         return json.dumps(user_info)
 
     @cherrypy.expose
     def logout(self):
+        method = 'POST'
+        params = {'username': cherrypy.session.get(auth.USER_NAME, 'N/A')}
+        RequestRecord(
+            self.getRequestMessage(method, 'logout') % params,
+            app=get_plugin_from_request(),
+            req=method,
+            user=params['username']
+        ).log()
+
         auth.logout()
         return '{}'
diff --git a/src/wok/server.py b/src/wok/server.py
index 75b41d5..122efff 100644
--- a/src/wok/server.py
+++ b/src/wok/server.py
@@ -34,10 +34,12 @@ from wok.config import paths, PluginConfig, WokConfig
 from wok.control import sub_nodes
 from wok.model import model
 from wok.proxy import start_proxy, terminate_proxy
+from wok.reqlogger import RequestLogger
 from wok.root import WokRoot
 from wok.safewatchedfilehandler import SafeWatchedFileHandler
 from wok.utils import get_enabled_plugins, import_class
 
+
 LOGGING_LEVEL = {"debug": logging.DEBUG,
                  "info": logging.INFO,
                  "warning": logging.WARNING,
@@ -65,6 +67,7 @@ class Server(object):
         start_proxy(options)
 
         make_dirs = [
+            os.path.abspath(config.get_log_download_path()),
             os.path.dirname(os.path.abspath(options.access_log)),
             os.path.dirname(os.path.abspath(options.error_log)),
             os.path.dirname(os.path.abspath(config.get_object_store()))
@@ -130,6 +133,9 @@ class Server(object):
         # Add error log file to cherrypy configuration
         cherrypy.log.error_log.addHandler(h)
 
+        # start request logger
+        self.reqLogger = RequestLogger()
+
         # only add logrotate if wok is installed
         if paths.installed:
 
diff --git a/src/wok/utils.py b/src/wok/utils.py
index 48fe414..e3a4124 100644
--- a/src/wok/utils.py
+++ b/src/wok/utils.py
@@ -123,6 +123,18 @@ def get_all_tabs():
     return tabs
 
 
+def get_plugin_from_request():
+    """
+    Returns name of plugin being requested. If no plugin, returns 'wok'.
+    """
+    script_name = cherrypy.request.script_name
+    split = script_name.split('/')
+    if len(split) > 2 and split[1] == 'plugins':
+        return split[2]
+
+    return 'wok'
+
+
 def import_class(class_path):
     module_name, class_name = class_path.rsplit('.', 1)
     try:
-- 
1.9.1




More information about the Kimchi-devel mailing list