Repository: getmoto/moto

Issue:
create_bucket should fail with CreateBucketConfiguration={}
The following code produces an obscure exception with real (unmocked) boto3:
```
import boto3
s3_client = boto3.client("s3", region_name="us-east-1")
s3_client.create_bucket(ACL="private", Bucket="eldar-temp", CreateBucketConfiguration={})
```
Result:
```
---------------------------------------------------------------------------
ClientError                               Traceback (most recent call last)
<ipython-input-5-3f69eeffcfc7> in <module>()
----> 1 s3_client.create_bucket(ACL="private", Bucket="eldar-temp", CreateBucketConfiguration={})

/usr/local/lib/python2.7/dist-packages/botocore/client.pyc in _api_call(self, *args, **kwargs)
    312                     "%s() only accepts keyword arguments." % py_operation_name)
    313             # The "self" in this scope is referring to the BaseClient.
--> 314             return self._make_api_call(operation_name, kwargs)
    315
    316         _api_call.__name__ = str(py_operation_name)

/usr/local/lib/python2.7/dist-packages/botocore/client.pyc in _make_api_call(self, operation_name, api_params)
    610             error_code = parsed_response.get("Error", {}).get("Code")
    611             error_class = self.exceptions.from_code(error_code)
--> 612             raise error_class(parsed_response, operation_name)
    613         else:
    614             return parsed_response

ClientError: An error occurred (MalformedXML) when calling the CreateBucket operation: The XML you provided was not well-formed or did not validate against our published schema
```

What boto3 (or rather, underlying s3 API) is unhappy about is the `CreateBucketConfiguration={}` statement.

When this code is mocked with moto's mock_s3, the call to create_bucket succeeds, so its behavior is inconsistent with actual boto3.

I'm using moto==1.3.8.
test_streaming_upload_from_file_to_presigned_url fails
# Steps to reproduce

`make test` on a fresh copy of `master`.

# Expected result

All tests pass.

# Actual result

One S3 test (`test_streaming_upload_from_file_to_presigned_url`) produces an error:

```
ERROR: tests.test_s3.test_s3.test_streaming_upload_from_file_to_presigned_url
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/dawn.james/dev/github.com/dawngerpony/moto/.venv/lib/python3.7/site-packages/nose/case.py", line 198, in runTest
    self.test(*self.arg)
  File "/Users/dawn.james/dev/github.com/dawngerpony/moto/moto/core/models.py", line 98, in wrapper
    result = func(*args, **kwargs)
  File "/Users/dawn.james/dev/github.com/dawngerpony/moto/tests/test_s3/test_s3.py", line 1048, in test_streaming_upload_from_file_to_presigned_url
    bucket.create()
  File "/Users/dawn.james/dev/github.com/dawngerpony/moto/.venv/lib/python3.7/site-packages/boto3-1.14.6-py3.7.egg/boto3/resources/factory.py", line 520, in do_action
    response = action(self, *args, **kwargs)
  File "/Users/dawn.james/dev/github.com/dawngerpony/moto/.venv/lib/python3.7/site-packages/boto3-1.14.6-py3.7.egg/boto3/resources/action.py", line 83, in __call__
    response = getattr(parent.meta.client, operation_name)(*args, **params)
  File "/Users/dawn.james/dev/github.com/dawngerpony/moto/.venv/lib/python3.7/site-packages/botocore-1.17.6-py3.7.egg/botocore/client.py", line 316, in _api_call
    return self._make_api_call(operation_name, kwargs)
  File "/Users/dawn.james/dev/github.com/dawngerpony/moto/.venv/lib/python3.7/site-packages/botocore-1.17.6-py3.7.egg/botocore/client.py", line 635, in _make_api_call
    raise error_class(parsed_response, operation_name)
botocore.exceptions.ClientError: An error occurred (IllegalLocationConstraintException) when calling the CreateBucket operation: The unspecified location constraint is incompatible for the region specific endpoint this request was sent to.
```

# Notes

* The fix appears to be simple, I have included it in #3079, but I thought I should raise a separate issue anyway.
* I'm not sure why this is happening today, I'm pretty sure it wasn't happening yesterday, but the fix seems harmless so I see no problem in making it.



Discussion:
Thanks; setting as a feature request for us to do validation of the bucket config.
Since this issue was raised a while ago, it might be worth mentioning that this is still an issue:

```
Traceback (most recent call last):
  File "issue_2210.py", line 3, in <module>
    s3_client.create_bucket(ACL="private", Bucket="eldar-temp", CreateBucketConfiguration={})
  File "/Users/dawn.james/dev/github.com/dawngerpony/moto/.venv/lib/python3.7/site-packages/botocore-1.17.6-py3.7.egg/botocore/client.py", line 316, in _api_call
    return self._make_api_call(operation_name, kwargs)
  File "/Users/dawn.james/dev/github.com/dawngerpony/moto/.venv/lib/python3.7/site-packages/botocore-1.17.6-py3.7.egg/botocore/client.py", line 635, in _make_api_call
    raise error_class(parsed_response, operation_name)
botocore.exceptions.ClientError: An error occurred (MalformedXML) when calling the CreateBucket operation: The XML you provided was not well-formed or did not validate against our published schema
```



Resolving patch:
diff --git a/moto/s3/responses.py b/moto/s3/responses.py
--- a/moto/s3/responses.py
+++ b/moto/s3/responses.py
@@ -615,6 +615,19 @@ def _body_contains_location_constraint(self, body):
                 pass
         return False
 
+    def _create_bucket_configuration_is_empty(self, body):
+        if body:
+            try:
+                create_bucket_configuration = xmltodict.parse(body)[
+                    "CreateBucketConfiguration"
+                ]
+                del create_bucket_configuration["@xmlns"]
+                if len(create_bucket_configuration) == 0:
+                    return True
+            except KeyError:
+                pass
+        return False
+
     def _parse_pab_config(self, body):
         parsed_xml = xmltodict.parse(body)
         parsed_xml["PublicAccessBlockConfiguration"].pop("@xmlns", None)
@@ -733,6 +746,9 @@ def _bucket_response_put(
             ):
                 raise IllegalLocationConstraintException()
             if body:
+                if self._create_bucket_configuration_is_empty(body):
+                    raise MalformedXML()
+
                 try:
                     forced_region = xmltodict.parse(body)["CreateBucketConfiguration"][
                         "LocationConstraint"


Test patch:
diff --git a/tests/test_s3/test_s3.py b/tests/test_s3/test_s3.py
--- a/tests/test_s3/test_s3.py
+++ b/tests/test_s3/test_s3.py
@@ -1042,7 +1042,7 @@ def test_s3_object_in_public_bucket_using_multiple_presigned_urls():
 
 @mock_s3
 def test_streaming_upload_from_file_to_presigned_url():
-    s3 = boto3.resource("s3")
+    s3 = boto3.resource("s3", region_name="us-east-1")
     bucket = s3.Bucket("test-bucket")
     bucket.create()
     bucket.put_object(Body=b"ABCD", Key="file.txt")
@@ -1976,6 +1976,15 @@ def test_boto3_bucket_create_eu_central():
     )
 
 
+@mock_s3
+def test_bucket_create_empty_bucket_configuration_should_return_malformed_xml_error():
+    s3 = boto3.resource("s3", region_name="us-east-1")
+    with assert_raises(ClientError) as e:
+        s3.create_bucket(Bucket="whatever", CreateBucketConfiguration={})
+    e.exception.response["Error"]["Code"].should.equal("MalformedXML")
+    e.exception.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
+
+
 @mock_s3
 def test_boto3_head_object():
     s3 = boto3.resource("s3", region_name=DEFAULT_REGION_NAME)