From 922fb9746d569cc14e3567941debc004eac806be Mon Sep 17 00:00:00 2001 From: Jan Zerdik <zerdik@cesnet.cz> Date: Fri, 15 Mar 2019 16:48:00 +0100 Subject: [PATCH] Better work with datetime in json Datetime in json structured_data is now store in utc timezone, change to report timezone is done in templates. (Redmine issue: #4499) --- conf/requirements-latest.pip | 1 + conf/requirements.pip | 1 + conf/templates/reporter/_macros.common.txt.j2 | 2 +- lib/hawat/blueprints/reports/__init__.py | 21 +++++++++++++++---- .../reports/templates/reports/show.html | 4 ++-- lib/mentat/reports/base.py | 17 ++++++++++++++- lib/mentat/reports/event.py | 4 ++-- 7 files changed, 40 insertions(+), 10 deletions(-) diff --git a/conf/requirements-latest.pip b/conf/requirements-latest.pip index c4c2a48b6..e2dd8d284 100644 --- a/conf/requirements-latest.pip +++ b/conf/requirements-latest.pip @@ -27,3 +27,4 @@ pynspect ipranges typedcols idea-format +python-dateutil diff --git a/conf/requirements.pip b/conf/requirements.pip index eb863de79..de67d8d3a 100644 --- a/conf/requirements.pip +++ b/conf/requirements.pip @@ -27,3 +27,4 @@ pynspect==0.16 ipranges==0.1.10 typedcols==0.1.13 idea-format==0.1.11 +python-dateutil==2.8.0 diff --git a/conf/templates/reporter/_macros.common.txt.j2 b/conf/templates/reporter/_macros.common.txt.j2 index d6f22d957..83e3a5900 100644 --- a/conf/templates/reporter/_macros.common.txt.j2 +++ b/conf/templates/reporter/_macros.common.txt.j2 @@ -36,7 +36,7 @@ {{ '{:30s}'.format(_('Source')) }} {{ '{:25s}'.format(_('First event time')) }} {{ '{:25s}'.format(_('Last event time')) }} {{ '{:>7s}'.format(_('Count')) }} {{ _('Protocol') }} {{ '{}'.format('─' * 110) }} {%- for ip in section_data | dictsort %} - {{ '{:30s}'.format(ip[0]) }} {{ '{:<25s}'.format(ip[1]['first_time']) }} {{ '{:<25s}'.format(ip[1]['last_time']) }} {{ '{:>7s}'.format(format_decimal(ip[1]['count'])) }} {% if ip[1]['ports'] %}{{ ', '.join(ip[1]['ports']) }}{% else %}---{% endif %} + {{ '{:30s}'.format(ip[0]) }} {{ '{:<25s}'.format(format_rfctzdatetime(ip[1]['first_time'])) }} {{ '{:<25s}'.format(format_rfctzdatetime(ip[1]['last_time'])) }} {{ '{:>7s}'.format(format_decimal(ip[1]['count'])) }} {% if ip[1]['ports'] %}{{ ', '.join(ip[1]['ports']) }}{% else %}---{% endif %} {%- endfor %} {{ '{}'.format('─' * 110) }} {{ report_references.render_section_reference(section_name, logger) }} diff --git a/lib/hawat/blueprints/reports/__init__.py b/lib/hawat/blueprints/reports/__init__.py index 0dce075af..6ba2832e7 100644 --- a/lib/hawat/blueprints/reports/__init__.py +++ b/lib/hawat/blueprints/reports/__init__.py @@ -24,6 +24,7 @@ __credits__ = "Pavel Kácha <pavel.kacha@cesnet.cz>, Andrea Kropáčová <andrea import datetime import pytz import json +import dateutil.parser # # Flask related modules. @@ -32,7 +33,7 @@ import flask import flask_login import flask_principal import flask_mail -from flask_babel import gettext, lazy_gettext, force_locale +from flask_babel import gettext, lazy_gettext, force_locale, format_datetime from jinja2.loaders import ChoiceLoader, FileSystemLoader from flask.helpers import locked_cached_property import os.path @@ -55,6 +56,8 @@ from hawat.blueprints.reports.forms import EventReportSearchForm, ReportingDashb BLUEPRINT_NAME = 'reports' """Name of the blueprint as module global constant.""" +BABEL_RFC3339_FORMAT = "yyyy-MM-ddTHH:mm:ssZZZ" + def build_related_search_params(item): """ @@ -298,13 +301,23 @@ class ShowView(HTMLMixin, SQLAlchemyMixin, ItemShowView): ) return action_menu + @staticmethod + def format_datetime(val, tz): + """ + Static method that take string with isoformat datetime in utc and return + string with BABEL_RFC3339_FORMAT formated datetime in tz timezone + """ + return format_datetime(dateutil.parser.parse(val).replace(tzinfo=pytz.utc).astimezone(tz), BABEL_RFC3339_FORMAT, rebase=False) + def do_before_response(self, **kwargs): """*Implementation* of :py:func:`hawat.base.RenderableView.do_before_response`.""" if 'item' in self.response_context and self.response_context['item']: self.response_context.update( - statistics = self.response_context['item'].statistics, - template_vars = flask.current_app.mconfig[mentat.const.CKEY_CORE_REPORTER][mentat.const.CKEY_CORE_REPORTER_TEMPLATEVARS], - form = FeedbackForm() + statistics = self.response_context['item'].statistics, + template_vars = flask.current_app.mconfig[mentat.const.CKEY_CORE_REPORTER][mentat.const.CKEY_CORE_REPORTER_TEMPLATEVARS], + form = FeedbackForm(), + format_datetime = ShowView.format_datetime, + tz = pytz.timezone(self.response_context['item'].structured_data["timezone"]) ) diff --git a/lib/hawat/blueprints/reports/templates/reports/show.html b/lib/hawat/blueprints/reports/templates/reports/show.html index bdec1eccf..036bd5719 100644 --- a/lib/hawat/blueprints/reports/templates/reports/show.html +++ b/lib/hawat/blueprints/reports/templates/reports/show.html @@ -135,8 +135,8 @@ {%- set classes = section_name -%} {%- endif -%} <a href="{{ url_for('events.search', source_addrs=ip[0], classes=classes, st_from=item.dt_from, st_to=item.dt_to, severity=item.severity, submit='Search') }}">{% endif %}{{ ip[0] }}</td>{% if current_user.is_authenticated %}</a>{% endif %} - <td>{{ ip[1]['first_time'] }}</td> - <td>{{ ip[1]['last_time'] }}</td> + <td>{{ format_datetime(ip[1]['first_time'], tz) }}</td> + <td>{{ format_datetime(ip[1]['last_time'], tz) }}</td> <td style="text-align:right">{{ ip[1]['count'] }}</td> <td>{% if ip[1]['ports'] %}{{ ', '.join(ip[1]['ports']) }}{% else %}---{% endif %}</td> {%- if current_user.is_authenticated %} diff --git a/lib/mentat/reports/base.py b/lib/mentat/reports/base.py index f091fc6ce..58d0ee58b 100644 --- a/lib/mentat/reports/base.py +++ b/lib/mentat/reports/base.py @@ -26,6 +26,7 @@ import time import datetime import gettext import jinja2 +import dateutil.parser from babel.numbers import format_decimal from babel.dates import format_datetime, format_timedelta, get_timezone, UTC @@ -163,11 +164,23 @@ class BaseReporter: """ return format_decimal(val, locale = self.locale) + def get_datetime(self, val): + """ + Method tries parse datetime if provided with string, otherwise return val directly. + """ + if isinstance(val, str): + try: + return dateutil.parser.parse(val) + except: + pass + return val + def format_datetime(self, val): """ Simple wrapper around :py:func:babel.dates.format_datetime` function that takes care of figuring out the appropriate locale. """ + val = self.get_datetime(val) return format_datetime(val, locale = self.locale) def format_localdatetime(self, val): @@ -176,6 +189,7 @@ class BaseReporter: that takes care of figuring out the appropriate locale and prints the datetime in local timezone (local to the server). """ + val = self.get_datetime(val) epoch = time.mktime(val.timetuple()) offset = datetime.datetime.fromtimestamp(epoch) - datetime.datetime.utcfromtimestamp(epoch) return format_datetime(val + offset, locale = self.locale) @@ -186,6 +200,7 @@ class BaseReporter: that takes care of figuring out the appropriate locale and prints the datetime in configured timezone. """ + val = self.get_datetime(val) if self.timezone != 'UTC': return format_datetime(val, tzinfo = self.tzinfo, locale = self.locale) return format_datetime(val, locale = self.locale) @@ -196,7 +211,7 @@ class BaseReporter: that prints the datetime in configured timezone and enforced RFC 3339 format. """ - + val = self.get_datetime(val) if self.timezone != 'UTC': return format_datetime(val, BABEL_RFC3339_FORMAT, tzinfo = self.tzinfo, locale = self.locale) return format_datetime(val, BABEL_RFC3339_FORMAT, locale = self.locale) diff --git a/lib/mentat/reports/event.py b/lib/mentat/reports/event.py index d7f2e1227..564dc495d 100644 --- a/lib/mentat/reports/event.py +++ b/lib/mentat/reports/event.py @@ -803,8 +803,8 @@ class EventReporter(BaseReporter): ip_result["count"] += 1 for abuse_value in result.values(): for ip_value in abuse_value.values(): - ip_value["first_time"] = self.format_rfctzdatetime(ip_value["first_time"]) - ip_value["last_time"] = self.format_rfctzdatetime(ip_value["last_time"]) + ip_value["first_time"] = ip_value["first_time"].isoformat() + ip_value["last_time"] = ip_value["last_time"].isoformat() ip_value["ports"] = sorted(set(ip_value["ports"])) return result -- GitLab