diff --git a/boto/auth.py b/boto/auth.py index f05bb275ec..b2bcc62e40 100644 --- a/boto/auth.py +++ b/boto/auth.py @@ -234,7 +234,7 @@ def canonical_headers(self, headers_to_sign): them into a string, separated by newlines. """ l = sorted(['%s:%s' % (n.lower().strip(), - headers_to_sign[n].strip()) for n in headers_to_sign]) + headers_to_sign[n].strip()) for n in headers_to_sign]) return '\n'.join(l) def string_to_sign(self, http_request): @@ -305,12 +305,15 @@ def _sign(self, key, msg, hex=False): sig = hmac.new(key, msg.encode('utf-8'), sha256).digest() return sig - def headers_to_sign(self, http_request): + def headers_to_sign(self, http_request, override_for_cloudsearch=False): """ Select the headers from the request that need to be included in the StringToSign. """ - host_header_value = self.host_header(self.host, http_request) + if override_for_cloudsearch: + host_header_value = http_request.host + else: + host_header_value = self.host_header(self.host, http_request) headers_to_sign = {'Host': host_header_value} for name, value in http_request.headers.items(): lname = name.lower() @@ -394,21 +397,24 @@ def payload(self, http_request): body = body.encode('utf-8') return sha256(body).hexdigest() - def canonical_request(self, http_request): + def canonical_request(self, http_request, override_for_cloudsearch=False): cr = [http_request.method.upper()] cr.append(self.canonical_uri(http_request)) cr.append(self.canonical_query_string(http_request)) - headers_to_sign = self.headers_to_sign(http_request) + headers_to_sign = self.headers_to_sign(http_request, override_for_cloudsearch=override_for_cloudsearch) cr.append(self.canonical_headers(headers_to_sign) + '\n') cr.append(self.signed_headers(headers_to_sign)) cr.append(self.payload(http_request)) return '\n'.join(cr) - def scope(self, http_request): + def scope(self, http_request, override_for_cloudsearch=False): scope = [self._provider.access_key] scope.append(http_request.timestamp) scope.append(http_request.region_name) - scope.append(http_request.service_name) + if override_for_cloudsearch: + scope.append('cloudsearch') + else: + scope.append(http_request.service_name) scope.append('aws4_request') return '/'.join(scope) @@ -440,7 +446,7 @@ def determine_service_name(self, host): service_name = parts[0] return service_name - def credential_scope(self, http_request): + def credential_scope(self, http_request, override_for_cloudsearch=False): scope = [] http_request.timestamp = http_request.headers['X-Amz-Date'][0:8] scope.append(http_request.timestamp) @@ -448,7 +454,10 @@ def credential_scope(self, http_request): # * The service_name/region_name attrs or (if these values are None) # * parsed from the endpoint ..amazonaws.com. region_name = self.determine_region_name(http_request.host) - service_name = self.determine_service_name(http_request.host) + if override_for_cloudsearch: + service_name = 'cloudsearch' + else: + service_name = self.determine_service_name(http_request.host) http_request.service_name = service_name http_request.region_name = region_name @@ -457,7 +466,7 @@ def credential_scope(self, http_request): scope.append('aws4_request') return '/'.join(scope) - def string_to_sign(self, http_request, canonical_request): + def string_to_sign(self, http_request, canonical_request, override_for_cloudsearch=False): """ Return the canonical StringToSign as well as a dict containing the original version of all headers that @@ -465,7 +474,7 @@ def string_to_sign(self, http_request, canonical_request): """ sts = ['AWS4-HMAC-SHA256'] sts.append(http_request.headers['X-Amz-Date']) - sts.append(self.credential_scope(http_request)) + sts.append(self.credential_scope(http_request, override_for_cloudsearch=override_for_cloudsearch)) sts.append(sha256(canonical_request.encode('utf-8')).hexdigest()) return '\n'.join(sts) @@ -487,6 +496,9 @@ def add_auth(self, req, **kwargs): """ # This could be a retry. Make sure the previous # authorization header is removed first. + override_for_cloudsearch = False + if 'override_for_cloudsearch' in kwargs: + override_for_cloudsearch = True if 'X-Amzn-Authorization' in req.headers: del req.headers['X-Amzn-Authorization'] now = datetime.datetime.utcnow() @@ -508,9 +520,9 @@ def add_auth(self, req, **kwargs): if qs: # Don't insert the '?' unless there's actually a query string req.path = req.path + '?' + qs - canonical_request = self.canonical_request(req) + canonical_request = self.canonical_request(req, override_for_cloudsearch=override_for_cloudsearch) boto.log.debug('CanonicalRequest:\n%s' % canonical_request) - string_to_sign = self.string_to_sign(req, canonical_request) + string_to_sign = self.string_to_sign(req, canonical_request, override_for_cloudsearch=override_for_cloudsearch) boto.log.debug('StringToSign:\n%s' % string_to_sign) signature = self.signature(req, string_to_sign) boto.log.debug('Signature:\n%s' % signature) diff --git a/boto/cloudsearch2/document.py b/boto/cloudsearch2/document.py index 3b36f13337..80a8995c4c 100644 --- a/boto/cloudsearch2/document.py +++ b/boto/cloudsearch2/document.py @@ -198,8 +198,9 @@ def commit(self): ) session.mount('http://', adapter) session.mount('https://', adapter) + r2 = self.domain.layer1.make_dummy_request_for_cloudsearch2( action=None, path='/'+api_version+'/documents/batch', verb='POST', host=self.endpoint,headers={'Content-Type': 'application/json'}, data=sdf) r = session.post(url, data=sdf, - headers={'Content-Type': 'application/json'}) + headers=r2.headers) return CommitResponse(r, self, sdf) diff --git a/boto/cloudsearch2/search.py b/boto/cloudsearch2/search.py index 634faf85e8..b7def529f5 100644 --- a/boto/cloudsearch2/search.py +++ b/boto/cloudsearch2/search.py @@ -278,7 +278,9 @@ def __call__(self, query): url = "http://%s/%s/search" % (self.endpoint, api_version) params = query.to_params() - r = self.session.get(url, params=params) + r2 = self.domain.layer1.make_dummy_request_for_cloudsearch2( action=None, params=params, path='/'+api_version+'/search', verb='GET', host=self.endpoint) + r = self.session.get(url, params=params, headers = r2.headers) + _body = r.content.decode('utf-8') try: data = json.loads(_body) @@ -294,6 +296,11 @@ def __call__(self, query): raise SearchServiceException('Authentication error from Amazon%s' % msg) raise SearchServiceException("Got non-json response from Amazon. %s" % _body, query) + if '__type' in data and data['__type']=='#AccessDenied': + message = 'Access Denied' + if 'message' in data: + message = data['message'] + raise SearchServiceException(message, '#AccessDenied') if 'messages' in data and 'error' in data: for m in data['messages']: if m['severity'] == 'fatal': diff --git a/boto/connection.py b/boto/connection.py index 40db69a782..1c167fac23 100644 --- a/boto/connection.py +++ b/boto/connection.py @@ -1111,6 +1111,17 @@ def make_request(self, action, params=None, path='/', verb='GET'): http_request.params['Version'] = self.APIVersion return self._mexe(http_request) + def make_dummy_request_for_cloudsearch2(self, action, params=None, path='/', verb='GET', host = None, data='', headers={}): + http_request = self.build_base_http_request(verb, path, None, + params, headers, data, + host) + self._auth_handler.add_auth(req=http_request, override_for_cloudsearch=True) + if action: + http_request.params['Action'] = action + if self.APIVersion: + http_request.params['Version'] = self.APIVersion + return http_request + def build_list_params(self, params, items, label): if isinstance(items, six.string_types): items = [items]