Repository: privacyidea/privacyidea

Issue:
Tokenhandler: Additional parameter for token enrollment
The action "enroll" in the token handler allows to enroll certain token types.

I makes sense to also add additional parameters. Maybe the parameters can simply be added as dictionary.

E.g. Enrolling a dynamic SMS tokens requires the parameter ``dynamic_phone=1``.


Discussion:
We can leave this open for further, generic parameters.

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
@@ -1,6 +1,8 @@
 # -*- coding: utf-8 -*-
 #
-#  2018-34-14 Paul Lettich <paul.lettich@netknights.it>
+#  2018-06-11 Cornelius Kölbel <cornelius.koelbel@netknights.it>
+#             Add dynamic email
+#  2018-04-14 Paul Lettich <paul.lettich@netknights.it>
 #             Add "delete tokeninfo" action
 #  2017-07-18 Cornelius Kölbel <cornelius.koelbel@netknights.it>
 #             Allow setting time with timedelta
@@ -51,6 +53,7 @@
 import json
 import logging
 import datetime
+import yaml
 from dateutil.parser import parse as parse_date_string
 from dateutil.tz import tzlocal
 
@@ -147,6 +150,17 @@ def actions(cls):
                             "description": _("Dynamically read the mobile number "
                                              "from the user store.")
                         },
+                        "dynamic_email": {
+                            "type": "bool",
+                            "visibleIf": "tokentype",
+                            "visibleValue": "email",
+                            "description": _("Dynamically read the email address "
+                                             "from the user store.")
+                        },
+                        "additional_params": {
+                            "type": "str",
+                            "description": _("A dictionary of additional init parameters.")
+                        },
                         "motppin": {
                             "type": "str",
                             "visibleIf": "tokentype",
@@ -344,9 +358,12 @@ def do(self, action, options=None):
             if is_true(handler_options.get("user")):
                 user = self._get_tokenowner(request)
                 tokentype = handler_options.get("tokentype")
-                # Some tokentypes need additional parameters or otherwise
-                # will fail to enroll.
-                # TODO: Other tokentypes will require additional parameters
+                # Some tokentypes need additional parameters
+                if handler_options.get("additional_params"):
+                    add_params = yaml.safe_load(handler_options.get("additional_params"))
+                    if type(add_params) == dict:
+                        init_param.update(add_params)
+
                 if tokentype == "sms":
                     if handler_options.get("dynamic_phone"):
                         init_param["dynamic_phone"] = 1
@@ -357,10 +374,13 @@ def do(self, action, options=None):
                             log.warning("Enrolling SMS token. But the user "
                                         "{0!r} has no mobile number!".format(user))
                 elif tokentype == "email":
-                    init_param['email'] = user.info.get("email", "")
-                    if not init_param['email']:
-                        log.warning("Enrolling EMail token. But the user {0!s}"
-                                    "has no email address!".format(user))
+                    if handler_options.get("dynamic_email"):
+                        init_param["dynamic_email"] = 1
+                    else:
+                        init_param['email'] = user.info.get("email", "")
+                        if not init_param['email']:
+                            log.warning("Enrolling EMail token. But the user {0!s}"
+                                        "has no email address!".format(user))
                 elif tokentype == "motp":
                     init_param['motppin'] = handler_options.get("motppin")
 


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
@@ -1076,6 +1076,68 @@ def test_05_enroll(self):
         self.assertTrue(is_true(t.get_tokeninfo("dynamic_phone")))
         remove_token(t.token.serial)
 
+        # Enroll a dynamic email token
+        options = {"g": g,
+                   "request": req,
+                   "response": resp,
+                   "handler_def": {"options":
+                                       {"tokentype": "email",
+                                        "user": "1",
+                                        "dynamic_email": "1"}}
+                   }
+
+        t_handler = TokenEventHandler()
+        res = t_handler.do(ACTION_TYPE.INIT, options=options)
+        self.assertTrue(res)
+        # Check if the token was created and assigned
+        t = get_tokens(tokentype="email")[0]
+        self.assertTrue(t)
+        self.assertEqual(t.user, user_obj)
+        self.assertTrue(is_true(t.get_tokeninfo("dynamic_email")))
+        remove_token(t.token.serial)
+
+        # Enroll an email token to a user, who has no email address
+        user_obj_no_email = User("shadow", self.realm1)
+        req.User = user_obj_no_email
+        options = {"g": g,
+                   "request": req,
+                   "response": resp,
+                   "handler_def": {"options":
+                                       {"tokentype": "email",
+                                        "user": "1"}}
+                   }
+
+        t_handler = TokenEventHandler()
+        res = t_handler.do(ACTION_TYPE.INIT, options=options)
+        self.assertTrue(res)
+        # Check if the token was created and assigned
+        t = get_tokens(tokentype="email")[0]
+        self.assertTrue(t)
+        self.assertEqual(t.user, user_obj_no_email)
+        self.assertEqual(t.get_tokeninfo("email"), "")
+        remove_token(t.token.serial)
+
+        # Enroll a totp token with genkey
+        req.User = user_obj
+        options = {"g": g,
+                   "request": req,
+                   "response": resp,
+                   "handler_def": {"options":
+                                       {"tokentype": "totp",
+                                        "user": "1",
+                                        "additional_params": "{'totp.hashlib': 'sha256'}"}}
+                   }
+
+        t_handler = TokenEventHandler()
+        res = t_handler.do(ACTION_TYPE.INIT, options=options)
+        self.assertTrue(res)
+        # Check if the token was created and assigned
+        t = get_tokens(tokentype="totp")[0]
+        self.assertTrue(t)
+        self.assertEqual(t.user, user_obj)
+        self.assertEqual(t.get_tokeninfo("totp.hashlib"), "sha256")
+        remove_token(t.token.serial)
+
     def test_06_set_description(self):
         # setup realms
         self.setUp_user_realms()