[PATCH] [Wok 0/6] Log failed user requests

This patch requires "[Kimchi] Do not break the logging of failed requests". Otherwise, some tests will be broken. Lucio Correia (6): Revert "Use past verbs" Parse request before authorization check Use status code 200 for PUT requests on resource Log failed login/logout attempts Log failed user requests Add status code to request log message src/wok/control/base.py | 164 ++++++++++++++++++++++++++++++------------------ src/wok/i18n.py | 4 +- src/wok/reqlogger.py | 10 +-- src/wok/root.py | 44 +++++++++---- 4 files changed, 140 insertions(+), 82 deletions(-) -- 1.9.1

This reverts commit 04358829a34f246efa7198191b4ca7a851c02bbb. --- src/wok/i18n.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wok/i18n.py b/src/wok/i18n.py index 4fc21f1..d6cb17c 100644 --- a/src/wok/i18n.py +++ b/src/wok/i18n.py @@ -58,6 +58,6 @@ messages = { # These messages (ending with L) are for user log purposes "WOKCOL0001L": _("Request made on collection"), "WOKRES0001L": _("Request made on resource"), - "WOKROOT0001L": _("User '%(username)s' logged in"), - "WOKROOT0002L": _("User '%(username)s' logged out"), + "WOKROOT0001L": _("User '%(username)s' login"), + "WOKROOT0002L": _("User '%(username)s' logout"), } -- 1.9.1

The variable request is used to log request, but if authorization fails, it will not be initialize and break logging. Signed-off-by: Lucio Correia <luciojhc@linux.vnet.ibm.com> --- src/wok/control/base.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/wok/control/base.py b/src/wok/control/base.py index 419179a..b861b9c 100644 --- a/src/wok/control/base.py +++ b/src/wok/control/base.py @@ -122,13 +122,14 @@ class Resource(object): method = 'POST' validate_method((method), self.role_key, self.admin_methods) try: + request = parse_request() + validate_params(request, self, action_name) + self.lookup() if not self.is_authorized(): raise UnauthorizedError('WOKAPI0009E') model_args = list(self.model_args) - request = parse_request() - validate_params(request, self, action_name) if action_args is not None: model_args.extend( request[key] if key in request.keys() else None -- 1.9.1

Some PUT requests on resource have no status code defined. Other were returning 202, which is not the best option for PUT requests. Signed-off-by: Lucio Correia <luciojhc@linux.vnet.ibm.com> --- src/wok/control/base.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/wok/control/base.py b/src/wok/control/base.py index b861b9c..9b34c55 100644 --- a/src/wok/control/base.py +++ b/src/wok/control/base.py @@ -263,6 +263,7 @@ class Resource(object): args = list(self.model_args) + [params] ident = update(*args) self._redirect(ident) + cherrypy.response.status = 200 self.lookup() return self.get() -- 1.9.1

Signed-off-by: Lucio Correia <luciojhc@linux.vnet.ibm.com> --- src/wok/root.py | 44 +++++++++++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/src/wok/root.py b/src/wok/root.py index 902e203..e2b9216 100644 --- a/src/wok/root.py +++ b/src/wok/root.py @@ -31,10 +31,9 @@ from wok.config import paths as wok_paths 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.exception import MissingParameter from wok.message import WokMessage from wok.reqlogger import RequestRecord -from wok.utils import get_plugin_from_request ROOT_REQUESTS = { @@ -152,28 +151,43 @@ class WokRoot(Root): @cherrypy.expose def login(self, *args): + method = 'POST' + code = self.getRequestMessage(method, 'login') + app = 'wok' + ip = cherrypy.request.remote.ip + try: params = parse_request() + msg = WokMessage(code, params).get_text(prepend_code=False) username = params['username'] password = params['password'] except KeyError, item: + RequestRecord( + msg, + app=app, + req=method, + status=400, + user='N/A', + ip=ip + ).log() + e = MissingParameter('WOKAUTH0003E', {'item': str(item)}) raise cherrypy.HTTPError(400, e.message) try: + status = 200 user_info = auth.login(username, password) - except OperationFailed: - raise cherrypy.HTTPError(401) + except cherrypy.HTTPError, e: + status = e.status + raise finally: - method = 'POST' - code = self.getRequestMessage(method, 'login') - msg = WokMessage(code, params).get_text(prepend_code=False) RequestRecord( msg, - app=get_plugin_from_request(), + app=app, req=method, - user=cherrypy.session.get(auth.USER_NAME, 'N/A'), - ip=cherrypy.request.remote.ip + status=status, + user='N/A', + ip=ip ).log() return json.dumps(user_info) @@ -184,13 +198,17 @@ class WokRoot(Root): code = self.getRequestMessage(method, 'logout') params = {'username': cherrypy.session.get(auth.USER_NAME, 'N/A')} msg = WokMessage(code, params).get_text(prepend_code=False) + ip = cherrypy.request.remote.ip + + auth.logout() + RequestRecord( msg, - app=get_plugin_from_request(), + app='wok', req=method, + status=200, user=params['username'], - ip=cherrypy.request.remote.ip + ip=ip ).log() - auth.logout() return '{}' -- 1.9.1

Signed-off-by: Lucio Correia <luciojhc@linux.vnet.ibm.com> --- src/wok/control/base.py | 158 ++++++++++++++++++++++++++++++------------------ 1 file changed, 98 insertions(+), 60 deletions(-) diff --git a/src/wok/control/base.py b/src/wok/control/base.py index 9b34c55..f1cce11 100644 --- a/src/wok/control/base.py +++ b/src/wok/control/base.py @@ -119,6 +119,10 @@ class Resource(object): def _generate_action_handler_base(self, action_name, render_fn, destructive=False, action_args=None): def wrapper(*args, **kwargs): + # status must be always set in order to request be logged. + # use 500 as fallback for "exception not handled" cases. + status = 500 + method = 'POST' validate_method((method), self.role_key, self.admin_methods) try: @@ -138,7 +142,36 @@ class Resource(object): action_fn = getattr(self.model, model_fn(self, action_name)) action_result = action_fn(*model_args) + status = 200 + if destructive is False or \ + ('persistent' in self.info.keys() and + self.info['persistent'] is True): + result = render_fn(self, action_result) + status = cherrypy.response.status + return result + except MissingParameter, e: + status = 400 + raise cherrypy.HTTPError(status, e.message) + except InvalidParameter, e: + status = 400 + raise cherrypy.HTTPError(status, e.message) + except InvalidOperation, e: + status = 400 + raise cherrypy.HTTPError(status, e.message) + except UnauthorizedError, e: + status = 403 + raise cherrypy.HTTPError(status, e.message) + except NotFoundError, e: + status = 404 + raise cherrypy.HTTPError(status, e.message) + except OperationFailed, e: + status = 500 + raise cherrypy.HTTPError(status, e.message) + except WokException, e: + status = 500 + raise cherrypy.HTTPError(status, e.message) + finally: # log request code = self.getRequestMessage(method, action_name) reqParams = utf8_dict(self.log_args, request) @@ -147,29 +180,11 @@ class Resource(object): msg, app=get_plugin_from_request(), req=method, + status=status, user=cherrypy.session.get(USER_NAME, 'N/A'), ip=cherrypy.request.remote.ip ).log() - if destructive is False or \ - ('persistent' in self.info.keys() and - self.info['persistent'] is True): - return render_fn(self, action_result) - except MissingParameter, e: - raise cherrypy.HTTPError(400, e.message) - except InvalidParameter, e: - raise cherrypy.HTTPError(400, e.message) - except InvalidOperation, e: - raise cherrypy.HTTPError(400, e.message) - except UnauthorizedError, e: - raise cherrypy.HTTPError(403, e.message) - except NotFoundError, e: - raise cherrypy.HTTPError(404, e.message) - except OperationFailed, e: - raise cherrypy.HTTPError(500, e.message) - except WokException, e: - raise cherrypy.HTTPError(500, e.message) - wrapper.__name__ = action_name wrapper.exposed = True return wrapper @@ -190,13 +205,13 @@ class Resource(object): e = InvalidOperation('WOKAPI0002E', {'resource': get_class_name(self)}) raise cherrypy.HTTPError(405, e.message) - except OperationFailed, e: - raise cherrypy.HTTPError(500, e.message) - except InvalidOperation, e: - raise cherrypy.HTTPError(400, e.message) @cherrypy.expose def index(self, *args, **kargs): + # status must be always set in order to request be logged. + # use 500 as fallback for "exception not handled" cases. + status = 500 + method = validate_method(('GET', 'DELETE', 'PUT'), self.role_key, self.admin_methods) @@ -208,30 +223,42 @@ class Resource(object): result = {'GET': self.get, 'DELETE': self.delete, 'PUT': self.update}[method](*args, **kargs) + + status = cherrypy.response.status except InvalidOperation, e: - raise cherrypy.HTTPError(400, e.message) + status = 400 + raise cherrypy.HTTPError(status, e.message) except InvalidParameter, e: - raise cherrypy.HTTPError(400, e.message) + status = 400 + raise cherrypy.HTTPError(status, e.message) except UnauthorizedError, e: - raise cherrypy.HTTPError(403, e.message) + status = 403 + raise cherrypy.HTTPError(status, e.message) except NotFoundError, e: - raise cherrypy.HTTPError(404, e.message) + status = 404 + raise cherrypy.HTTPError(status, e.message) except OperationFailed, e: - raise cherrypy.HTTPError(500, e.message) + status = 500 + raise cherrypy.HTTPError(status, e.message) except WokException, e: - raise cherrypy.HTTPError(500, e.message) - - # log request - if method not in LOG_DISABLED_METHODS: - code = self.getRequestMessage(method) - msg = WokMessage(code, self.log_args).get_text(prepend_code=False) - RequestRecord( - msg, - app=get_plugin_from_request(), - req=method, - user=cherrypy.session.get(USER_NAME, 'N/A'), - ip=cherrypy.request.remote.ip - ).log() + status = 500 + raise cherrypy.HTTPError(status, e.message) + except cherrypy.HTTPError, e: + status = e.status + raise + finally: + # log request + if method not in LOG_DISABLED_METHODS: + code = self.getRequestMessage(method) + msg = WokMessage(code, self.log_args) + RequestRecord( + msg.get_text(prepend_code=False), + app=get_plugin_from_request(), + req=method, + status=status, + user=cherrypy.session.get(USER_NAME, 'N/A'), + ip=cherrypy.request.remote.ip + ).log() return result @@ -311,10 +338,6 @@ class AsyncResource(Resource): e = InvalidOperation('WOKAPI0002E', {'resource': get_class_name(self)}) raise cherrypy.HTTPError(405, e.message) - except OperationFailed, e: - raise cherrypy.HTTPError(500, e.message) - except InvalidOperation, e: - raise cherrypy.HTTPError(400, e.message) cherrypy.response.status = 202 return wok.template.render("Task", task) @@ -437,6 +460,10 @@ class Collection(object): @cherrypy.expose def index(self, *args, **kwargs): + # status must be always set in order to request be logged. + # use 500 as fallback for "exception not handled" cases. + status = 500 + params = {} method = validate_method(('GET', 'POST'), self.role_key, self.admin_methods) @@ -449,7 +476,31 @@ class Collection(object): elif method == 'POST': params = parse_request() result = self.create(params, *args) - + status = cherrypy.response.status + return result + except InvalidOperation, e: + status = 400 + raise cherrypy.HTTPError(status, e.message) + except InvalidParameter, e: + status = 400 + raise cherrypy.HTTPError(status, e.message) + except MissingParameter, e: + status = 400 + raise cherrypy.HTTPError(status, e.message) + except NotFoundError, e: + status = 404 + raise cherrypy.HTTPError(status, e.message) + except OperationFailed, e: + status = 500 + raise cherrypy.HTTPError(status, e.message) + except WokException, e: + status = 500 + raise cherrypy.HTTPError(status, e.message) + except cherrypy.HTTPError, e: + status = e.status + raise + finally: + if method not in LOG_DISABLED_METHODS: # log request code = self.getRequestMessage(method) reqParams = utf8_dict(self.log_args, params) @@ -458,24 +509,11 @@ class Collection(object): msg, app=get_plugin_from_request(), req=method, + status=status, user=cherrypy.session.get(USER_NAME, 'N/A'), ip=cherrypy.request.remote.ip ).log() - return result - except InvalidOperation, e: - raise cherrypy.HTTPError(400, e.message) - except InvalidParameter, e: - raise cherrypy.HTTPError(400, e.message) - except MissingParameter, e: - raise cherrypy.HTTPError(400, e.message) - except NotFoundError, e: - raise cherrypy.HTTPError(404, e.message) - except OperationFailed, e: - raise cherrypy.HTTPError(500, e.message) - except WokException, e: - raise cherrypy.HTTPError(500, e.message) - class AsyncCollection(Collection): """ -- 1.9.1

On 06/08/2016 04:27 PM, Lucio Correia wrote:
Signed-off-by: Lucio Correia <luciojhc@linux.vnet.ibm.com> --- src/wok/control/base.py | 158 ++++++++++++++++++++++++++++++------------------ 1 file changed, 98 insertions(+), 60 deletions(-)
diff --git a/src/wok/control/base.py b/src/wok/control/base.py index 9b34c55..f1cce11 100644 --- a/src/wok/control/base.py +++ b/src/wok/control/base.py @@ -119,6 +119,10 @@ class Resource(object): def _generate_action_handler_base(self, action_name, render_fn, destructive=False, action_args=None): def wrapper(*args, **kwargs): + # status must be always set in order to request be logged. + # use 500 as fallback for "exception not handled" cases. + status = 500 + method = 'POST' validate_method((method), self.role_key, self.admin_methods) try: @@ -138,7 +142,36 @@ class Resource(object):
action_fn = getattr(self.model, model_fn(self, action_name)) action_result = action_fn(*model_args) + status = 200
+ if destructive is False or \ + ('persistent' in self.info.keys() and + self.info['persistent'] is True): + result = render_fn(self, action_result) + status = cherrypy.response.status + return result
+ except MissingParameter, e: + status = 400 + raise cherrypy.HTTPError(status, e.message) + except InvalidParameter, e: + status = 400 + raise cherrypy.HTTPError(status, e.message) + except InvalidOperation, e: + status = 400 + raise cherrypy.HTTPError(status, e.message) + except UnauthorizedError, e: + status = 403 + raise cherrypy.HTTPError(status, e.message) + except NotFoundError, e: + status = 404 + raise cherrypy.HTTPError(status, e.message) + except OperationFailed, e: + status = 500 + raise cherrypy.HTTPError(status, e.message) + except WokException, e: + status = 500 + raise cherrypy.HTTPError(status, e.message) +
Is not possible to do a map exception: code to avoid the multiple except statements? Something like: except Exception, e: status = map.get(type(e), 500) raise cherrypy.HTTPError(status, e.message) Then you can reuse that map when needed and we guarantee all exceptions will have the same error code.
finally: # log request code = self.getRequestMessage(method, action_name) reqParams = utf8_dict(self.log_args, request) @@ -147,29 +180,11 @@ class Resource(object): msg, app=get_plugin_from_request(), req=method, + status=status, user=cherrypy.session.get(USER_NAME, 'N/A'), ip=cherrypy.request.remote.ip ).log()
- if destructive is False or \ - ('persistent' in self.info.keys() and - self.info['persistent'] is True): - return render_fn(self, action_result) - except MissingParameter, e: - raise cherrypy.HTTPError(400, e.message) - except InvalidParameter, e: - raise cherrypy.HTTPError(400, e.message) - except InvalidOperation, e: - raise cherrypy.HTTPError(400, e.message) - except UnauthorizedError, e: - raise cherrypy.HTTPError(403, e.message) - except NotFoundError, e: - raise cherrypy.HTTPError(404, e.message) - except OperationFailed, e: - raise cherrypy.HTTPError(500, e.message) - except WokException, e: - raise cherrypy.HTTPError(500, e.message) - wrapper.__name__ = action_name wrapper.exposed = True return wrapper @@ -190,13 +205,13 @@ class Resource(object): e = InvalidOperation('WOKAPI0002E', {'resource': get_class_name(self)}) raise cherrypy.HTTPError(405, e.message) - except OperationFailed, e: - raise cherrypy.HTTPError(500, e.message) - except InvalidOperation, e: - raise cherrypy.HTTPError(400, e.message)
@cherrypy.expose def index(self, *args, **kargs): + # status must be always set in order to request be logged. + # use 500 as fallback for "exception not handled" cases. + status = 500 + method = validate_method(('GET', 'DELETE', 'PUT'), self.role_key, self.admin_methods)
@@ -208,30 +223,42 @@ class Resource(object): result = {'GET': self.get, 'DELETE': self.delete, 'PUT': self.update}[method](*args, **kargs) + + status = cherrypy.response.status except InvalidOperation, e: - raise cherrypy.HTTPError(400, e.message) + status = 400 + raise cherrypy.HTTPError(status, e.message) except InvalidParameter, e: - raise cherrypy.HTTPError(400, e.message) + status = 400 + raise cherrypy.HTTPError(status, e.message) except UnauthorizedError, e: - raise cherrypy.HTTPError(403, e.message) + status = 403 + raise cherrypy.HTTPError(status, e.message) except NotFoundError, e: - raise cherrypy.HTTPError(404, e.message) + status = 404 + raise cherrypy.HTTPError(status, e.message) except OperationFailed, e: - raise cherrypy.HTTPError(500, e.message) + status = 500 + raise cherrypy.HTTPError(status, e.message) except WokException, e: - raise cherrypy.HTTPError(500, e.message) - - # log request - if method not in LOG_DISABLED_METHODS: - code = self.getRequestMessage(method) - msg = WokMessage(code, self.log_args).get_text(prepend_code=False) - RequestRecord( - msg, - app=get_plugin_from_request(), - req=method, - user=cherrypy.session.get(USER_NAME, 'N/A'), - ip=cherrypy.request.remote.ip - ).log() + status = 500 + raise cherrypy.HTTPError(status, e.message) + except cherrypy.HTTPError, e: + status = e.status + raise + finally: + # log request + if method not in LOG_DISABLED_METHODS: + code = self.getRequestMessage(method) + msg = WokMessage(code, self.log_args) + RequestRecord( + msg.get_text(prepend_code=False), + app=get_plugin_from_request(), + req=method, + status=status, + user=cherrypy.session.get(USER_NAME, 'N/A'), + ip=cherrypy.request.remote.ip + ).log()
return result
@@ -311,10 +338,6 @@ class AsyncResource(Resource): e = InvalidOperation('WOKAPI0002E', {'resource': get_class_name(self)}) raise cherrypy.HTTPError(405, e.message) - except OperationFailed, e: - raise cherrypy.HTTPError(500, e.message) - except InvalidOperation, e: - raise cherrypy.HTTPError(400, e.message)
cherrypy.response.status = 202 return wok.template.render("Task", task) @@ -437,6 +460,10 @@ class Collection(object):
@cherrypy.expose def index(self, *args, **kwargs): + # status must be always set in order to request be logged. + # use 500 as fallback for "exception not handled" cases. + status = 500 + params = {} method = validate_method(('GET', 'POST'), self.role_key, self.admin_methods) @@ -449,7 +476,31 @@ class Collection(object): elif method == 'POST': params = parse_request() result = self.create(params, *args) - + status = cherrypy.response.status + return result + except InvalidOperation, e: + status = 400 + raise cherrypy.HTTPError(status, e.message) + except InvalidParameter, e: + status = 400 + raise cherrypy.HTTPError(status, e.message) + except MissingParameter, e: + status = 400 + raise cherrypy.HTTPError(status, e.message) + except NotFoundError, e: + status = 404 + raise cherrypy.HTTPError(status, e.message) + except OperationFailed, e: + status = 500 + raise cherrypy.HTTPError(status, e.message) + except WokException, e: + status = 500 + raise cherrypy.HTTPError(status, e.message) + except cherrypy.HTTPError, e: + status = e.status + raise + finally: + if method not in LOG_DISABLED_METHODS: # log request code = self.getRequestMessage(method) reqParams = utf8_dict(self.log_args, params) @@ -458,24 +509,11 @@ class Collection(object): msg, app=get_plugin_from_request(), req=method, + status=status, user=cherrypy.session.get(USER_NAME, 'N/A'), ip=cherrypy.request.remote.ip ).log()
- return result - except InvalidOperation, e: - raise cherrypy.HTTPError(400, e.message) - except InvalidParameter, e: - raise cherrypy.HTTPError(400, e.message) - except MissingParameter, e: - raise cherrypy.HTTPError(400, e.message) - except NotFoundError, e: - raise cherrypy.HTTPError(404, e.message) - except OperationFailed, e: - raise cherrypy.HTTPError(500, e.message) - except WokException, e: - raise cherrypy.HTTPError(500, e.message) -
class AsyncCollection(Collection): """

Also, remove download field from filtering options. Signed-off-by: Lucio Correia <luciojhc@linux.vnet.ibm.com> --- src/wok/reqlogger.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/wok/reqlogger.py b/src/wok/reqlogger.py index 6f32c44..8fadbcf 100644 --- a/src/wok/reqlogger.py +++ b/src/wok/reqlogger.py @@ -34,16 +34,17 @@ from wok.utils import ascii_dict, remove_old_files # Log search setup -FILTER_FIELDS = ['app', 'date', 'download', 'ip', 'req', 'user', 'time'] +FILTER_FIELDS = ['app', 'date', 'ip', 'req', 'status' 'user', 'time'] LOG_DOWNLOAD_URI = "/data/logs/%s" LOG_DOWNLOAD_TIMEOUT = 6 -LOG_FORMAT = "[%(date)s %(time)s %(zone)s] %(req)-6s %(app)-11s %(ip)-15s " \ - "%(user)s: %(message)s\n" +LOG_FORMAT = "[%(date)s %(time)s %(zone)s] %(req)-6s %(status)s %(app)-11s " \ + "%(ip)-15s %(user)s: %(message)s\n" RECORD_TEMPLATE_DICT = { 'date': '', 'time': '', 'zone': '', 'req': '', + 'status': '', 'app': '', 'ip': '', 'user': '', @@ -157,6 +158,7 @@ class RequestParser(object): uri = None results = [] records = self.getRecords() + download = filter_params.pop('download', False) # fail for unrecognized filter options for key in filter_params.keys(): @@ -164,8 +166,6 @@ class RequestParser(object): filters = ", ".join(FILTER_FIELDS) raise InvalidParameter("WOKLOG0001E", {"filters": filters}) - download = filter_params.pop('download', False) - # filter records according to parameters for record in records: if all(key in record and record[key] == val -- 1.9.1

Update the API.md to reflect that change. Also add a test to verify the API is working. On 06/08/2016 04:27 PM, Lucio Correia wrote:
Also, remove download field from filtering options.
Signed-off-by: Lucio Correia <luciojhc@linux.vnet.ibm.com> --- src/wok/reqlogger.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/wok/reqlogger.py b/src/wok/reqlogger.py index 6f32c44..8fadbcf 100644 --- a/src/wok/reqlogger.py +++ b/src/wok/reqlogger.py @@ -34,16 +34,17 @@ from wok.utils import ascii_dict, remove_old_files
# Log search setup -FILTER_FIELDS = ['app', 'date', 'download', 'ip', 'req', 'user', 'time'] +FILTER_FIELDS = ['app', 'date', 'ip', 'req', 'status' 'user', 'time'] LOG_DOWNLOAD_URI = "/data/logs/%s" LOG_DOWNLOAD_TIMEOUT = 6 -LOG_FORMAT = "[%(date)s %(time)s %(zone)s] %(req)-6s %(app)-11s %(ip)-15s " \ - "%(user)s: %(message)s\n" +LOG_FORMAT = "[%(date)s %(time)s %(zone)s] %(req)-6s %(status)s %(app)-11s " \ + "%(ip)-15s %(user)s: %(message)s\n" RECORD_TEMPLATE_DICT = { 'date': '', 'time': '', 'zone': '', 'req': '', + 'status': '', 'app': '', 'ip': '', 'user': '', @@ -157,6 +158,7 @@ class RequestParser(object): uri = None results = [] records = self.getRecords() + download = filter_params.pop('download', False)
# fail for unrecognized filter options for key in filter_params.keys(): @@ -164,8 +166,6 @@ class RequestParser(object): filters = ", ".join(FILTER_FIELDS) raise InvalidParameter("WOKLOG0001E", {"filters": filters})
- download = filter_params.pop('download', False) - # filter records according to parameters for record in records: if all(key in record and record[key] == val

On 08-06-2016 16:27, Lucio Correia wrote:
This patch requires "[Kimchi] Do not break the logging of failed requests". Otherwise, some tests will be broken.
Also, this patch depends on [Wok 0/2] User Request Log improvements -- Lucio Correia Software Engineer IBM LTC Brazil
participants (2)
-
Aline Manera
-
Lucio Correia