Repository: getmoto/moto

Issue:
"InvalidParameter: Phone number does not meet the E164 format" from recent update to localstack SNS
Not sure if I should file this in the localstack Github Repo. Please advise if I need to move this issue. 

A recent update for validating E164 format broke localstack SNS subscribe. in the real AWS SNS, dashes in the E.164 formatted number is supported aside from the usual E.164 format.

**In AWS SNS:**
```
# usual 
+631234567890
# also still allowed
+63-123-456-7890
```
This works in our production SNS that uses `apiVersion: 2010-03-31`.

**When using localstack SNS** 
It rejects the dashed phone number with the error:
`Stack trace "InvalidParameter: Phone number does not meet the E164 format\n    at Request.extractError...` (This is when used via npm 'aws-sdk' in a nodejs app. A simpler repro using awscli is below.)

**Step to replicate** (needs `awscli`, and maybe `awscli-local`, installable by pip)
1. Spin up localstack SNS service. (defaults to http://localstack:4575)
2. Prepare a mock topic.
```
$ aws --endpoint-url=http://localhost:4575 sns create-topic --name SOME_TOPIC

{
    "TopicArn": "arn:aws:sns:us-west-2:123456789012:SOME_TOPIC"
}
```
3. Subscribe to the mock topic with "usual" E164 format. (succeeds)
```
$ aws --endpoint-url=http://localhost:4575 sns subscribe --topic-arn arn:aws:sns:us-west-2:123456789012:SOME_TOPIC --protocol sms --notification-endpoint +631234567890

{
    "SubscriptionArn": "arn:aws:sns:us-west-2:123456789012:SOME_TOPIC:2002f71d-1a9e-425c-9065-1d709b5c6c53"
}
```
4. Subscribe to the mock topic with "dashed" E164 format. (rejected).
```
$ aws --endpoint-url=http://localhost:4575 sns subscribe --topic-arn arn:aws:sns:us-west-2:123456789012:SOME_TOPIC --protocol sms --notification-endpoint +63-123-456-7890

An error occurred (InvalidParameter) when calling the Subscribe operation: Phone number does not meet the E164 format
```

The cause was a recent update to the REGEX for checking E164 format.
https://github.com/spulec/moto/blob/e00af2f73cb7d27c3755f18b2161b9acbd8ca8aa/moto/sns/utils.py#L5

Is it possible to make this REGEX support dashes? The real AWS SNS supports it, and our production db has thousands of records with the dashed format and they are working. Would be nice if localstack and moto can imitate that as well. =)



Discussion:

Resolving patch:
diff --git a/moto/sns/models.py b/moto/sns/models.py
--- a/moto/sns/models.py
+++ b/moto/sns/models.py
@@ -29,7 +29,7 @@
     ResourceNotFoundError,
     TagLimitExceededError,
 )
-from .utils import make_arn_for_topic, make_arn_for_subscription
+from .utils import make_arn_for_topic, make_arn_for_subscription, is_e164
 
 DEFAULT_ACCOUNT_ID = 123456789012
 DEFAULT_PAGE_SIZE = 100
@@ -413,6 +413,17 @@ def set_topic_attribute(self, topic_arn, attribute_name, attribute_value):
         setattr(topic, attribute_name, attribute_value)
 
     def subscribe(self, topic_arn, endpoint, protocol):
+        if protocol == "sms":
+            if re.search(r"[./-]{2,}", endpoint) or re.search(
+                r"(^[./-]|[./-]$)", endpoint
+            ):
+                raise SNSInvalidParameter("Invalid SMS endpoint: {}".format(endpoint))
+
+            reduced_endpoint = re.sub(r"[./-]", "", endpoint)
+
+            if not is_e164(reduced_endpoint):
+                raise SNSInvalidParameter("Invalid SMS endpoint: {}".format(endpoint))
+
         # AWS doesn't create duplicates
         old_subscription = self._find_subscription(topic_arn, endpoint, protocol)
         if old_subscription:
diff --git a/moto/sns/responses.py b/moto/sns/responses.py
--- a/moto/sns/responses.py
+++ b/moto/sns/responses.py
@@ -211,14 +211,6 @@ def subscribe(self):
         protocol = self._get_param("Protocol")
         attributes = self._get_attributes()
 
-        if protocol == "sms" and not is_e164(endpoint):
-            return (
-                self._error(
-                    "InvalidParameter", "Phone number does not meet the E164 format"
-                ),
-                dict(status=400),
-            )
-
         subscription = self.backend.subscribe(topic_arn, endpoint, protocol)
 
         if attributes is not None:


Test patch:
diff --git a/tests/test_sns/test_subscriptions_boto3.py b/tests/test_sns/test_subscriptions_boto3.py
--- a/tests/test_sns/test_subscriptions_boto3.py
+++ b/tests/test_sns/test_subscriptions_boto3.py
@@ -19,7 +19,10 @@ def test_subscribe_sms():
     arn = resp["TopicArn"]
 
     resp = client.subscribe(TopicArn=arn, Protocol="sms", Endpoint="+15551234567")
-    resp.should.contain("SubscriptionArn")
+    resp.should.have.key("SubscriptionArn")
+
+    resp = client.subscribe(TopicArn=arn, Protocol="sms", Endpoint="+15/55-123.4567")
+    resp.should.have.key("SubscriptionArn")
 
 
 @mock_sns
@@ -51,6 +54,18 @@ def test_subscribe_bad_sms():
     except ClientError as err:
         err.response["Error"]["Code"].should.equal("InvalidParameter")
 
+    client.subscribe.when.called_with(
+        TopicArn=arn, Protocol="sms", Endpoint="+15--551234567"
+    ).should.throw(ClientError, "Invalid SMS endpoint: +15--551234567")
+
+    client.subscribe.when.called_with(
+        TopicArn=arn, Protocol="sms", Endpoint="+15551234567."
+    ).should.throw(ClientError, "Invalid SMS endpoint: +15551234567.")
+
+    client.subscribe.when.called_with(
+        TopicArn=arn, Protocol="sms", Endpoint="/+15551234567"
+    ).should.throw(ClientError, "Invalid SMS endpoint: /+15551234567")
+
 
 @mock_sns
 def test_creating_subscription():