Repository: boto/botocore
Pull request #433 (2015-01-12 22:23:46)

Add option to specify endpoint's signature version
Add option to pass ``signature_version`` to EndpointCreator's ``create_endpoint`` method.

When ``get_endpoint`` is called from Service objects, it will pass in its signature version to ``create_endpoint``. That is required for the CLI's ``--no-sign-request`` flag to work because the handler changes the service object's signature version.

I will be sending a PR out real soon for the CLI, that will add a test to catch that --no-sign-request is adhered to.

cc @jamesls @danielgtaylor 


Patch:
diff --git a/botocore/endpoint.py b/botocore/endpoint.py
index 963068d87..519dd6c61 100644
--- a/botocore/endpoint.py
+++ b/botocore/endpoint.py
@@ -35,6 +35,7 @@ from botocore import parsers
 
 logger = logging.getLogger(__name__)
 DEFAULT_TIMEOUT = 60
+NOT_SET = object()
 
 
 def convert_to_response_dict(http_response, operation_model):
@@ -306,7 +307,8 @@ class EndpointCreator(object):
 
     def create_endpoint(self, service_model, region_name=None, is_secure=True,
                         endpoint_url=None, verify=None, credentials=None,
-                        response_parser_factory=None):
+                        response_parser_factory=None,
+                        signature_version=NOT_SET):
         if region_name is None:
             region_name = self._configured_region
         # Use the endpoint resolver heuristics to build the endpoint url.
@@ -328,9 +330,10 @@ class EndpointCreator(object):
         # provided region name.
         region_name_override = endpoint['properties'].get(
             'credentialScope', {}).get('region')
-        signature_version = service_model.signature_version
-        if 'signatureVersion' in endpoint['properties']:
-            signature_version = endpoint['properties']['signatureVersion']
+        if signature_version is NOT_SET:
+            signature_version = service_model.signature_version
+            if 'signatureVersion' in endpoint['properties']:
+                signature_version = endpoint['properties']['signatureVersion']
         if region_name_override is not None:
             # Letting the heuristics rule override the region_name
             # allows for having a default region of something like us-west-2
diff --git a/botocore/service.py b/botocore/service.py
index 6eac220a8..409566f0f 100644
--- a/botocore/service.py
+++ b/botocore/service.py
@@ -65,6 +65,7 @@ class Service(object):
         # as an attribute for client obejcts.
         self.service_name = service_name
         self._signature_version = NOT_SET
+        self._has_custom_signature_version = False
         # Not all services have a top level documentation key,
         # so we'll add one if needed.
         if 'documentation' not in self.__dict__:
@@ -99,11 +100,13 @@ class Service(object):
         if self._signature_version is NOT_SET:
             signature_version = self._model.metadata.get('signatureVersion')
             self._signature_version = signature_version
+            self._has_custom_signature_version = False
         return self._signature_version
 
     @signature_version.setter
     def signature_version(self, value):
         self._signature_version = value
+        self._has_custom_signature_version = True
 
     def _create_operation_objects(self):
         logger.debug("Creating operation objects for: %s", self)
@@ -158,9 +161,14 @@ class Service(object):
         user_agent= self.session.user_agent()
         endpoint_creator = EndpointCreator(resolver, region, event_emitter,
                                            credentials, user_agent)
-        return endpoint_creator.create_endpoint(
-            self._model, region_name, is_secure, endpoint_url, verify,
-            response_parser_factory=response_parser_factory)
+        kwargs = {'service_model': self._model, 'region_name': region_name,
+                  'is_secure': is_secure, 'endpoint_url': endpoint_url,
+                  'verify': verify,
+                  'response_parser_factory': response_parser_factory}
+        if self._has_custom_signature_version:
+            kwargs['signature_version'] = self.signature_version
+
+        return endpoint_creator.create_endpoint(**kwargs)
 
     def get_operation(self, operation_name):
         """


Test patch:
diff --git a/tests/unit/test_endpoint.py b/tests/unit/test_endpoint.py
index fc46616a6..53e8bcfe8 100644
--- a/tests/unit/test_endpoint.py
+++ b/tests/unit/test_endpoint.py
@@ -179,6 +179,20 @@ class TestEndpointFeatures(TestEndpointBase):
         prepared_request = self.http_session.send.call_args[0][0]
         self.assertNotIn('Authorization', prepared_request.headers)
 
+    def test_make_request_no_signature_version(self):
+        self.endpoint = Endpoint(
+            'us-west-2', 'https://ec2.us-west-2.amazonaws.com/',
+            auth=self.auth, user_agent='botoore', signature_version=None,
+            endpoint_prefix='ec2', event_emitter=self.event_emitter)
+        self.endpoint.http_session = self.http_session
+
+        self.endpoint.make_request(self.op, request_dict())
+
+        # http_session should be used to send the request.
+        self.assertTrue(self.http_session.send.called)
+        prepared_request = self.http_session.send.call_args[0][0]
+        self.assertNotIn('Authorization', prepared_request.headers)
+
 
 class TestRetryInterface(TestEndpointBase):
     def setUp(self):
diff --git a/tests/unit/test_service.py b/tests/unit/test_service.py
index 3b38a11e1..b4a84a786 100644
--- a/tests/unit/test_service.py
+++ b/tests/unit/test_service.py
@@ -11,6 +11,7 @@
 # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
 # ANY KIND, either express or implied. See the License for the specific
 # language governing permissions and limitations under the License.
+import mock
 
 from tests import BaseSessionTest
 
@@ -83,6 +84,17 @@ class TestService(BaseSessionTest):
             endpoint_url='http://custom-endpoint/')
         self.assertEqual(endpoint.host, 'http://custom-endpoint/')
 
+    def test_turnoff_signing(self):
+        service = self.session.get_service('ec2')
+        service.signature_version = None
+        with mock.patch('botocore.endpoint.EndpointCreator.create_endpoint') \
+                as mock_create_endpoint:
+            service.get_endpoint('us-east-1')
+            self.assertEqual(
+                mock_create_endpoint.call_args[1]['signature_version'],
+                None
+            )
+
 
 if __name__ == "__main__":
     unittest.main()