Repository: privacyidea/privacyidea

Issue:
Configurable db session commit in the script handler
#2294 fixed #2293 statically by forcing a database session commit right before the script handler calls the script.

Since the session commit causes a database access which is not necessary in many use cases, we should better make this configurable via a checkbox in the script handler. 

Usage: It should be checked whenever the called script changes data which should be immediately available to privacyIDEA (within the same request).


Discussion:

Resolving patch:
diff --git a/privacyidea/lib/eventhandler/scripthandler.py b/privacyidea/lib/eventhandler/scripthandler.py
--- a/privacyidea/lib/eventhandler/scripthandler.py
+++ b/privacyidea/lib/eventhandler/scripthandler.py
@@ -103,6 +103,13 @@ def actions(cls):
                     "visibleValue": SCRIPT_WAIT,
                     "description": _("On script error raise exception in HTTP request.")
                 },
+                "sync_to_database": {
+                    "type": "bool",
+                    "description": _("Finish current transaction before running "
+                                     "the script. This is useful if changes to "
+                                     "the database should be made available to "
+                                     "the script or the running request.")
+                },
                 "serial": {
                     "type": "bool",
                     "description": _("Add '--serial <serial number>' as script "
@@ -190,7 +197,10 @@ def do(self, action, options=None):
         rcode = 0
         try:
             log.info("Starting script {script!r}.".format(script=script_name))
-            db.session.commit()
+            if is_true(handler_options.get('sync_to_database', False)):
+                log.debug('Committing current transaction for script '
+                          '{0!s}'.format(script_name))
+                db.session.commit()
             p = subprocess.Popen(proc_args, cwd=self.script_directory, universal_newlines=True)
             if handler_options.get("background") == SCRIPT_WAIT:
                 rcode = p.wait()


Test patch:
diff --git a/tests/test_lib_events.py b/tests/test_lib_events.py
--- a/tests/test_lib_events.py
+++ b/tests/test_lib_events.py
@@ -9,6 +9,7 @@
 
 import responses
 import os
+import mock
 
 from privacyidea.lib.eventhandler.usernotification import UserNotificationEventHandler
 from .base import MyTestCase, FakeFlaskG, FakeAudit
@@ -770,6 +771,57 @@ def test_02_failscript(self):
         t_handler = ScriptEventHandler(script_directory=d)
         self.assertRaises(Exception, t_handler.do, script_name, options=options)
 
+    def test_03_sync_to_db(self):
+        g = FakeFlaskG()
+        g.logged_in_user = {"username": "admin",
+                            "role": "admin",
+                            "realm": ""}
+
+        builder = EnvironBuilder(method='POST',
+                                 data={'serial': "SPASS01"},
+                                 headers={})
+
+        req = Request(builder.get_environ())
+        req.all_data = {"serial": "SPASS01", "type": "spass"}
+        req.User = User()
+        resp = Response()
+        resp.data = """{"result": {"value": true}}"""
+
+        options = {"g": g,
+                   "request": req,
+                   "response": resp,
+                   "handler_def": {
+                       "options": {
+                           "user": "1",
+                           "realm": "1",
+                           "serial": "1",
+                           "logged_in_user": "1",
+                           "logged_in_role": "1"}
+                   }
+                   }
+
+        script_name = "ls.sh"
+        d = os.getcwd()
+        d = "{0!s}/tests/testdata/scripts/".format(d)
+        t_handler = ScriptEventHandler(script_directory=d)
+        # first check that the db session is not synced by default
+        with mock.patch('privacyidea.lib.eventhandler.scripthandler.db') as mdb:
+            res = t_handler.do(script_name, options=options)
+            mdb.session.commit.assert_not_called()
+        self.assertTrue(res)
+        # now set the parameter to sync the db session before running the script
+        options['handler_def']['options']['sync_to_database'] = "1"
+        with mock.patch('privacyidea.lib.eventhandler.scripthandler.db') as mdb:
+            res = t_handler.do(script_name, options=options)
+            mdb.session.commit.assert_called_with()
+        self.assertTrue(res)
+        # and now with the parameter explicitly disabled
+        options['handler_def']['options']['sync_to_database'] = "0"
+        with mock.patch('privacyidea.lib.eventhandler.scripthandler.db') as mdb:
+            res = t_handler.do(script_name, options=options)
+            mdb.session.commit.assert_not_called()
+        self.assertTrue(res)
+
 
 class FederationEventTestCase(MyTestCase):