Repository: privacyidea/privacyidea

Issue:
HTTPSMSProvider implement retries
hi,
it would be great if there would be retries for sending SMS, because there might be several components between us and SMS provider where interruptions might happen e.g. our network/Internet/Provider HA etc...


Discussion:
On what kind of retries are you thinking? Send within 30 seconds again? 3 times each minute?

Currently this is not possible to add, since we do not detach the sending of the SMS, i.e. the SMS is send within the first HTTP request. There is no task or cron job, that issues the new SMS sending. 

However, we plan to add a cron runner (#992) and maybe this would also be an additional way to use the cron runner.
@cornelinux not at this level, really at the http request level. More precisely somewhere here: https://github.com/privacyidea/privacyidea/blob/master/privacyidea/lib/smsprovider/HttpSMSProvider.py#L133

Something like a retry using the `r.status_code` as a control to be a bit more persistent on the HTTP request.
Still. This is the library that triggers the request to the SMS Gateway. And this actually happens within the _first_ request to the privacyIDEA server. Thus, the originial request (with the static password) would only return after ``n`` minutes, till all retries have failed.

Resolving patch:
diff --git a/privacyidea/lib/smsprovider/HttpSMSProvider.py b/privacyidea/lib/smsprovider/HttpSMSProvider.py
--- a/privacyidea/lib/smsprovider/HttpSMSProvider.py
+++ b/privacyidea/lib/smsprovider/HttpSMSProvider.py
@@ -3,6 +3,8 @@
 #    E-mail: info@privacyidea.org
 #    Contact: www.privacyidea.org
 #
+#    2018-06-15 Pascal Fuks <pascal@foxit.pro>
+#               Added REGEXP parameter on phone number
 #    2018-01-10 Cornelius Kölbel <cornelius.koelbel@netknights.it>
 #               Fix type cast for timeout
 #    2016-06-14 Cornelius Kölbel <cornelius@privacyidea.org>
@@ -46,13 +48,27 @@
 from privacyidea.lib import _
 import requests
 from urlparse import urlparse
-
+import re
 import logging
 log = logging.getLogger(__name__)
 
 
 class HttpSMSProvider(ISMSProvider):
 
+    @staticmethod
+    def _mangle_phone(phone, config):
+        regexp = config.get("REGEXP")
+        if regexp:
+            try:
+                m = re.match("^/(.*)/(.*)/$", regexp)
+                if m:
+                    phone = re.sub(m.group(1), m.group(2), phone)
+            except re.error:
+                log.warning(u"Can not mangle phone number. "
+                            u"Please check your REGEXP: {0!s}".format(regexp))
+
+        return phone
+
     def submit_message(self, phone, message):
         """
         send a message to a phone via an http sms gateway
@@ -64,6 +80,7 @@ def submit_message(self, phone, message):
         log.debug("submitting message {0!r} to {1!s}".format(message, phone))
         parameter = {}
         if self.smsgateway:
+            phone = self._mangle_phone(phone, self.smsgateway.option_dict)
             url = self.smsgateway.option_dict.get("URL")
             method = self.smsgateway.option_dict.get("HTTP_METHOD", "GET")
             username = self.smsgateway.option_dict.get("USERNAME")
@@ -80,6 +97,7 @@ def submit_message(self, phone, message):
                     # This is an additional option
                     parameter[k] = v.format(otp=message, phone=phone)
         else:
+            phone = self._mangle_phone(phone, self.config)
             url = self.config.get('URL')
             method = self.config.get('HTTP_Method', 'GET')
             username = self.config.get('USERNAME')
@@ -244,6 +262,12 @@ def parameters(cls):
                                            "verified."),
                           "values": ["yes", "no"]
                       },
+                      "REGEXP": {
+                          "description": _("Regular expression to modify the phone number "                 
+                                           "to make it compatible with provider. "
+                                           "Enter something like '/[\+/]//' to remove "
+                                           "pluses and slashes.")
+                      },
                       "PROXY": {"description": _("An optional proxy string. DEPRECATED. Do not use"
                                                  "this anymore. Rather use HTTP_PROXY for http connections and"
                                                  "HTTPS_PROXY for https connection. The PROXY option will be"
@@ -254,3 +278,4 @@ def parameters(cls):
                   }
                   }
         return params
+        
\ No newline at end of file


Test patch:
diff --git a/tests/test_lib_smsprovider.py b/tests/test_lib_smsprovider.py
--- a/tests/test_lib_smsprovider.py
+++ b/tests/test_lib_smsprovider.py
@@ -284,6 +284,19 @@ class HttpSMSTestCase(MyTestCase):
                    "RETURN_SUCCESS": "ID"
     }
 
+    config_regexp = {"URL": post_url,
+                     "PARAMETER": {"from": "0170111111",
+                                   "password": "yoursecret",
+                                   "sender": "name",
+                                   "account": "company_ltd"},
+                     "SMS_TEXT_KEY": "text",
+                     "SMS_PHONENUMBER_KEY": "destination",
+                     "HTTP_Method": "POST",
+                     "PROXY": "http://username:password@your-proxy:8080",
+                     "RETURN_SUCCESS": "ID",
+                     "REGEXP": "/[+-/. ]//"
+                     }
+
     get_url = "http://api.clickatell.com/http/sendmsg"
     config_get = {"URL": get_url,
                   "PARAMETER": {"user": "username",
@@ -329,6 +342,9 @@ def setUp(self):
         self.post_provider = HttpSMSProvider()
         self.post_provider.load_config(self.config_post)
 
+        self.regexp_provider = HttpSMSProvider()
+        self.regexp_provider.load_config(self.config_regexp)
+
         self.get_provider = HttpSMSProvider()
         self.get_provider.load_config(self.config_get)
 
@@ -351,6 +367,37 @@ def test_01_send_sms_post_success(self):
         r = self.post_provider.submit_message("123456", "Hello")
         self.assertTrue(r)
 
+    @responses.activate
+    def test_01_send_sms_regexp_success(self):
+        responses.add(responses.POST,
+                      self.post_url,
+                      body=self.success_body)
+        # Here we need to send the SMS
+        r = self.regexp_provider.submit_message("+49 123/456-78", "Hello")
+        self.assertTrue(r)
+
+        p = HttpSMSProvider._mangle_phone("+49 123/456-78", self.config_regexp)
+        self.assertEqual("4912345678", p)
+
+        # Replace + with 00
+        p = HttpSMSProvider._mangle_phone("+49 123/456-78", {"REGEXP": "/\+/00/"})
+        self.assertEqual("0049 123/456-78", p)
+        p = self.regexp_provider._mangle_phone("+49 123/456-78", {"REGEXP": "/[\+/]//"})
+        self.assertEqual("49 123456-78", p)
+
+        # An invalid regexp is caught and a log error is written. The same
+        # phone number is returned
+        p = HttpSMSProvider._mangle_phone("+49 123/456-78", {"REGEXP": "/+/00/"})
+        self.assertEqual("+49 123/456-78", p)
+
+        # Only use leading numbers and not the rest
+        p = HttpSMSProvider._mangle_phone("12345abcdef", {"REGEXP":  "/^([0-9]+).*/\\1/"})
+        self.assertEqual("12345", p)
+
+        # Known limitation: The slash in the replace statement does not work!
+        p = HttpSMSProvider._mangle_phone("12.34.56.78", {"REGEXP": "/\./\//"})
+        self.assertEqual("12.34.56.78", p)
+
     @responses.activate
     def test_02_send_sms_post_fail(self):
         responses.add(responses.POST,