Repository: getmoto/moto

Issue:
Meta - Builds are failing in Server Mode
Last successful build: https://travis-ci.org/github/spulec/moto/jobs/702363984
Failing build (example): https://travis-ci.org/github/spulec/moto/jobs/702608025

Failing test:
```
===================================================================
ERROR: test_cognitoidp.test_change_password
----------------------------------------------------------------------
Traceback (most recent call last):

  File "/home/travis/virtualenv/python2.7.17/lib/python2.7/site-packages/nose/case.py", line 197, in runTest
    self.test(*self.arg)
  File "/home/travis/build/spulec/moto/moto/core/models.py", line 98, in wrapper
    result = func(*args, **kwargs)
  File "/home/travis/build/spulec/moto/tests/test_cognitoidp/test_cognitoidp.py", line 1224, in test_change_password
    outputs = authentication_flow(conn)
  File "/home/travis/build/spulec/moto/tests/test_cognitoidp/test_cognitoidp.py", line 1169, in authentication_flow
    ChallengeResponses={"USERNAME": username, "NEW_PASSWORD": new_password},
  File "/home/travis/virtualenv/python2.7.17/lib/python2.7/site-packages/botocore/client.py", line 316, in _api_call
    return self._make_api_call(operation_name, kwargs)
  File "/home/travis/virtualenv/python2.7.17/lib/python2.7/site-packages/botocore/client.py", line 635, in _make_api_call
    raise error_class(parsed_response, operation_name)
ResourceNotFoundException: An error occurred (ResourceNotFoundException) when calling the RespondToAuthChallenge operation: b22f073c-53fb-43ef-971c-65c161cd5924
```

No changes have been made to our Cognito IDP code in between.

## Cause:

A new version of boto3 was released (1.14.11 -> 1.14.12), which depends on a new version of botocore (1.17.11 -> 1.7.12)
Botocore has made a change to the CognitoIDP RespondToAuthChallenge response format:
https://github.com/boto/botocore/commit/2300e3a87e2835c93d832ee9c8ec7433cfa7fe18#diff-104dd1e3491647c1ef3da008f40f2abdR1331

Will continue to investigate


Discussion:

Resolving patch:
diff --git a/moto/core/models.py b/moto/core/models.py
--- a/moto/core/models.py
+++ b/moto/core/models.py
@@ -10,6 +10,7 @@
 import types
 from io import BytesIO
 from collections import defaultdict
+from botocore.config import Config
 from botocore.handlers import BUILTIN_HANDLERS
 from botocore.awsrequest import AWSResponse
 from six.moves.urllib.parse import urlparse
@@ -416,6 +417,13 @@ def enable_patching(self):
         import mock
 
         def fake_boto3_client(*args, **kwargs):
+            region = self._get_region(*args, **kwargs)
+            if region:
+                if "config" in kwargs:
+                    kwargs["config"].__dict__["user_agent_extra"] += " region/" + region
+                else:
+                    config = Config(user_agent_extra="region/" + region)
+                    kwargs["config"] = config
             if "endpoint_url" not in kwargs:
                 kwargs["endpoint_url"] = "http://localhost:5000"
             return real_boto3_client(*args, **kwargs)
@@ -463,6 +471,14 @@ def _convert_to_bytes(mixed_buffer):
         if six.PY2:
             self._httplib_patcher.start()
 
+    def _get_region(self, *args, **kwargs):
+        if "region_name" in kwargs:
+            return kwargs["region_name"]
+        if type(args) == tuple and len(args) == 2:
+            service, region = args
+            return region
+        return None
+
     def disable_patching(self):
         if self._client_patcher:
             self._client_patcher.stop()
diff --git a/moto/core/responses.py b/moto/core/responses.py
--- a/moto/core/responses.py
+++ b/moto/core/responses.py
@@ -188,6 +188,9 @@ class BaseResponse(_TemplateEnvironmentMixin, ActionAuthenticatorMixin):
     default_region = "us-east-1"
     # to extract region, use [^.]
     region_regex = re.compile(r"\.(?P<region>[a-z]{2}-[a-z]+-\d{1})\.amazonaws\.com")
+    region_from_useragent_regex = re.compile(
+        r"region/(?P<region>[a-z]{2}-[a-z]+-\d{1})"
+    )
     param_list_regex = re.compile(r"(.*)\.(\d+)\.")
     access_key_regex = re.compile(
         r"AWS.*(?P<access_key>(?<![A-Z0-9])[A-Z0-9]{20}(?![A-Z0-9]))[:/]"
@@ -272,9 +275,14 @@ def setup_class(self, request, full_url, headers):
         self.response_headers = {"server": "amazon.com"}
 
     def get_region_from_url(self, request, full_url):
-        match = self.region_regex.search(full_url)
-        if match:
-            region = match.group(1)
+        url_match = self.region_regex.search(full_url)
+        user_agent_match = self.region_from_useragent_regex.search(
+            request.headers.get("User-Agent", "")
+        )
+        if url_match:
+            region = url_match.group(1)
+        elif user_agent_match:
+            region = user_agent_match.group(1)
         elif (
             "Authorization" in request.headers
             and "AWS4" in request.headers["Authorization"]


Test patch:
diff --git a/tests/test_cognitoidp/test_cognitoidp.py b/tests/test_cognitoidp/test_cognitoidp.py
--- a/tests/test_cognitoidp/test_cognitoidp.py
+++ b/tests/test_cognitoidp/test_cognitoidp.py
@@ -1243,6 +1243,38 @@ def test_change_password():
     result["AuthenticationResult"].should_not.be.none
 
 
+@mock_cognitoidp
+def test_change_password__using_custom_user_agent_header():
+    # https://github.com/spulec/moto/issues/3098
+    # As the admin_initiate_auth-method is unauthenticated, we use the user-agent header to pass in the region
+    # This test verifies this works, even if we pass in our own user-agent header
+    from botocore.config import Config
+
+    my_config = Config(user_agent_extra="more/info", signature_version="v4")
+    conn = boto3.client("cognito-idp", "us-west-2", config=my_config)
+
+    outputs = authentication_flow(conn)
+
+    # Take this opportunity to test change_password, which requires an access token.
+    newer_password = str(uuid.uuid4())
+    conn.change_password(
+        AccessToken=outputs["access_token"],
+        PreviousPassword=outputs["password"],
+        ProposedPassword=newer_password,
+    )
+
+    # Log in again, which should succeed without a challenge because the user is no
+    # longer in the force-new-password state.
+    result = conn.admin_initiate_auth(
+        UserPoolId=outputs["user_pool_id"],
+        ClientId=outputs["client_id"],
+        AuthFlow="ADMIN_NO_SRP_AUTH",
+        AuthParameters={"USERNAME": outputs["username"], "PASSWORD": newer_password},
+    )
+
+    result["AuthenticationResult"].should_not.be.none
+
+
 @mock_cognitoidp
 def test_forgot_password():
     conn = boto3.client("cognito-idp", "us-west-2")