Repository: privacyidea/privacyidea

Issue:
Add "set maxfail" to the TokenHandler.
Sometimes it might come in handy if an event handler could set the MaxFailCounter of a token, since there is no mass-way to do this.


Discussion:

Resolving patch:
diff --git a/privacyidea/lib/eventhandler/tokenhandler.py b/privacyidea/lib/eventhandler/tokenhandler.py
--- a/privacyidea/lib/eventhandler/tokenhandler.py
+++ b/privacyidea/lib/eventhandler/tokenhandler.py
@@ -47,7 +47,7 @@
                                    unassign_token, init_token, set_description,
                                    set_count_window, add_tokeninfo,
                                    set_failcounter, delete_tokeninfo,
-                                   get_one_token)
+                                   get_one_token, set_max_failcount)
 from privacyidea.lib.utils import (parse_date, is_true,
                                    parse_time_offset_from_now)
 from privacyidea.lib.tokenclass import DATE_FORMAT, AUTH_DATE_FORMAT
@@ -78,6 +78,7 @@ class ACTION_TYPE(object):
     SET_COUNTWINDOW = "set countwindow"
     SET_TOKENINFO = "set tokeninfo"
     SET_FAILCOUNTER = "set failcounter"
+    SET_MAXFAIL = "set max failcount"
     CHANGE_FAILCOUNTER = "change failcounter"
     DELETE_TOKENINFO = "delete tokeninfo"
     SET_RANDOM_PIN = "set random pin"
@@ -259,6 +260,15 @@ def actions(cls):
                                                     "Values of +n, -n with n being an integer are accepted.")
                                }
                        },
+                   ACTION_TYPE.SET_MAXFAIL:
+                       {
+                            "max failcount":
+                                {
+                                    "type": "str",
+                                    "required": True,
+                                    "description": _("Set the maximum failcounter of the token.")
+                                }
+                       },
                    ACTION_TYPE.SET_TOKENINFO:
                        {"key":
                            {
@@ -314,6 +324,7 @@ def do(self, action, options=None):
                               ACTION_TYPE.SET_COUNTWINDOW,
                               ACTION_TYPE.SET_TOKENINFO,
                               ACTION_TYPE.SET_FAILCOUNTER,
+                              ACTION_TYPE.SET_MAXFAIL,
                               ACTION_TYPE.CHANGE_FAILCOUNTER,
                               ACTION_TYPE.SET_RANDOM_PIN,
                               ACTION_TYPE.DELETE_TOKENINFO]:
@@ -398,6 +409,12 @@ def do(self, action, options=None):
                     except Exception as exx:
                         log.warning("Misconfiguration: Failed to set fail "
                                     "counter!")
+                elif action.lower() == ACTION_TYPE.SET_MAXFAIL:
+                    try:
+                        set_max_failcount(serial,
+                                          int(handler_options.get("max failcount")))
+                    except Exception as exx:
+                        log.warning("Misconfiguration: Failed to set max failcount!")
                 elif action.lower() == ACTION_TYPE.CHANGE_FAILCOUNTER:
                     try:
                         token_obj = get_one_token(serial=serial)


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
@@ -2308,3 +2308,53 @@ def test_11_set_random_pin(self):
         self.assertTrue(r)
 
         remove_token("SPASS01")
+
+    def test_12_set_max_failcount(self):
+        # setup realms
+        self.setUp_user_realms()
+
+        init_token({"serial": "SPASS01", "type": "spass"},
+                   User("cornelius", self.realm1))
+        t = get_tokens(serial="SPASS01")
+        uid = t[0].get_user_id()
+        self.assertEqual(uid, "1000")
+
+        g = FakeFlaskG()
+        audit_object = FakeAudit()
+        audit_object.audit_data["serial"] = "SPASS01"
+
+        g.logged_in_user = {"username": "admin",
+                            "role": "admin",
+                            "realm": ""}
+        g.audit_object = audit_object
+
+        builder = EnvironBuilder(method='POST',
+                                 data={'serial': "SPASS01"},
+                                 headers={})
+
+        env = builder.get_environ()
+        # Set the remote address so that we can filter for it
+        env["REMOTE_ADDR"] = "10.0.0.1"
+        g.client_ip = env["REMOTE_ADDR"]
+        req = Request(env)
+        req.all_data = {"serial": "SPASS01", "type": "spass"}
+        resp = Response()
+        resp.data = """{"result": {"value": true}}"""
+
+        # The count window of the token will be set to 123
+        options = {"g": g,
+                   "request": req,
+                   "response": resp,
+                   "handler_def": {"options": {"max failcount": "123"}
+                                   }
+                   }
+
+        t_handler = TokenEventHandler()
+        res = t_handler.do(ACTION_TYPE.SET_MAXFAIL, options=options)
+        self.assertTrue(res)
+        # Check if the token has the correct sync window
+        t = get_tokens(serial="SPASS01")
+        fc = t[0].get_max_failcount()
+        self.assertEqual(fc, 123)
+
+        remove_token("SPASS01")