Skip to content
This repository
Browse code

Merge pull request #1134 from jamesls/sigv4-overrides

Switch SQS to Signature Version 4
  • Loading branch information...
commit 87a399ba09a588e2865624b0807a7aa4c38106a6 2 parents e9a059c + f725244
Mitch Garnaat garnaat authored
29 boto/auth.py
@@ -303,9 +303,15 @@ class HmacAuthV4Handler(AuthHandler, HmacKeys):
303 303
304 304 capability = ['hmac-v4']
305 305
306   - def __init__(self, host, config, provider):
  306 + def __init__(self, host, config, provider,
  307 + service_name=None, region_name=None):
307 308 AuthHandler.__init__(self, host, config, provider)
308 309 HmacKeys.__init__(self, host, config, provider)
  310 + # You can set the service_name and region_name to override the
  311 + # values which would otherwise come from the endpoint, e.g.
  312 + # <service>.<region>.amazonaws.com.
  313 + self.service_name = service_name
  314 + self.region_name = region_name
309 315
310 316 def _sign(self, key, msg, hex=False):
311 317 if hex:
@@ -399,13 +405,26 @@ def credential_scope(self, http_request):
399 405 scope = []
400 406 http_request.timestamp = http_request.headers['X-Amz-Date'][0:8]
401 407 scope.append(http_request.timestamp)
  408 + # The service_name and region_name either come from:
  409 + # * The service_name/region_name attrs or (if these values are None)
  410 + # * parsed from the endpoint <service>.<region>.amazonaws.com.
402 411 parts = http_request.host.split('.')
403   - if len(parts) == 3:
404   - http_request.region_name = 'us-east-1'
  412 + if self.region_name is not None:
  413 + region_name = self.region_name
  414 + else:
  415 + if len(parts) == 3:
  416 + region_name = 'us-east-1'
  417 + else:
  418 + region_name = parts[1]
  419 + if self.service_name is not None:
  420 + service_name = self.service_name
405 421 else:
406   - http_request.region_name = parts[1]
  422 + service_name = parts[0]
  423 +
  424 + http_request.service_name = service_name
  425 + http_request.region_name = region_name
  426 +
407 427 scope.append(http_request.region_name)
408   - http_request.service_name = parts[0]
409 428 scope.append(http_request.service_name)
410 429 scope.append('aws4_request')
411 430 return '/'.join(scope)
19 boto/connection.py
@@ -546,6 +546,8 @@ def __init__(self, host, aws_access_key_id=None,
546 546 self._last_rs = None
547 547 self._auth_handler = auth.get_auth_handler(
548 548 host, config, self.provider, self._required_auth_capability())
  549 + if getattr(self, 'AuthServiceName', None) is not None:
  550 + self.auth_service_name = self.AuthServiceName
549 551
550 552 def __repr__(self):
551 553 return '%s:%s' % (self.__class__.__name__, self.host)
@@ -553,6 +555,23 @@ def __repr__(self):
553 555 def _required_auth_capability(self):
554 556 return []
555 557
  558 + def _get_auth_service_name(self):
  559 + return getattr(self._auth_handler, 'service_name')
  560 +
  561 + # For Sigv4, the auth_service_name/auth_region_name properties allow
  562 + # the service_name/region_name to be explicitly set instead of being
  563 + # derived from the endpoint url.
  564 + def _set_auth_service_name(self, value):
  565 + self._auth_handler.service_name = value
  566 + auth_service_name = property(_get_auth_service_name, _set_auth_service_name)
  567 +
  568 + def _get_auth_region_name(self):
  569 + return getattr(self._auth_handler, 'region_name')
  570 +
  571 + def _set_auth_region_name(self, value):
  572 + self._auth_handler.region_name = value
  573 + auth_region_name = property(_get_auth_service_name, _set_auth_region_name)
  574 +
556 575 def connection(self):
557 576 return self.get_http_connection(*self._connection)
558 577 connection = property(connection)
4 boto/sqs/connection.py
@@ -37,6 +37,7 @@ class SQSConnection(AWSQueryConnection):
37 37 APIVersion = '2012-11-05'
38 38 DefaultContentType = 'text/plain'
39 39 ResponseError = SQSError
  40 + AuthServiceName = 'sqs'
40 41
41 42 def __init__(self, aws_access_key_id=None, aws_secret_access_key=None,
42 43 is_secure=True, port=None, proxy=None, proxy_port=None,
@@ -56,9 +57,10 @@ def __init__(self, aws_access_key_id=None, aws_secret_access_key=None,
56 57 https_connection_factory, path,
57 58 security_token=security_token,
58 59 validate_certs=validate_certs)
  60 + self.auth_region_name = self.region.name
59 61
60 62 def _required_auth_capability(self):
61   - return ['sqs']
  63 + return ['hmac-v4']
62 64
63 65 def create_queue(self, queue_name, visibility_timeout=None):
64 66 """
1  tests/integration/sqs/test_connection.py
@@ -208,6 +208,7 @@ def send_message():
208 208 response = queue.read(wait_time_seconds=10)
209 209 end = time.time()
210 210
  211 + t.join()
211 212 self.assertEqual(response.id, messages[0].id)
212 213 self.assertEqual(response.get_body(), messages[0].get_body())
213 214 # The timer thread should send the message in 5 seconds, so
5 tests/unit/__init__.py
@@ -22,6 +22,9 @@ def setUp(self):
22 22 https_connection_factory=self.https_connection_factory,
23 23 aws_access_key_id='aws_access_key_id',
24 24 aws_secret_access_key='aws_secret_access_key')
  25 + self.initialize_service_connection()
  26 +
  27 + def initialize_service_connection(self):
25 28 self.actual_request = None
26 29 self.original_mexe = self.service_connection._mexe
27 30 self.service_connection._mexe = self._mexe_spy
@@ -52,7 +55,7 @@ def overwrite_header(arg, default=None):
52 55 else:
53 56 return default
54 57 response.getheader.side_effect = overwrite_header
55   -
  58 +
56 59 return response
57 60
58 61 def assert_request_parameters(self, params, ignore_params_values=None):
10 tests/unit/auth/test_sigv4.py
@@ -61,3 +61,13 @@ def test_canonical_query_string(self):
61 61 request.params['Foo.10'] = 'zzz'
62 62 query_string = auth.canonical_query_string(request)
63 63 self.assertEqual(query_string, 'Foo.1=aaa&Foo.10=zzz')
  64 +
  65 + def test_region_and_service_can_be_overriden(self):
  66 + auth = HmacAuthV4Handler('queue.amazonaws.com',
  67 + Mock(), self.provider)
  68 + self.request.headers['X-Amz-Date'] = '20121121000000'
  69 +
  70 + auth.region_name = 'us-west-2'
  71 + auth.service_name = 'sqs'
  72 + scope = auth.credential_scope(self.request)
  73 + self.assertEqual(scope, '20121121/us-west-2/sqs/aws4_request')
0  tests/unit/sqs/__init__.py
No changes.
90 tests/unit/sqs/test_connection.py
... ... @@ -0,0 +1,90 @@
  1 +#!/usr/bin/env python
  2 +# Copyright (c) 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved
  3 +#
  4 +# Permission is hereby granted, free of charge, to any person obtaining a
  5 +# copy of this software and associated documentation files (the
  6 +# "Software"), to deal in the Software without restriction, including
  7 +# without limitation the rights to use, copy, modify, merge, publish, dis-
  8 +# tribute, sublicense, and/or sell copies of the Software, and to permit
  9 +# persons to whom the Software is furnished to do so, subject to the fol-
  10 +# lowing conditions:
  11 +#
  12 +# The above copyright notice and this permission notice shall be included
  13 +# in all copies or substantial portions of the Software.
  14 +#
  15 +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
  16 +# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
  17 +# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
  18 +# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  19 +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20 +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  21 +# IN THE SOFTWARE.
  22 +#
  23 +
  24 +from tests.unit import unittest
  25 +from tests.unit import AWSMockServiceTestCase
  26 +
  27 +from boto.sqs.connection import SQSConnection
  28 +from boto.sqs.regioninfo import SQSRegionInfo
  29 +
  30 +
  31 +class SQSAuthParams(AWSMockServiceTestCase):
  32 + connection_class = SQSConnection
  33 +
  34 + def setUp(self):
  35 + super(SQSAuthParams, self).setUp()
  36 +
  37 + def default_body(self):
  38 + return """<?xml version="1.0"?>
  39 + <CreateQueueResponse>
  40 + <CreateQueueResult>
  41 + <QueueUrl>
  42 + https://queue.amazonaws.com/599169622985/myqueue1
  43 + </QueueUrl>
  44 + </CreateQueueResult>
  45 + <ResponseMetadata>
  46 + <RequestId>54d4c94d-2307-54a8-bb27-806a682a5abd</RequestId>
  47 + </ResponseMetadata>
  48 + </CreateQueueResponse>"""
  49 +
  50 + def test_auth_service_name_override(self):
  51 + self.set_http_response(status_code=200)
  52 + # We can use the auth_service_name to change what service
  53 + # name to use for the credential scope for sigv4.
  54 + self.service_connection.auth_service_name = 'service_override'
  55 +
  56 + self.service_connection.create_queue('my_queue')
  57 + # Note the service_override value instead.
  58 + self.assertIn('us-east-1/service_override/aws4_request',
  59 + self.actual_request.headers['Authorization'])
  60 +
  61 + def test_class_attribute_can_set_service_name(self):
  62 + self.set_http_response(status_code=200)
  63 + # The SQS class has an 'AuthServiceName' param of 'sqs':
  64 + self.assertEqual(self.service_connection.AuthServiceName, 'sqs')
  65 +
  66 + self.service_connection.create_queue('my_queue')
  67 + # And because of this, the value of 'sqs' will be used instead of
  68 + # 'queue' for the credential scope:
  69 + self.assertIn('us-east-1/sqs/aws4_request',
  70 + self.actual_request.headers['Authorization'])
  71 +
  72 + def test_auth_region_name_is_automatically_updated(self):
  73 + region = SQSRegionInfo(name='us-west-2',
  74 + endpoint='us-west-2.queue.amazonaws.com')
  75 + self.service_connection = SQSConnection(
  76 + https_connection_factory=self.https_connection_factory,
  77 + aws_access_key_id='aws_access_key_id',
  78 + aws_secret_access_key='aws_secret_access_key',
  79 + region=region)
  80 + self.initialize_service_connection()
  81 + self.set_http_response(status_code=200)
  82 +
  83 + self.service_connection.create_queue('my_queue')
  84 + # Note the region name below is 'us-west-2'.
  85 + self.assertIn('us-west-2/sqs/aws4_request',
  86 + self.actual_request.headers['Authorization'])
  87 +
  88 +
  89 +if __name__ == '__main__':
  90 + unittest.main()

0 comments on commit 87a399b

Please sign in to comment.
Something went wrong with that request. Please try again.