def _validate_signature(self, request, principal, args, params): """Validate the signature.""" creds = AWSCredentials(principal.access_key, principal.secret_key) endpoint = AWSServiceEndpoint() endpoint.set_method(request.method) endpoint.set_canonical_host(request.getHeader("Host")) path = request.path if self.path is not None: path = "%s/%s" % (self.path.rstrip("/"), path.lstrip("/")) endpoint.set_path(path) signature = Signature( creds, endpoint, params, signature_method=args["signature_method"], signature_version=args["signature_version"], ) if signature.compute() != args["signature"]: raise APIError( 403, "SignatureDoesNotMatch", "The request signature we calculated does not " "match the signature you provided. Check your " "key and signing method.", )
class AWSServiceEndpointTestCase(TXAWSTestCase): def setUp(self): self.endpoint = AWSServiceEndpoint(uri="http://my.service/da_endpoint") def test_simple_creation(self): endpoint = AWSServiceEndpoint() self.assertEquals(endpoint.scheme, "http") self.assertEquals(endpoint.host, "") self.assertEquals(endpoint.port, 80) self.assertEquals(endpoint.path, "/") self.assertEquals(endpoint.method, "GET") def test_custom_method(self): endpoint = AWSServiceEndpoint( uri="http://service/endpoint", method="PUT") self.assertEquals(endpoint.method, "PUT") def test_parse_uri(self): self.assertEquals(self.endpoint.scheme, "http") self.assertEquals(self.endpoint.host, "my.service") self.assertEquals(self.endpoint.port, 80) self.assertEquals(self.endpoint.path, "/da_endpoint") def test_parse_uri_https_and_custom_port(self): endpoint = AWSServiceEndpoint(uri="https://my.service:8080/endpoint") self.assertEquals(endpoint.scheme, "https") self.assertEquals(endpoint.host, "my.service") self.assertEquals(endpoint.port, 8080) self.assertEquals(endpoint.path, "/endpoint") def test_get_uri(self): uri = self.endpoint.get_uri() self.assertEquals(uri, "http://my.service/da_endpoint") def test_get_uri_custom_port(self): uri = "https://my.service:8080/endpoint" endpoint = AWSServiceEndpoint(uri=uri) new_uri = endpoint.get_uri() self.assertEquals(new_uri, uri) def test_set_host(self): self.assertEquals(self.endpoint.host, "my.service") self.endpoint.set_host("newhost.com") self.assertEquals(self.endpoint.host, "newhost.com") def test_get_host(self): self.assertEquals(self.endpoint.host, self.endpoint.get_host()) def test_set_path(self): self.endpoint.set_path("/newpath") self.assertEquals( self.endpoint.get_uri(), "http://my.service/newpath") def test_set_method(self): self.assertEquals(self.endpoint.method, "GET") self.endpoint.set_method("PUT") self.assertEquals(self.endpoint.method, "PUT")
class AWSServiceEndpointTestCase(TXAWSTestCase): def setUp(self): self.endpoint = AWSServiceEndpoint(uri="http://my.service/da_endpoint") def test_simple_creation(self): endpoint = AWSServiceEndpoint() self.assertEquals(endpoint.scheme, "http") self.assertEquals(endpoint.host, "") self.assertEquals(endpoint.port, 80) self.assertEquals(endpoint.path, "/") self.assertEquals(endpoint.method, "GET") def test_custom_method(self): endpoint = AWSServiceEndpoint(uri="http://service/endpoint", method="PUT") self.assertEquals(endpoint.method, "PUT") def test_parse_uri(self): self.assertEquals(self.endpoint.scheme, "http") self.assertEquals(self.endpoint.host, "my.service") self.assertEquals(self.endpoint.port, 80) self.assertEquals(self.endpoint.path, "/da_endpoint") def test_parse_uri_https_and_custom_port(self): endpoint = AWSServiceEndpoint(uri="https://my.service:8080/endpoint") self.assertEquals(endpoint.scheme, "https") self.assertEquals(endpoint.host, "my.service") self.assertEquals(endpoint.port, 8080) self.assertEquals(endpoint.path, "/endpoint") def test_get_uri(self): uri = self.endpoint.get_uri() self.assertEquals(uri, "http://my.service/da_endpoint") def test_get_uri_custom_port(self): uri = "https://my.service:8080/endpoint" endpoint = AWSServiceEndpoint(uri=uri) new_uri = endpoint.get_uri() self.assertEquals(new_uri, uri) def test_set_host(self): self.assertEquals(self.endpoint.host, "my.service") self.endpoint.set_host("newhost.com") self.assertEquals(self.endpoint.host, "newhost.com") def test_get_host(self): self.assertEquals(self.endpoint.host, self.endpoint.get_host()) def test_set_path(self): self.endpoint.set_path("/newpath") self.assertEquals(self.endpoint.get_uri(), "http://my.service/newpath") def test_set_method(self): self.assertEquals(self.endpoint.method, "GET") self.endpoint.set_method("PUT") self.assertEquals(self.endpoint.method, "PUT")
def _validate_signature(self, request, principal, args, params): """Validate the signature.""" creds = AWSCredentials(principal.access_key, principal.secret_key) endpoint = AWSServiceEndpoint() endpoint.set_method(request.method) endpoint.set_canonical_host(request.getHeader("Host")) path = request.path if self.path is not None: path = "%s/%s" % (self.path.rstrip("/"), path.lstrip("/")) endpoint.set_path(path) params.pop("Signature") signature = Signature(creds, endpoint, params) if signature.compute() != args.Signature: raise APIError(403, "SignatureDoesNotMatch", "The request signature we calculated does not " "match the signature you provided. Check your " "key and signing method.")
class AWSServiceEndpointTestCase(TestCase): def setUp(self): self.endpoint = AWSServiceEndpoint(uri="http://my.service/da_endpoint") def test_warning_when_verification_disabled(self): """ L{AWSServiceEndpoint} emits a warning when told not to perform certificate verification. """ self.assertWarns( UserWarning, "Operating with certificate verification disabled!", __file__, lambda: AWSServiceEndpoint(ssl_hostname_verification=False), ) def test_simple_creation(self): endpoint = AWSServiceEndpoint() self.assertEquals(endpoint.scheme, "http") self.assertEquals(endpoint.host, "") self.assertEquals(endpoint.port, None) self.assertEquals(endpoint.path, "/") self.assertEquals(endpoint.method, "GET") def test_custom_method(self): endpoint = AWSServiceEndpoint( uri="http://service/endpoint", method="PUT") self.assertEquals(endpoint.method, "PUT") def test_parse_uri(self): self.assertEquals(self.endpoint.scheme, "http") self.assertEquals(self.endpoint.host, "my.service") self.assertIdentical(self.endpoint.port, None) self.assertEquals(self.endpoint.path, "/da_endpoint") def test_parse_uri_https_and_custom_port(self): endpoint = AWSServiceEndpoint(uri="https://my.service:8080/endpoint") self.assertEquals(endpoint.scheme, "https") self.assertEquals(endpoint.host, "my.service") self.assertEquals(endpoint.port, 8080) self.assertEquals(endpoint.path, "/endpoint") def test_get_uri(self): uri = self.endpoint.get_uri() self.assertEquals(uri, "http://my.service/da_endpoint") def test_get_uri_custom_port(self): uri = "https://my.service:8080/endpoint" endpoint = AWSServiceEndpoint(uri=uri) new_uri = endpoint.get_uri() self.assertEquals(new_uri, uri) def test_set_host(self): self.assertEquals(self.endpoint.host, "my.service") self.endpoint.set_host("newhost.com") self.assertEquals(self.endpoint.host, "newhost.com") def test_get_host(self): self.assertEquals(self.endpoint.host, self.endpoint.get_host()) def test_get_canonical_host(self): """ If the port is not specified the canonical host is the same as the host. """ uri = "http://my.service/endpoint" endpoint = AWSServiceEndpoint(uri=uri) self.assertEquals("my.service", endpoint.get_canonical_host()) def test_get_canonical_host_with_non_default_port(self): """ If the port is not the default, the canonical host includes it. """ uri = "http://my.service:99/endpoint" endpoint = AWSServiceEndpoint(uri=uri) self.assertEquals("my.service:99", endpoint.get_canonical_host()) def test_get_canonical_host_is_lower_case(self): """ The canonical host is guaranteed to be lower case. """ uri = "http://MY.SerVice:99/endpoint" endpoint = AWSServiceEndpoint(uri=uri) self.assertEquals("my.service:99", endpoint.get_canonical_host()) def test_set_canonical_host(self): """ The canonical host is converted to lower case. """ endpoint = AWSServiceEndpoint() endpoint.set_canonical_host("My.Service") self.assertEquals("my.service", endpoint.host) self.assertIdentical(None, endpoint.port) def test_set_canonical_host_with_port(self): """ The canonical host can optionally have a port. """ endpoint = AWSServiceEndpoint() endpoint.set_canonical_host("my.service:99") self.assertEquals("my.service", endpoint.host) self.assertEquals(99, endpoint.port) def test_set_canonical_host_with_empty_port(self): """ The canonical host can also have no port. """ endpoint = AWSServiceEndpoint() endpoint.set_canonical_host("my.service:") self.assertEquals("my.service", endpoint.host) self.assertIdentical(None, endpoint.port) def test_set_path(self): self.endpoint.set_path("/newpath") self.assertEquals( self.endpoint.get_uri(), "http://my.service/newpath") def test_set_method(self): self.assertEquals(self.endpoint.method, "GET") self.endpoint.set_method("PUT") self.assertEquals(self.endpoint.method, "PUT")
class Query(BaseQuery): """A query for submission to the S3 service.""" def __init__(self, bucket=None, object_name=None, data="", content_type=None, metadata={}, amz_headers={}, *args, **kwargs): super(Query, self).__init__(*args, **kwargs) self.bucket = bucket self.object_name = object_name self.data = data self.content_type = content_type self.metadata = metadata self.amz_headers = amz_headers self.date = datetimeToString() if not self.endpoint or not self.endpoint.host: self.endpoint = AWSServiceEndpoint(S3_ENDPOINT) self.endpoint.set_method(self.action) def set_content_type(self): """ Set the content type based on the file extension used in the object name. """ if self.object_name and not self.content_type: # XXX nothing is currently done with the encoding... we may # need to in the future self.content_type, encoding = mimetypes.guess_type( self.object_name, strict=False) def get_headers(self): """ Build the list of headers needed in order to perform S3 operations. """ headers = {"Content-Length": len(self.data), "Content-MD5": calculate_md5(self.data), "Date": self.date} for key, value in self.metadata.iteritems(): headers["x-amz-meta-" + key] = value for key, values in self.amz_headers.iteritems(): if isinstance(values, tuple): headers["x-amz-" + key] = ",".join(values) else: headers["x-amz-" + key] = values # Before we check if the content type is set, let's see if we can set # it by guessing the the mimetype. self.set_content_type() if self.content_type is not None: headers["Content-Type"] = self.content_type if self.creds is not None: signature = self.sign(headers) headers["Authorization"] = "AWS %s:%s" % ( self.creds.access_key, signature) return headers def get_canonicalized_amz_headers(self, headers): """ Get the headers defined by Amazon S3. """ headers = [ (name.lower(), value) for name, value in headers.iteritems() if name.lower().startswith("x-amz-")] headers.sort() # XXX missing spec implementation: # txAWS doesn't currently unfold long headers def represent(n, vs): if isinstance(vs, tuple): return "".join(["%s:%s\n" % (n, vs) for v in vs]) else: return "%s:%s\n" % (n, vs) return "".join([represent(name, value) for name, value in headers]) def get_canonicalized_resource(self): """ Get an S3 resource path. """ # As <http://docs.amazonwebservices.com/AmazonS3/latest/dev/RESTAuthentication.html> # says, if there is a subresource (e.g. ?acl), it is included, but other query # parameters (e.g. ?prefix=... in a GET Bucket request) are not included. # Yes, that makes no sense in terms of either security or consistency. resource = self.object_name if resource: q = resource.find('?') if q >= 0: # There can be both a subresource and other parameters, for example # '?versions&prefix=foo'. "You are in a maze of twisty edge cases..." firstparam = resource[q:].partition('&')[0] # includes the initial '?' resource = resource[:q] # strip the query if '=' not in firstparam: resource += firstparam # add back '?subresource' if present path = "/" if self.bucket is not None: path += self.bucket if self.bucket is not None and resource: if not resource.startswith("/"): path += "/" path += resource elif self.bucket is not None and not path.endswith("/"): path += "/" return path def sign(self, headers): """Sign this query using its built in credentials.""" text = (self.action + "\n" + headers.get("Content-MD5", "") + "\n" + headers.get("Content-Type", "") + "\n" + headers.get("Date", "") + "\n" + self.get_canonicalized_amz_headers(headers) + self.get_canonicalized_resource()) return self.creds.sign(text, hash_type="sha1") def submit(self, url_context=None): """Submit this query. @return: A deferred from get_page """ if not url_context: url_context = URLContext( self.endpoint, self.bucket, self.object_name) d = self.get_page( url_context.get_url(), method=self.action, postdata=self.data, headers=self.get_headers()) return d.addErrback(s3_error_wrapper)
class Query(BaseQuery): """A query for submission to the S3 service.""" def __init__(self, bucket=None, object_name=None, data="", content_type=None, metadata={}, amz_headers={}, body_producer=None, *args, **kwargs): super(Query, self).__init__(*args, **kwargs) # data might be None or "", alas. if data and body_producer is not None: raise ValueError("data and body_producer are mutually exclusive.") self.bucket = bucket self.object_name = object_name self.data = data self.body_producer = body_producer self.content_type = content_type self.metadata = metadata self.amz_headers = amz_headers self._date = datetimeToString() if not self.endpoint or not self.endpoint.host: self.endpoint = AWSServiceEndpoint(S3_ENDPOINT) self.endpoint.set_method(self.action) @property def date(self): """ Return the date and emit a deprecation warning. """ warnings.warn("txaws.s3.client.Query.date is a deprecated attribute", DeprecationWarning, stacklevel=2) return self._date @date.setter def date(self, value): """ Set the date. @param value: The new date for this L{Query}. @type value: L{str} """ self._date = value def set_content_type(self): """ Set the content type based on the file extension used in the object name. """ if self.object_name and not self.content_type: # XXX nothing is currently done with the encoding... we may # need to in the future self.content_type, encoding = mimetypes.guess_type( self.object_name, strict=False) def get_headers(self, instant): """ Build the list of headers needed in order to perform S3 operations. """ headers = {'x-amz-date': _auth_v4.makeAMZDate(instant)} if self.body_producer is None: data = self.data if data is None: data = b"" headers["x-amz-content-sha256"] = hashlib.sha256(data).hexdigest() else: data = None headers["x-amz-content-sha256"] = b"UNSIGNED-PAYLOAD" for key, value in self.metadata.iteritems(): headers["x-amz-meta-" + key] = value for key, value in self.amz_headers.iteritems(): headers["x-amz-" + key] = value # Before we check if the content type is set, let's see if we can set # it by guessing the the mimetype. self.set_content_type() if self.content_type is not None: headers["Content-Type"] = self.content_type if self.creds is not None: headers["Authorization"] = self.sign( headers, data, s3_url_context(self.endpoint, self.bucket, self.object_name), instant, method=self.action) return headers def sign(self, headers, data, url_context, instant, method, region=REGION_US_EAST_1): """Sign this query using its built in credentials.""" headers["host"] = url_context.get_encoded_host() if data is None: request = _auth_v4._CanonicalRequest.from_request_components( method=method, url=url_context.get_encoded_path(), headers=headers, headers_to_sign=('host', 'x-amz-date'), payload_hash=None, ) else: request = _auth_v4._CanonicalRequest.from_request_components_and_payload( method=method, url=url_context.get_encoded_path(), headers=headers, headers_to_sign=('host', 'x-amz-date'), payload=data, ) return _auth_v4._make_authorization_header( region=region, service="s3", canonical_request=request, credentials=self.creds, instant=instant) def submit(self, url_context=None, utcnow=datetime.datetime.utcnow): """Submit this query. @return: A deferred from get_page """ if not url_context: url_context = s3_url_context( self.endpoint, self.bucket, self.object_name) d = self.get_page( url_context.get_encoded_url(), method=self.action, postdata=self.data or b"", headers=self.get_headers(utcnow()), ) return d.addErrback(s3_error_wrapper)
class Query(BaseQuery): """A query for submission to the S3 service.""" def __init__(self, bucket=None, object_name=None, data="", content_type=None, metadata={}, amz_headers={}, body_producer=None, *args, **kwargs): super(Query, self).__init__(*args, **kwargs) # data might be None or "", alas. if data and body_producer is not None: raise ValueError("data and body_producer are mutually exclusive.") self.bucket = bucket self.object_name = object_name self.data = data self.body_producer = body_producer self.content_type = content_type self.metadata = metadata self.amz_headers = amz_headers self._date = datetimeToString() if not self.endpoint or not self.endpoint.host: self.endpoint = AWSServiceEndpoint(S3_ENDPOINT) self.endpoint.set_method(self.action) @property def date(self): """ Return the date and emit a deprecation warning. """ warnings.warn("txaws.s3.client.Query.date is a deprecated attribute", DeprecationWarning, stacklevel=2) return self._date @date.setter def date(self, value): """ Set the date. @param value: The new date for this L{Query}. @type value: L{str} """ self._date = value def set_content_type(self): """ Set the content type based on the file extension used in the object name. """ if self.object_name and not self.content_type: # XXX nothing is currently done with the encoding... we may # need to in the future self.content_type, encoding = mimetypes.guess_type( self.object_name, strict=False) def get_headers(self, instant): """ Build the list of headers needed in order to perform S3 operations. """ headers = {'x-amz-date': _auth_v4.makeAMZDate(instant)} if self.body_producer is None: data = self.data if data is None: data = b"" headers["x-amz-content-sha256"] = hashlib.sha256(data).hexdigest() else: data = None headers["x-amz-content-sha256"] = b"UNSIGNED-PAYLOAD" for key, value in self.metadata.items(): headers["x-amz-meta-" + key] = value for key, value in self.amz_headers.items(): headers["x-amz-" + key] = value # Before we check if the content type is set, let's see if we can set # it by guessing the the mimetype. self.set_content_type() if self.content_type is not None: headers["Content-Type"] = self.content_type if self.creds is not None: headers["Authorization"] = self.sign( headers, data, s3_url_context(self.endpoint, self.bucket, self.object_name), instant, method=self.action) return headers def sign(self, headers, data, url_context, instant, method, region=REGION_US_EAST_1): """Sign this query using its built in credentials.""" headers["host"] = url_context.get_encoded_host() if data is None: request = _auth_v4._CanonicalRequest.from_request_components( method=method, url=url_context.get_encoded_path(), headers=headers, headers_to_sign=('host', 'x-amz-date'), payload_hash=None, ) else: request = _auth_v4._CanonicalRequest.from_request_components_and_payload( method=method, url=url_context.get_encoded_path(), headers=headers, headers_to_sign=('host', 'x-amz-date'), payload=data, ) return _auth_v4._make_authorization_header( region=region, service="s3", canonical_request=request, credentials=self.creds, instant=instant) def submit(self, url_context=None, utcnow=datetime.datetime.utcnow): """Submit this query. @return: A deferred from get_page """ if not url_context: url_context = s3_url_context( self.endpoint, self.bucket, self.object_name) d = self.get_page( url_context.get_encoded_url(), method=self.action, postdata=self.data or b"", headers=self.get_headers(utcnow()), ) return d.addErrback(s3_error_wrapper)
class Query(BaseQuery): """A query for submission to the S3 service.""" def __init__(self, bucket=None, object_name=None, data="", content_type=None, metadata={}, amz_headers={}, *args, **kwargs): super(Query, self).__init__(*args, **kwargs) self.bucket = bucket self.object_name = object_name self.data = data self.content_type = content_type self.metadata = metadata self.amz_headers = amz_headers self.date = datetimeToString() if not self.endpoint or not self.endpoint.host: self.endpoint = AWSServiceEndpoint(S3_ENDPOINT) self.endpoint.set_method(self.action) def set_content_type(self): """ Set the content type based on the file extension used in the object name. """ if self.object_name and not self.content_type: # XXX nothing is currently done with the encoding... we may # need to in the future self.content_type, encoding = mimetypes.guess_type( self.object_name, strict=False) def get_headers(self): """ Build the list of headers needed in order to perform S3 operations. """ headers = {"Content-Length": len(self.data), "Content-MD5": calculate_md5(self.data), "Date": self.date} for key, value in self.metadata.iteritems(): headers["x-amz-meta-" + key] = value for key, values in self.amz_headers.iteritems(): if isinstance(values, tuple): headers["x-amz-" + key] = ",".join(values) else: headers["x-amz-" + key] = values # Before we check if the content type is set, let's see if we can set # it by guessing the the mimetype. self.set_content_type() if self.content_type is not None: headers["Content-Type"] = self.content_type if self.creds is not None: signature = self.sign(headers) headers["Authorization"] = "AWS %s:%s" % ( self.creds.access_key, signature) return headers def get_canonicalized_amz_headers(self, headers): """ Get the headers defined by Amazon S3. """ headers = [ (name.lower(), value) for name, value in headers.iteritems() if name.lower().startswith("x-amz-")] headers.sort() # XXX missing spec implementation: # txAWS doesn't currently unfold long headers def represent(n, vs): if isinstance(vs, tuple): return "".join(["%s:%s\n" % (n, vs) for v in vs]) else: return "%s:%s\n" % (n, vs) return "".join([represent(name, value) for name, value in headers]) def get_canonicalized_resource(self): """ Get an S3 resource path. """ path = "/" if self.bucket is not None: path += self.bucket if self.bucket is not None and self.object_name: if not self.object_name.startswith("/"): path += "/" path += self.object_name elif self.bucket is not None and not path.endswith("/"): path += "/" return path def sign(self, headers): """Sign this query using its built in credentials.""" text = (self.action + "\n" + headers.get("Content-MD5", "") + "\n" + headers.get("Content-Type", "") + "\n" + headers.get("Date", "") + "\n" + self.get_canonicalized_amz_headers(headers) + self.get_canonicalized_resource()) return self.creds.sign(text, hash_type="sha1") def submit(self, url_context=None): """Submit this query. @return: A deferred from get_page """ if not url_context: url_context = URLContext( self.endpoint, self.bucket, self.object_name) d = self.get_page( url_context.get_url(), method=self.action, postdata=self.data, headers=self.get_headers()) return d.addErrback(s3_error_wrapper)
class Query(BaseQuery): """A query for submission to the S3 service.""" def __init__(self, bucket=None, object_name=None, data="", content_type=None, metadata={}, amz_headers={}, *args, **kwargs): super(Query, self).__init__(*args, **kwargs) self.bucket = bucket self.object_name = object_name self.data = data self.content_type = content_type self.metadata = metadata self.amz_headers = amz_headers self.date = datetimeToString() if not self.endpoint or not self.endpoint.host: self.endpoint = AWSServiceEndpoint(S3_ENDPOINT) self.endpoint.set_method(self.action) def set_content_type(self): """ Set the content type based on the file extension used in the object name. """ if self.object_name and not self.content_type: # XXX nothing is currently done with the encoding... we may # need to in the future self.content_type, encoding = mimetypes.guess_type( self.object_name, strict=False) def get_headers(self): """ Build the list of headers needed in order to perform S3 operations. """ headers = {"Content-Length": len(self.data), "Content-MD5": calculate_md5(self.data), "Date": self.date} for key, value in self.metadata.iteritems(): headers["x-amz-meta-" + key] = value for key, value in self.amz_headers.iteritems(): headers["x-amz-" + key] = value # Before we check if the content type is set, let's see if we can set # it by guessing the the mimetype. self.set_content_type() if self.content_type is not None: headers["Content-Type"] = self.content_type if self.creds is not None: signature = self.sign(headers) headers["Authorization"] = "AWS %s:%s" % ( self.creds.access_key, signature) return headers def get_canonicalized_amz_headers(self, headers): """ Get the headers defined by Amazon S3. """ headers = [ (name.lower(), value) for name, value in headers.iteritems() if name.lower().startswith("x-amz-")] headers.sort() # XXX missing spec implementation: # 1) txAWS doesn't currently combine headers with the same name # 2) txAWS doesn't currently unfold long headers return "".join("%s:%s\n" % (name, value) for name, value in headers) def get_canonicalized_resource(self): """ Get an S3 resource path. """ path = "/" if self.bucket is not None: path += self.bucket if self.bucket is not None and self.object_name: if not self.object_name.startswith("/"): path += "/" path += self.object_name elif self.bucket is not None and not path.endswith("/"): path += "/" return path def sign(self, headers): """Sign this query using its built in credentials.""" text = (self.action + "\n" + headers.get("Content-MD5", "") + "\n" + headers.get("Content-Type", "") + "\n" + headers.get("Date", "") + "\n" + self.get_canonicalized_amz_headers(headers) + self.get_canonicalized_resource()) return self.creds.sign(text, hash_type="sha1") def submit(self, url_context=None): """Submit this query. @return: A deferred from get_page """ if not url_context: url_context = URLContext( self.endpoint, self.bucket, self.object_name) d = self.get_page( url_context.get_url(), method=self.action, postdata=self.data, headers=self.get_headers()) return d.addErrback(s3_error_wrapper)
class Query(BaseQuery): """A query for submission to the S3 service.""" def __init__(self, bucket=None, object_name=None, data="", content_type=None, metadata={}, amz_headers={}, *args, **kwargs): super(Query, self).__init__(*args, **kwargs) self.bucket = bucket self.object_name = object_name self.data = data self.content_type = content_type self.metadata = metadata self.amz_headers = amz_headers self.date = datetimeToString() if not self.endpoint or not self.endpoint.host: self.endpoint = AWSServiceEndpoint(S3_ENDPOINT) self.endpoint.set_method(self.action) def set_content_type(self): """ Set the content type based on the file extension used in the object name. """ if self.object_name and not self.content_type: # XXX nothing is currently done with the encoding... we may # need to in the future self.content_type, encoding = mimetypes.guess_type( self.object_name, strict=False) def get_headers(self): """ Build the list of headers needed in order to perform S3 operations. """ headers = { "Content-Length": len(self.data), "Content-MD5": calculate_md5(self.data), "Date": self.date } for key, value in self.metadata.iteritems(): headers["x-amz-meta-" + key] = value for key, values in self.amz_headers.iteritems(): if isinstance(values, tuple): headers["x-amz-" + key] = ",".join(values) else: headers["x-amz-" + key] = values # Before we check if the content type is set, let's see if we can set # it by guessing the the mimetype. self.set_content_type() if self.content_type is not None: headers["Content-Type"] = self.content_type if self.creds is not None: signature = self.sign(headers) headers["Authorization"] = "AWS %s:%s" % (self.creds.access_key, signature) return headers def get_canonicalized_amz_headers(self, headers): """ Get the headers defined by Amazon S3. """ headers = [(name.lower(), value) for name, value in headers.iteritems() if name.lower().startswith("x-amz-")] headers.sort() # XXX missing spec implementation: # txAWS doesn't currently unfold long headers def represent(n, vs): if isinstance(vs, tuple): return "".join(["%s:%s\n" % (n, vs) for v in vs]) else: return "%s:%s\n" % (n, vs) return "".join([represent(name, value) for name, value in headers]) def get_canonicalized_resource(self): """ Get an S3 resource path. """ # As <http://docs.amazonwebservices.com/AmazonS3/latest/dev/RESTAuthentication.html> # says, if there is a subresource (e.g. ?acl), it is included, but other query # parameters (e.g. ?prefix=... in a GET Bucket request) are not included. # Yes, that makes no sense in terms of either security or consistency. resource = self.object_name if resource: q = resource.find('?') if q >= 0: # There can be both a subresource and other parameters, for example # '?versions&prefix=foo'. "You are in a maze of twisty edge cases..." firstparam = resource[q:].partition('&')[ 0] # includes the initial '?' resource = resource[:q] # strip the query if '=' not in firstparam: resource += firstparam # add back '?subresource' if present path = "/" if self.bucket is not None: path += self.bucket if self.bucket is not None and resource: if not resource.startswith("/"): path += "/" path += resource elif self.bucket is not None and not path.endswith("/"): path += "/" return path def sign(self, headers): """Sign this query using its built in credentials.""" text = (self.action + "\n" + headers.get("Content-MD5", "") + "\n" + headers.get("Content-Type", "") + "\n" + headers.get("Date", "") + "\n" + self.get_canonicalized_amz_headers(headers) + self.get_canonicalized_resource()) return self.creds.sign(text, hash_type="sha1") def submit(self, url_context=None): """Submit this query. @return: A deferred from get_page """ if not url_context: url_context = URLContext(self.endpoint, self.bucket, self.object_name) d = self.get_page(url_context.get_url(), method=self.action, postdata=self.data, headers=self.get_headers()) return d.addErrback(s3_error_wrapper)