Validate echoed client request ID
This commit is contained in:
Родитель
64c61f3dd9
Коммит
219d85e81b
|
@ -8,7 +8,7 @@ __author__ = 'Microsoft Corp. <ptvshelp@microsoft.com>'
|
|||
__version__ = '2.0.1'
|
||||
|
||||
# x-ms-version for storage service.
|
||||
X_MS_VERSION = '2018-11-09'
|
||||
X_MS_VERSION = '2019-02-02'
|
||||
|
||||
# internal configurations, should not be changed
|
||||
_LARGE_BLOB_UPLOAD_MAX_READ_BUFFER_SIZE = 4 * 1024 * 1024
|
||||
|
|
|
@ -2,10 +2,17 @@
|
|||
|
||||
> See [BreakingChanges](BreakingChanges.md) for a detailed list of API breaks.
|
||||
|
||||
## Version XX.XX.XX:
|
||||
|
||||
- Support for 2019-02-02 REST version. Please see our REST API documentation and blog for information about the related added features.
|
||||
- Validate that the echoed client request ID from the service matches the sent one.
|
||||
|
||||
## Version 2.0.0:
|
||||
|
||||
- Bump version to avoid breaking file/blob/queue v1.5.0.
|
||||
|
||||
## Version 1.4.1:
|
||||
|
||||
- Added minor helpers for SAS related changes
|
||||
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ USER_AGENT_STRING_SUFFIX = '(Python {} {}; {} {})'.format(platform.python_implem
|
|||
platform.release())
|
||||
|
||||
# default values for common package, in case it is used directly
|
||||
DEFAULT_X_MS_VERSION = '2018-03-28'
|
||||
DEFAULT_X_MS_VERSION = '2019-02-02'
|
||||
DEFAULT_USER_AGENT_STRING = '{}None {}'.format(USER_AGENT_STRING_PREFIX, USER_AGENT_STRING_SUFFIX)
|
||||
|
||||
# Live ServiceClient URLs
|
||||
|
@ -49,3 +49,4 @@ _ENCRYPTION_PROTOCOL_V1 = '1.0'
|
|||
_AUTHORIZATION_HEADER_NAME = 'Authorization'
|
||||
_COPY_SOURCE_HEADER_NAME = 'x-ms-copy-source'
|
||||
_REDACTED_VALUE = 'REDACTED'
|
||||
_CLIENT_REQUEST_ID_HEADER_NAME = 'x-ms-client-request-id'
|
||||
|
|
|
@ -34,6 +34,7 @@ from .models import (
|
|||
from ._common_conversion import (
|
||||
_str,
|
||||
)
|
||||
from ._constants import _CLIENT_REQUEST_ID_HEADER_NAME
|
||||
|
||||
|
||||
def _to_utc_datetime(value):
|
||||
|
@ -62,7 +63,7 @@ def _update_request(request, x_ms_version, user_agent_string):
|
|||
# append addtional headers based on the service
|
||||
request.headers['x-ms-version'] = x_ms_version
|
||||
request.headers['User-Agent'] = user_agent_string
|
||||
request.headers['x-ms-client-request-id'] = str(uuid.uuid1())
|
||||
request.headers[_CLIENT_REQUEST_ID_HEADER_NAME] = str(uuid.uuid1())
|
||||
|
||||
# If the host has a path component (ex local storage), move it
|
||||
path = request.host.split('/', 1)
|
||||
|
|
|
@ -24,6 +24,7 @@ from ._constants import (
|
|||
_AUTHORIZATION_HEADER_NAME,
|
||||
_REDACTED_VALUE,
|
||||
_COPY_SOURCE_HEADER_NAME,
|
||||
_CLIENT_REQUEST_ID_HEADER_NAME,
|
||||
)
|
||||
from ._error import (
|
||||
_ERROR_DECRYPTION_FAILURE,
|
||||
|
@ -260,6 +261,16 @@ class StorageClient(object):
|
|||
clean_queries[_QueryStringConstants.SIGNED_SIGNATURE] = _REDACTED_VALUE
|
||||
return clean_queries
|
||||
|
||||
@staticmethod
|
||||
def _validate_echoed_client_request_id(request, response):
|
||||
# raise exception if the echoed client request id from the service is not identical to the one we sent
|
||||
if _CLIENT_REQUEST_ID_HEADER_NAME in response.headers and \
|
||||
request.headers[_CLIENT_REQUEST_ID_HEADER_NAME] != response.headers[_CLIENT_REQUEST_ID_HEADER_NAME]:
|
||||
raise AzureException(
|
||||
"Echoed client request ID: {} does not match sent client request ID: {}. Service request ID: {}".format(
|
||||
response.headers[_CLIENT_REQUEST_ID_HEADER_NAME], request.headers[_CLIENT_REQUEST_ID_HEADER_NAME],
|
||||
response.headers['x-ms-request-id']))
|
||||
|
||||
def _perform_request(self, request, parser=None, parser_args=None, operation_context=None, expected_errors=None):
|
||||
'''
|
||||
Sends the request and return response. Catches HTTPError and hands it
|
||||
|
@ -282,7 +293,7 @@ class StorageClient(object):
|
|||
|
||||
# Apply common settings to the request
|
||||
_update_request(request, self._X_MS_VERSION, self._USER_AGENT_STRING)
|
||||
client_request_id_prefix = str.format("Client-Request-ID={0}", request.headers['x-ms-client-request-id'])
|
||||
client_request_id_prefix = str.format("Client-Request-ID={0}", request.headers[_CLIENT_REQUEST_ID_HEADER_NAME])
|
||||
|
||||
while True:
|
||||
try:
|
||||
|
@ -324,6 +335,9 @@ class StorageClient(object):
|
|||
if self.response_callback:
|
||||
self.response_callback(response)
|
||||
|
||||
# Validate the client request ID
|
||||
self._validate_echoed_client_request_id(request, response)
|
||||
|
||||
# Set the response context
|
||||
retry_context.response = response
|
||||
|
||||
|
|
|
@ -16,7 +16,9 @@ from tests.testcase import (
|
|||
StorageTestCase,
|
||||
record,
|
||||
)
|
||||
from azure.storage.common import TokenCredential
|
||||
from azure.storage.common import TokenCredential, ExponentialRetry
|
||||
from azure.storage.common._constants import _CLIENT_REQUEST_ID_HEADER_NAME
|
||||
from azure.common import AzureException
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
SERVICES = {
|
||||
|
@ -389,6 +391,34 @@ class StorageClientTest(StorageTestCase):
|
|||
exists = service.exists(name)
|
||||
self.assertTrue(exists)
|
||||
|
||||
@record
|
||||
def test_client_request_id_echo(self):
|
||||
# Arrange
|
||||
service = BlockBlobService(self.account_name, self.account_key, is_emulated=self.settings.IS_EMULATED)
|
||||
service.retry = ExponentialRetry(max_attempts=1, initial_backoff=1,).retry
|
||||
name = self.get_resource_name('cont')
|
||||
|
||||
# Act make the client request ID slightly different
|
||||
def callback(response):
|
||||
response.status = 200
|
||||
response.headers[_CLIENT_REQUEST_ID_HEADER_NAME] += '1'
|
||||
|
||||
service.response_callback = callback
|
||||
|
||||
# Assert the client request ID validation is working
|
||||
with self.assertRaises(AzureException):
|
||||
service.exists(name)
|
||||
|
||||
# Act remove the echoed client request ID
|
||||
def callback(response):
|
||||
response.status = 200
|
||||
del response.headers[_CLIENT_REQUEST_ID_HEADER_NAME]
|
||||
|
||||
service.response_callback = callback
|
||||
|
||||
# Assert the client request ID validation is not throwing when the ID is not echoed
|
||||
service.exists(name)
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
interactions:
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Connection: [keep-alive]
|
||||
User-Agent: [Azure-Storage/2.0.0-2.0.1 (Python CPython 3.7.0; Darwin 18.6.0)]
|
||||
x-ms-client-request-id: [b2d411ec-8c17-11e9-9ed6-acde48001122]
|
||||
x-ms-date: ['Tue, 11 Jun 2019 07:08:22 GMT']
|
||||
x-ms-version: ['2019-02-02']
|
||||
method: GET
|
||||
uri: https://storagename.blob.core.windows.net/cont40380ffd?restype=container
|
||||
response:
|
||||
body: {string: "\uFEFF<?xml version=\"1.0\" encoding=\"utf-8\"?><Error><Code>ContainerNotFound</Code><Message>The\
|
||||
\ specified container does not exist.\nRequestId:9059bb6e-f01e-0002-3d24-202c83000000\n\
|
||||
Time:2019-06-11T07:08:22.8572973Z</Message></Error>"}
|
||||
headers:
|
||||
Content-Length: ['225']
|
||||
Content-Type: [application/xml]
|
||||
Date: ['Tue, 11 Jun 2019 07:08:22 GMT']
|
||||
Server: [Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0]
|
||||
x-ms-client-request-id: [b2d411ec-8c17-11e9-9ed6-acde48001122]
|
||||
x-ms-error-code: [ContainerNotFound]
|
||||
x-ms-request-id: [9059bb6e-f01e-0002-3d24-202c83000000]
|
||||
x-ms-version: ['2019-02-02']
|
||||
status: {code: 404, message: The specified container does not exist.}
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Connection: [keep-alive]
|
||||
User-Agent: [Azure-Storage/2.0.0-2.0.1 (Python CPython 3.7.0; Darwin 18.6.0)]
|
||||
x-ms-client-request-id: [b2d411ec-8c17-11e9-9ed6-acde48001122]
|
||||
x-ms-date: ['Tue, 11 Jun 2019 07:08:26 GMT']
|
||||
x-ms-version: ['2019-02-02']
|
||||
method: GET
|
||||
uri: https://storagename.blob.core.windows.net/cont40380ffd?restype=container
|
||||
response:
|
||||
body: {string: "\uFEFF<?xml version=\"1.0\" encoding=\"utf-8\"?><Error><Code>ContainerNotFound</Code><Message>The\
|
||||
\ specified container does not exist.\nRequestId:9059bc96-f01e-0002-5824-202c83000000\n\
|
||||
Time:2019-06-11T07:08:26.7803058Z</Message></Error>"}
|
||||
headers:
|
||||
Content-Length: ['225']
|
||||
Content-Type: [application/xml]
|
||||
Date: ['Tue, 11 Jun 2019 07:08:25 GMT']
|
||||
Server: [Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0]
|
||||
x-ms-client-request-id: [b2d411ec-8c17-11e9-9ed6-acde48001122]
|
||||
x-ms-error-code: [ContainerNotFound]
|
||||
x-ms-request-id: [9059bc96-f01e-0002-5824-202c83000000]
|
||||
x-ms-version: ['2019-02-02']
|
||||
status: {code: 404, message: The specified container does not exist.}
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Connection: [keep-alive]
|
||||
User-Agent: [Azure-Storage/2.0.0-2.0.1 (Python CPython 3.7.0; Darwin 18.6.0)]
|
||||
x-ms-client-request-id: [b57a4e2a-8c17-11e9-9ed6-acde48001122]
|
||||
x-ms-date: ['Tue, 11 Jun 2019 07:08:26 GMT']
|
||||
x-ms-version: ['2019-02-02']
|
||||
method: GET
|
||||
uri: https://storagename.blob.core.windows.net/cont40380ffd?restype=container
|
||||
response:
|
||||
body: {string: "\uFEFF<?xml version=\"1.0\" encoding=\"utf-8\"?><Error><Code>ContainerNotFound</Code><Message>The\
|
||||
\ specified container does not exist.\nRequestId:9059bca5-f01e-0002-6424-202c83000000\n\
|
||||
Time:2019-06-11T07:08:26.8883049Z</Message></Error>"}
|
||||
headers:
|
||||
Content-Length: ['225']
|
||||
Content-Type: [application/xml]
|
||||
Date: ['Tue, 11 Jun 2019 07:08:26 GMT']
|
||||
Server: [Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0]
|
||||
x-ms-client-request-id: [b57a4e2a-8c17-11e9-9ed6-acde48001122]
|
||||
x-ms-error-code: [ContainerNotFound]
|
||||
x-ms-request-id: [9059bca5-f01e-0002-6424-202c83000000]
|
||||
x-ms-version: ['2019-02-02']
|
||||
status: {code: 404, message: The specified container does not exist.}
|
||||
version: 1
|
Загрузка…
Ссылка в новой задаче