Repository: privacyidea/privacyidea

Issue:
Event condition failcounter
In the event conditions we have count_auth and count_auth_success.
And we have "tokenlocked", which means that the failcounter reached the maximum.

Although you can handle all scenarios with these settings, it might be easier for configuration if we can also check for the failcount directly with "=", ">", "<".




Discussion:

Resolving patch:
diff --git a/privacyidea/lib/eventhandler/base.py b/privacyidea/lib/eventhandler/base.py
--- a/privacyidea/lib/eventhandler/base.py
+++ b/privacyidea/lib/eventhandler/base.py
@@ -64,6 +64,7 @@ class CONDITION(object):
     COUNT_AUTH = "count_auth"
     COUNT_AUTH_SUCCESS = "count_auth_success"
     COUNT_AUTH_FAIL = "count_auth_fail"
+    FAILCOUNTER = 'failcounter'
     TOKENINFO = "tokeninfo"
     DETAIL_ERROR_MESSAGE = "detail_error_message"
     DETAIL_MESSAGE = "detail_message"
@@ -237,6 +238,13 @@ def conditions(cls):
                           "field 'count_auth' and 'count_auth_success is "
                           "bigger than 100, less than 99 or exactly 100.")
             },
+            CONDITION.FAILCOUNTER: {
+                "type": "str",
+                "desc": _("This can be '>9', '<9', or '=10', to trigger "
+                          "the action, if the failcounter of a token matches this value. "
+                          "Note that the failcounter stops increasing, if the max_failcount is "
+                          "reached.")
+            },
             CONDITION.TOKENINFO: {
                 "type": "str",
                 "desc": _("This condition can check any arbitrary tokeninfo "
@@ -514,6 +522,12 @@ def check_condition(self, options):
                 if not compare_condition(cond, c_fail):
                     return False
 
+            if CONDITION.FAILCOUNTER in conditions:
+                failcount = token_obj.get_failcount()
+                cond = conditions.get(CONDITION.FAILCOUNTER)
+                if not compare_condition(cond, failcount):
+                    return False
+
             if CONDITION.TOKENINFO in conditions:
                 cond = conditions.get(CONDITION.TOKENINFO)
                 # replace {now} in condition


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
@@ -272,6 +272,36 @@ def test_03_check_auth_count_conditions(self):
              }
         )
         self.assertFalse(r)
+
+        # check for failcounter
+        tok.set_failcount(8)
+        r = uhandler.check_condition(
+            {"g": {},
+             "handler_def": {"conditions": {CONDITION.FAILCOUNTER: "<9"}},
+             "request": req,
+             "response": resp
+             }
+        )
+        self.assertTrue(r)
+
+        r = uhandler.check_condition(
+            {"g": {},
+             "handler_def": {"conditions": {CONDITION.FAILCOUNTER: ">9"}},
+             "request": req,
+             "response": resp
+             }
+        )
+        self.assertFalse(r)
+
+        r = uhandler.check_condition(
+            {"g": {},
+             "handler_def": {"conditions": {CONDITION.FAILCOUNTER: "=8"}},
+             "request": req,
+             "response": resp
+             }
+        )
+        self.assertTrue(r)
+
         remove_token(serial)
 
     def test_04_tokeninfo_condition(self):