Repository: privacyidea/privacyidea

Issue:
Add rollout_state to event handlers
**Is your feature request related to a problem? Please describe.**

When enrolling a 2step HOTP or TOTP token, we would like to define different types of event handlers. And even handler should be triggered in the first step, but not in the second step.

**Describe the solution you'd like**

The conditions of the event handler should be able to be filtered for the ``rollout_state`` of a token.

**Describe alternatives you've considered**

As an alternative we would have to define a script.




Discussion:

Resolving patch:
diff --git a/doc/conf.py b/doc/conf.py
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -17,7 +17,7 @@
 # built documents.
 #
 # The short X.Y version.
-version = '3.0.2'
+version = '3.1'
 # The full version, including alpha/beta/rc tags.
 #release = '2.16dev5'
 release = version
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
@@ -76,6 +76,7 @@ class CONDITION(object):
     REALM = "realm"
     RESOLVER = "resolver"
     CLIENT_IP = "client_ip"
+    ROLLOUT_STATE = "rollout_state"
 
 
 class BaseEventHandler(object):
@@ -127,6 +128,10 @@ def conditions(cls):
         realms = get_realms()
         resolvers = get_resolver_list()
         cond = {
+            CONDITION.ROLLOUT_STATE: {
+                "type": "str",
+                "desc": _("The rollout_state of the token has a certain value like 'clientwait' or 'enrolled'.")
+            },
             CONDITION.REALM: {
                 "type": "str",
                 "desc": _("The realm of the user, for which this event should apply."),
@@ -538,6 +543,11 @@ def check_condition(self, options):
                                 "condition: {0!s}".format(cond))
                     return False
 
+            if CONDITION.ROLLOUT_STATE in conditions:
+                cond = conditions.get(CONDITION.ROLLOUT_STATE)
+                if not cond == token_obj.token.rollout_state:
+                    return False
+
         return True
 
     def do(self, action, options=None):
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -6,7 +6,7 @@
 import sys
 
 #VERSION="2.1dev4"
-VERSION="3.1dev3"
+VERSION="3.1"
 
 # Taken from kennethreitz/requests/setup.py
 package_directory = os.path.realpath(os.path.dirname(__file__))


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
@@ -467,6 +467,54 @@ def test_06_check_for_client_ip(self):
         )
         self.assertFalse(r)
 
+    def test_07_check_rollout_state(self):
+        self.setUp_user_realms()
+        serial = "rs01"
+        user = User("cornelius", "realm1")
+        remove_token(user=user)
+        # Prepare the token
+        tok = init_token({"serial": serial,
+                          "type": "pw", "otppin": "test", "otpkey": "secret"},
+                         user=user)
+        tok.token.rollout_state = "fakestate"
+        tok.token.save()
+
+        uhandler = BaseEventHandler()
+        # Prepare a fake request
+        builder = EnvironBuilder(method='POST',
+                                 data={'user': "cornelius@realm1",
+                                       "pass": "wrongvalue"},
+                                 headers={})
+        env = builder.get_environ()
+        req = Request(env)
+        req.all_data = {"user": "cornelius@realm1",
+                        "pass": "wrongvalue"}
+        req.User = user
+        resp = Response()
+        resp.data = """{"result": {"value": false}}"""
+
+        # Check if the condition matches
+        r = uhandler.check_condition(
+            {"g": {},
+             "handler_def": {"conditions": {CONDITION.ROLLOUT_STATE: "fakestate"}},
+             "request": req,
+             "response": resp
+             }
+        )
+        self.assertTrue(r)
+
+        # Check if the condition does not match
+        r = uhandler.check_condition(
+            {"g": {},
+             "handler_def": {"conditions": {CONDITION.ROLLOUT_STATE: "otherstate"}},
+             "request": req,
+             "response": resp
+             }
+        )
+        self.assertFalse(r)
+
+        remove_token(serial)
+
 
 class CounterEventTestCase(MyTestCase):