Repository: privacyidea/privacyidea

Issue:
do not update certain data
For performance reasons we could avoid updating certain data like the token info ``count_auth`` and ``count_auth_success``.
We could also drop the signing of the audit log, since this is an additional DB UPDATE request.


Discussion:

Resolving patch:
diff --git a/privacyidea/lib/auditmodules/sqlaudit.py b/privacyidea/lib/auditmodules/sqlaudit.py
--- a/privacyidea/lib/auditmodules/sqlaudit.py
+++ b/privacyidea/lib/auditmodules/sqlaudit.py
@@ -76,17 +76,32 @@ class Audit(AuditBase):
     """
     This is the SQLAudit module, which writes the audit entries
     to an SQL database table.
-    It requires the configuration parameters.
-    PI_AUDIT_SQL_URI
+    It requires the configuration parameters in pi.cfg:
+    * PI_AUDIT_KEY_PUBLIC
+    * PI_AUDIT_KEY_PRIVATE
+
+    If you want to host the SQL Audit database in another DB than the
+    token DB, you can use:
+    * PI_AUDIT_SQL_URI
+
+    It also takes the optional parameters:
+    * PI_AUDIT_POOL_SIZE
+    * PI_AUDIT_POOL_RECYCLE
+    * PI_AUDIT_SQL_TRUNCATE
+    * PI_AUDIT_NO_SIGN
+
+    You can use PI_AUDIT_NO_SIGN = True to avoid signing of the audit log.
     """
     
     def __init__(self, config=None):
         self.name = "sqlaudit"
         self.config = config or {}
         self.audit_data = {}
+        self.sign_data = not self.config.get("PI_AUDIT_NO_SIGN")
         self.sign_object = None
-        self.read_keys(self.config.get("PI_AUDIT_KEY_PUBLIC"),
-                       self.config.get("PI_AUDIT_KEY_PRIVATE"))
+        if self.sign_data:
+            self.read_keys(self.config.get("PI_AUDIT_KEY_PUBLIC"),
+                           self.config.get("PI_AUDIT_KEY_PRIVATE"))
 
         # We can use "sqlaudit" as the key because the SQLAudit connection
         # string is fixed for a running privacyIDEA instance.
@@ -248,7 +263,7 @@ def finalize_log(self):
             self.session.add(le)
             self.session.commit()
             # Add the signature
-            if self.sign_object:
+            if self.sign_data and self.sign_object:
                 s = self._log_to_string(le)
                 sign = self.sign_object.sign(s)
                 le.signature = sign
@@ -516,8 +531,11 @@ def clear(self):
         self.session.commit()
     
     def audit_entry_to_dict(self, audit_entry):
-        sig = self.sign_object.verify(self._log_to_string(audit_entry),
-                                      audit_entry.signature)
+        sig = None
+        if self.sign_data:
+            sig = self.sign_object.verify(self._log_to_string(audit_entry),
+                                          audit_entry.signature)
+
         is_not_missing = self._check_missing(int(audit_entry.id))
         # is_not_missing = True
         audit_dict = {'number': audit_entry.id,
diff --git a/privacyidea/lib/token.py b/privacyidea/lib/token.py
--- a/privacyidea/lib/token.py
+++ b/privacyidea/lib/token.py
@@ -72,7 +72,7 @@
 from privacyidea.lib.decorators import (check_user_or_serial,
                                         check_copy_serials)
 from privacyidea.lib.tokenclass import TokenClass
-from privacyidea.lib.utils import generate_password
+from privacyidea.lib.utils import generate_password, is_true
 from privacyidea.lib.log import log_with
 from privacyidea.models import (Token, Realm, TokenRealm, Challenge,
                                 MachineToken, TokenInfo)
@@ -1993,6 +1993,7 @@ def check_token_list(tokenobject_list, passw, user=None, options=None):
     """
     res = False
     reply_dict = {}
+    increase_auth_counters = not is_true(get_from_config(key="no_auth_counter"))
 
     # add the user to the options, so that every token, that get passed the
     # options can see the user
@@ -2084,7 +2085,8 @@ def check_token_list(tokenobject_list, passw, user=None, options=None):
         message_list = ["matching {0:d} tokens".format(len(valid_token_list))]
         # write serial numbers or something to audit log
         for token_obj in valid_token_list:
-            token_obj.inc_count_auth_success()
+            if increase_auth_counters:
+                token_obj.inc_count_auth_success()
             # Check if the max auth is succeeded
             if token_obj.check_all(message_list):
                 # The token is active and the auth counters are ok.
@@ -2153,7 +2155,8 @@ def check_token_list(tokenobject_list, passw, user=None, options=None):
                 if tokenobject.is_active():
                     # OTP matches
                     res = True
-                    tokenobject.inc_count_auth_success()
+                    if increase_auth_counters:
+                        tokenobject.inc_count_auth_success()
                     reply_dict["message"] = "Found matching challenge"
                     tokenobject.challenge_janitor()
                     # clean up all other challenges from other tokens. I.e.
@@ -2228,8 +2231,9 @@ def check_token_list(tokenobject_list, passw, user=None, options=None):
                 _r, pin, otp = token.split_pin_pass(passw)
                 if token.is_previous_otp(otp):
                     reply_dict["message"] += ". previous otp used again"
-            for token_obj in pin_matching_token_list:
-                token_obj.inc_count_auth()
+            if increase_auth_counters:
+                for token_obj in pin_matching_token_list:
+                    token_obj.inc_count_auth()
             # write the serial numbers to the audit log
             if len(pin_matching_token_list) == 1:
                 reply_dict["serial"] = pin_matching_token_list[0].token.serial
@@ -2244,7 +2248,8 @@ def check_token_list(tokenobject_list, passw, user=None, options=None):
         if get_inc_fail_count_on_false_pin():
             for tokenobject in invalid_token_list:
                 tokenobject.inc_failcount()
-                tokenobject.inc_count_auth()
+                if increase_auth_counters:
+                    tokenobject.inc_count_auth()
 
     return res, reply_dict
 


Test patch:
diff --git a/tests/test_api_validate.py b/tests/test_api_validate.py
--- a/tests/test_api_validate.py
+++ b/tests/test_api_validate.py
@@ -2227,6 +2227,38 @@ def test_01_check_invalid_input(self):
             error_msg = result.get("error").get("message")
             self.assertEqual("ERR905: Invalid serial number.", error_msg)
 
+    def test_31_count_auth(self):
+
+        serial = "authcount001"
+        tok = init_token({"serial": serial,
+                          "type": "spass",
+                          "pin": "spass"})
+        with self.app.test_request_context('/validate/check',
+                                           method='POST',
+                                           data={"serial": serial,
+                                                 "pass": "spass"}):
+            res = self.app.full_dispatch_request()
+            self.assertTrue(res.status_code == 200, res)
+            result = json.loads(res.data).get("result")
+            self.assertTrue(result.get("value"))
+
+        self.assertEqual(int(tok.get_tokeninfo("count_auth")), 1)
+
+        set_privacyidea_config("no_auth_counter", "True")
+        # Now an authentication does not increase the counter!
+        with self.app.test_request_context('/validate/check',
+                                           method='POST',
+                                           data={"serial": serial,
+                                                 "pass": "spass"}):
+            res = self.app.full_dispatch_request()
+            self.assertTrue(res.status_code == 200, res)
+            result = json.loads(res.data).get("result")
+            self.assertTrue(result.get("value"))
+
+        self.assertEqual(int(tok.get_tokeninfo("count_auth")), 1)
+        remove_token(serial)
+        delete_privacyidea_config("no_auth_counter")
+
 
 class AChallengeResponse(MyTestCase):