From 8b7b6d9c81be6740a8ffbad972d7e0fc089478aa Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rajmund=20Hru=C5=A1ka?= <rajmund.hruska@cesnet.cz>
Date: Wed, 6 Apr 2022 15:48:10 +0200
Subject: [PATCH] Feature: Add the API for retrieving the overall reports
 dashboard.

There is an endpoint /api/reports/dashboard that provides JSON document containing overall reports statistics. The data is the same as with the /reports/dashboard view. (Redmine issue: #7578)
---
 doc/sphinx/_doclib/hawat.rst                |   4 +
 doc/sphinx/_doclib/hawat_plugin_reports.rst | 166 +++++++++++++++++++-
 lib/hawat/blueprints/reports/__init__.py    |  46 ++++--
 3 files changed, 199 insertions(+), 17 deletions(-)

diff --git a/doc/sphinx/_doclib/hawat.rst b/doc/sphinx/_doclib/hawat.rst
index ff19f8f5f..6f1ebc52e 100644
--- a/doc/sphinx/_doclib/hawat.rst
+++ b/doc/sphinx/_doclib/hawat.rst
@@ -135,3 +135,7 @@ After that you have two options for using that key:
 	* :ref:`section-hawat-plugin-events-webapi-show`
 	* :ref:`section-hawat-plugin-events-webapi-dashboard`
 	* :ref:`section-hawat-plugin-events-webapi-metadata`
+
+* :ref:`section-hawat-plugin-reports`
+
+	* :ref:`section-hawat-plugin-reports-webapi-dashboard`
diff --git a/doc/sphinx/_doclib/hawat_plugin_reports.rst b/doc/sphinx/_doclib/hawat_plugin_reports.rst
index 1db997ba4..14df1dacd 100644
--- a/doc/sphinx/_doclib/hawat_plugin_reports.rst
+++ b/doc/sphinx/_doclib/hawat_plugin_reports.rst
@@ -3,5 +3,167 @@
 reports
 ================================================================================
 
-.. automodule:: hawat.blueprints.reports
-    :noindex:
+This pluggable module provides access to periodical event reports.
+
+.. _section-hawat-plugin-reports-endpoints:
+
+Provided endpoints
+--------------------------------------------------------------------------------
+
+``/api/events/dashboard``
+    * *Reference:* :ref:`section-hawat-plugin-reports-webapi-dashboard`
+
+
+.. _section-hawat-plugin-reports-webapi:
+
+Web API
+--------------------------------------------------------------------------------
+
+For general information about web API please refer to section :ref:`section-hawat-webapi`.
+
+Following is a list of all currently available API endpoints. These endpoints
+provide results as JSON document instead of full HTML page.
+
+
+.. _section-hawat-plugin-reports-webapi-dashboard:
+
+API endpoint: **dashboard**
+````````````````````````````````````````````````````````````````````````````````
+
+**Relevant endpoint:**
+
+``/api/reports/dashboard``
+    * *Authentication:* login required
+    * *Authorization:* any role
+    * *Methods:* ``GET``, ``POST``
+
+The URL for web API interface is available as normal endpoint to the user of the web
+interface. This fact can be used to debug the queries interactively and then simply
+copy them to another application. One might for example start with filling in the
+search form in the ``/reports/dashboard`` endpoint. Once you are satisfied with the
+result, you can simply switch the base URL to the ``/api/reports/dashboard`` endpoint
+and you are all set.
+
+**Available query parameters**:
+
+Following parameters may be specified as standard HTTP query parameters:
+
+``dt_from``
+    * *Description:* Lower event detection time boundary
+    * *Datatype:* Datetime in the format ``YYYY-MM-DD HH:MM:SS``, for example ``2022-01-01 00:00:00``
+
+``dt_to``
+    * *Description:* Upper event detection time boundary
+    * *Datatype:* Datetime in the format ``YYYY-MM-DD HH:MM:SS``, for example ``2022-01-01 00:00:00``
+
+``groups``
+    * *Description:* List of required abuse groups owning the reports
+    * *Datatype:* ``list of strings``
+    * *Logical operation:* All given values are *ORed*
+
+
+**Response format**
+
+JSON document, that will be received as a response for the search, can contain
+following keys:
+
+``form_data``
+    * *Description:* This subkey is present in case search operation was triggered.
+      It contains a dictionary with all query parameters described above and their
+      appropriate processed values.
+    * *Datatype:* ``dictionary``
+
+``form_errors``
+    * *Description:* This subkey is present in case there were any errors in the
+      submitted search form and the search operation could not be triggered. So
+      in another words the presence of this subkey is an indication of search failure.
+      This subkey contains list of all form errors as pairs of strings: name of
+      the form field and error description. The error description is localized
+      according to the user`s preferences.
+    * *Datatype:* ``list of tuples of strings``
+    * *Example:* ``[["dt_from", "Not a valid datetime value"]]``
+
+``statistics``
+    * *Description:* This subkey is present in case search operation was triggered.
+      It contains the actual result of the search. Following subkeys can be found
+      in this dictionary:
+
+      * ``cnt_emails`` - Total number of report emails generated
+      * ``cnt_events`` - Total number of matched events
+      * ``cnt_events_all`` - Total number of reported events
+      * ``cnt_events_filtered`` - Total number of matched events that were filtered and thus not reported
+      * ``cnt_events_new`` - Total number of new events
+      * ``cnt_events_relapsed`` - Total number of relapsed events
+      * ``cnt_events_thresholded`` - Total number of thresholded events
+      * ``cnt_recurring`` - Total number of relapsed events
+      * ``cnt_reports`` - Total number of reports created
+      * ``cnt_reports_summary`` - Total number of summary reports created
+      * ``cnt_unique`` - Total number of unique events
+      * ``dt_from`` - Lower time boundary of the result dataset
+      * ``dt_to`` - Upper time boundary of the result dataset
+      * ``abuses`` - Dictionary of the abuse groups and the number of events
+      * ``analyzers`` - Dictionary of the analyzers and the number of events
+      * ``asns`` - Dictionary of the ASNs and the number of reported events
+      * ``categories`` - Dictionary of the categories and the number of reported events
+      * ``category_sets`` - Dictionary of the category sets and the number of reported events
+      * ``classes`` - Dictionary of the classes and the number of reported events
+      * ``countries`` - Dictionary of the countries and the number of reported events
+      * ``detectors`` - Dictionary of the detectors and the number of reported events
+      * ``detectorsws`` - Dictionary of the detectors and analyzers and the number of reported events
+      * ``emails`` - Dictionary of the emails and the number of reports
+      * ``ips`` - Dictionary of the IPs and the number of reported events
+      * ``severities`` - Dictionary of the severities and the number of reported events
+      * ``timeline_cfg`` - Pre-calculated optimized timeline configurations
+
+    * *Datatype:* ``list of dictionaries``
+
+``items_count``
+    * *Description:* This subkey is present in case search operation was triggered.
+      It contains the number of original datasets that have been processed to produce
+      final dataset ``statistics``.
+    * *Datatype:* ``integer``
+
+``query_params``
+    * *Description:* This subkey is always present in the response. It contains
+      processed search query parameters that the user actually explicitly specified.
+    * *Datatype:* ``dictionary``
+    * *Example:* ``{"dt_from": "", "submit": "Search"}``
+
+``time_marks``
+    * *Description:* This subkey is present in case search operation was triggered.
+      It contains list of time marks that can be used to calculate the duration of
+      various processing steps like queriing database, processing and rendering the
+      result.
+    * *Datatype:* ``list of lists``
+
+``searched``
+    * *Description:* This subkey is present in case search operation was triggered.
+      It is a simple indication of the successful search operation.
+    * *Datatype:* ``boolean`` always set to ``True``
+
+``search_widget_item_limit``
+    * *Description:* This subkey is always present in the response. It is intended
+      for internal purposes.
+    * *Datatype:* ``integer``
+
+``view_icon``
+    * *Description:* This subkey is always present in the response. It is intended
+      for internal purposes.
+    * *Datatype:* ``string``
+
+``view_title``
+    * *Description:* This subkey is always present in the response. It is intended
+      for internal purposes.
+    * *Datatype:* ``string``
+
+**Example usage with curl:**
+
+.. code-block:: shell
+
+    $ curl -X POST -d "api_key=your%AP1_k3y" "https://.../api/reports/dashboard?submit=Search"
+
+.. note::
+
+    Please be aware, that this endpoint provides raw statistical dataset. The :ref:`section-hawat-plugin-reports-features-dashboard`
+    endpoint performs some additional client-side processing with JavaScript to transform
+    datasets provided by this endpoint into format appropriate for chart and table rendering.
diff --git a/lib/hawat/blueprints/reports/__init__.py b/lib/hawat/blueprints/reports/__init__.py
index c9543a331..aff577013 100644
--- a/lib/hawat/blueprints/reports/__init__.py
+++ b/lib/hawat/blueprints/reports/__init__.py
@@ -368,16 +368,12 @@ class DataView(FileIdView):
         return 'security-report-{}.{}'.format(fileid, fileext)
 
 
-class DashboardView(HTMLMixin, SQLAlchemyMixin, BaseSearchView):  # pylint: disable=locally-disabled,too-many-ancestors
+class AbstractDashboardView(SQLAlchemyMixin, BaseSearchView):  # pylint: disable=locally-disabled,too-many-ancestors
     """
-    View responsible for presenting reporting dashboard.
+    Base class responsible for presenting reporting dashboard.
     """
     authentication = True
 
-    @classmethod
-    def get_view_name(cls):
-        return 'dashboard'
-
     @classmethod
     def get_view_icon(cls):
         return 'module-{}'.format(cls.module_name)
@@ -456,6 +452,26 @@ class DashboardView(HTMLMixin, SQLAlchemyMixin, BaseSearchView):  # pylint: disa
         )
 
 
+class DashboardView(HTMLMixin, AbstractDashboardView):  # pylint: disable=locally-disabled,too-many-ancestors
+    """
+    View responsible for presenting reporting dashboard in the form of HTML page.
+    """
+
+    @classmethod
+    def get_view_name(cls):
+        return 'dashboard'
+
+
+class APIDashboardView(AJAXMixin, AbstractDashboardView):  # pylint: disable=locally-disabled,too-many-ancestors
+    """
+    View responsible for presenting reporting dashboard in the form of JSON document.
+    """
+
+    @classmethod
+    def get_view_name(cls):
+        return 'apidashboard'
+
+
 class DeleteView(HTMLMixin, SQLAlchemyMixin, ItemDeleteView):  # pylint: disable=locally-disabled,too-many-ancestors
     """
     View for deleting existing user accounts.
@@ -628,16 +644,16 @@ def get_blueprint():
     hbp = ReportsBlueprint(
         BLUEPRINT_NAME,
         __name__,
-        template_folder = 'templates',
-        url_prefix = '/{}'.format(BLUEPRINT_NAME)
+        template_folder = 'templates'
     )
 
-    hbp.register_view_class(SearchView,     '/search')
-    hbp.register_view_class(ShowView,       '/<int:item_id>/show')
-    hbp.register_view_class(UnauthShowView, '/<item_id>/unauth')
-    hbp.register_view_class(DataView,       '/data/<fileid>/<filetype>')
-    hbp.register_view_class(DashboardView,  '/dashboard')
-    hbp.register_view_class(DeleteView,     '/<int:item_id>/delete')
-    hbp.register_view_class(FeedbackView,   '/<item_id>/feedback')
+    hbp.register_view_class(SearchView,       '/{}/search'.format(BLUEPRINT_NAME))
+    hbp.register_view_class(ShowView,         '/{}/<int:item_id>/show'.format(BLUEPRINT_NAME))
+    hbp.register_view_class(UnauthShowView,   '/{}/<item_id>/unauth'.format(BLUEPRINT_NAME))
+    hbp.register_view_class(DataView,         '/{}/data/<fileid>/<filetype>'.format(BLUEPRINT_NAME))
+    hbp.register_view_class(DashboardView,    '/{}/dashboard'.format(BLUEPRINT_NAME))
+    hbp.register_view_class(DeleteView,       '/{}/<int:item_id>/delete'.format(BLUEPRINT_NAME))
+    hbp.register_view_class(FeedbackView,     '/{}/<item_id>/feedback'.format(BLUEPRINT_NAME))
+    hbp.register_view_class(APIDashboardView, '/api/{}/dashboard'.format(BLUEPRINT_NAME))
 
     return hbp
-- 
GitLab