From 30b4c8215409e4aeb1d7340a4f38a9b4d8cce774 Mon Sep 17 00:00:00 2001 From: huguesv Date: Tue, 19 Mar 2013 14:43:12 -0700 Subject: [PATCH 1/5] Fix crash on x64 Python on Windows --- src/azure/http/winhttp.py | 145 ++++++++++++++++++++++---------------- 1 file changed, 86 insertions(+), 59 deletions(-) diff --git a/src/azure/http/winhttp.py b/src/azure/http/winhttp.py index 6c67ef69a3a4..5ace9e635e2b 100644 --- a/src/azure/http/winhttp.py +++ b/src/azure/http/winhttp.py @@ -54,12 +54,27 @@ _SysFreeString = _oleaut32.SysFreeString _SysFreeString.argtypes = [c_void_p] -_CoTaskMemAlloc = _ole32.CoTaskMemAlloc -_CoTaskMemAlloc.restype = c_void_p -_CoTaskMemAlloc.argtypes = [c_size_t] +#SAFEARRAY* +#SafeArrayCreateVector(_In_ VARTYPE vt,_In_ LONG lLbound,_In_ ULONG cElements); +_SafeArrayCreateVector = _oleaut32.SafeArrayCreateVector +_SafeArrayCreateVector.restype = c_void_p +_SafeArrayCreateVector.argtypes = [c_ushort, c_long, c_ulong] + +#HRESULT +#SafeArrayAccessData(_In_ SAFEARRAY *psa, _Out_ void **ppvData); +_SafeArrayAccessData = _oleaut32.SafeArrayAccessData +_SafeArrayAccessData.argtypes = [c_void_p, POINTER(c_void_p)] + +#HRESULT +#SafeArrayUnaccessData(_In_ SAFEARRAY *psa); +_SafeArrayUnaccessData = _oleaut32.SafeArrayUnaccessData +_SafeArrayUnaccessData.argtypes = [c_void_p] + +#HRESULT +#SafeArrayGetUBound(_In_ SAFEARRAY *psa, _In_ UINT nDim, _Out_ LONG *plUbound); +_SafeArrayGetUBound = _oleaut32.SafeArrayGetUBound +_SafeArrayGetUBound.argtypes = [c_void_p, c_ulong, POINTER(c_long)] -_CoTaskMemFree = _ole32.CoTaskMemFree -_CoTaskMemFree.argtypes = [c_void_p] #------------------------------------------------------------------------------ @@ -72,26 +87,6 @@ def __init__(self, value): def __del__(self): _SysFreeString(self) -class _tagSAFEARRAY(Structure): - ''' - SAFEARRAY structure in python. Does not match the definition in - MSDN exactly & it is only mapping the used fields. Field names are also - slighty different. - ''' - - class _tagSAFEARRAYBOUND(Structure): - _fields_ = [('c_elements', c_ulong), ('l_lbound', c_long)] - - _fields_ = [('c_dims', c_ushort), - ('f_features', c_ushort), - ('cb_elements', c_ulong), - ('c_locks', c_ulong), - ('pvdata', c_void_p), - ('rgsabound', _tagSAFEARRAYBOUND*1)] - - def __del__(self): - _CoTaskMemFree(self.pvdata) - class VARIANT(Structure): ''' VARIANT structure in python. Does not match the definition in @@ -110,7 +105,7 @@ class _tagRecord(Structure): ('ival', c_short), ('boolval', c_ushort), ('bstrval', BSTR), - ('parray', POINTER(_tagSAFEARRAY)), + ('parray', c_void_p), ('record', _tagRecord)] _fields_ = [('vt', c_ushort), @@ -119,6 +114,63 @@ class _tagRecord(Structure): ('wReserved3', c_ushort), ('vdata', _tagData)] + @staticmethod + def create_empty(): + variant = VARIANT() + variant.vt = VT_EMPTY + variant.vdata.llval = 0 + return variant + + @staticmethod + def create_safearray_from_str(text): + variant = VARIANT() + variant.vt = VT_ARRAY | VT_UI1 + + length = len(text) + variant.vdata.parray = _SafeArrayCreateVector(VT_UI1, 0, length) + pvdata = c_void_p() + _SafeArrayAccessData(variant.vdata.parray, byref(pvdata)) + ctypes.memmove(pvdata, text, length) + _SafeArrayUnaccessData(variant.vdata.parray) + + return variant + + @staticmethod + def create_bstr_from_str(text): + variant = VARIANT() + variant.vt = VT_BSTR + variant.vdata.bstrval = BSTR(text) + return variant + + @staticmethod + def create_bool_false(): + variant = VARIANT() + variant.vt = VT_BOOL + variant.vdata.boolval = 0 + return variant + + def is_safearray_of_bytes(self): + return self.vt == VT_ARRAY | VT_UI1 + + def str_from_safearray(self): + assert self.vt == VT_ARRAY | VT_UI1 + pvdata = c_void_p() + count = c_long() + _SafeArrayGetUBound(self.vdata.parray, 1, byref(count)) + count = c_long(count.value + 1) + _SafeArrayAccessData(self.vdata.parray, byref(pvdata)) + text = ctypes.string_at(pvdata, count) + _SafeArrayUnaccessData(self.vdata.parray) + return text + + def __del__(self): + _VariantClear(self) + +#HRESULT VariantClear(_Inout_ VARIANTARG *pvarg); +_VariantClear = _oleaut32.VariantClear +_VariantClear.argtypes = [POINTER(VARIANT)] + + class GUID(Structure): ''' GUID structure in python. ''' @@ -165,10 +217,7 @@ def open(self, method, url): ''' _WinHttpRequest._SetTimeouts(self, 0, 65000, 65000, 65000) - flag = VARIANT() - flag.vt = VT_BOOL - flag.vdata.boolval = 0 - + flag = VARIANT.create_bool_false() _method = BSTR(method) _url = BSTR(url) _WinHttpRequest._Open(self, _method, _url, flag) @@ -195,24 +244,11 @@ def send(self, request = None): # Sends VT_EMPTY if it is GET, HEAD request. if request is None: - var_empty = VARIANT() - var_empty.vt = VT_EMPTY - var_empty.vdata.llval = 0 + var_empty = VARIANT.create_empty() _WinHttpRequest._Send(self, var_empty) else: # Sends request body as SAFEArray. - _request = VARIANT() - _request.vt = VT_ARRAY | VT_UI1 - safearray = _tagSAFEARRAY() - safearray.c_dims = 1 - safearray.cb_elements = 1 - safearray.c_locks = 0 - safearray.f_features = 128 - safearray.rgsabound[0].c_elements = len(request) - safearray.rgsabound[0].l_lbound = 0 - safearray.pvdata = cast(_CoTaskMemAlloc(len(request)), c_void_p) - ctypes.memmove(safearray.pvdata, request, len(request)) - _request.vdata.parray = cast(byref(safearray), POINTER(_tagSAFEARRAY)) - _WinHttpRequest._Send(self, _request) + _request = VARIANT.create_safearray_from_str(request) + _WinHttpRequest._Send(self, _request) def status(self): ''' Gets status of response. ''' @@ -238,10 +274,8 @@ def response_body(self): ''' var_respbody = VARIANT() _WinHttpRequest._ResponseBody(self, byref(var_respbody)) - if var_respbody.vt == VT_ARRAY | VT_UI1: - safearray = var_respbody.vdata.parray.contents - respbody = ctypes.string_at(safearray.pvdata, safearray.rgsabound[0].c_elements) - + if var_respbody.is_safearray_of_bytes(): + respbody = var_respbody.str_from_safearray() if respbody[3:].startswith(' Date: Wed, 20 Mar 2013 11:14:24 -0700 Subject: [PATCH 2/5] - Fixes for shared access signature for azure storage containers/blobs: wrong constants, quoting of the signature in the url. - Added tests that verify that an anonymous 'GET' on the blob url that has the shared access signature actually works. --- src/azure/storage/__init__.py | 8 +- src/azure/storage/sharedaccesssignature.py | 19 ++-- test/azuretest/test_blobservice.py | 98 +++++++++++++++++++- test/azuretest/test_sharedaccesssignature.py | 24 +++-- 4 files changed, 117 insertions(+), 32 deletions(-) diff --git a/src/azure/storage/__init__.py b/src/azure/storage/__init__.py index fd4a33d0e21e..c022b52b06da 100644 --- a/src/azure/storage/__init__.py +++ b/src/azure/storage/__init__.py @@ -123,10 +123,10 @@ def __init__(self): class AccessPolicy(WindowsAzureData): ''' Access Policy class in service properties. ''' - def __init__(self): - self.start = u'' - self.expiry = u'' - self.permission = u'' + def __init__(self, start=u'', expiry=u'', permission='u'): + self.start = start + self.expiry = expiry + self.permission = permission class SignedIdentifier(WindowsAzureData): ''' Signed Identifier class for service properties. ''' diff --git a/src/azure/storage/sharedaccesssignature.py b/src/azure/storage/sharedaccesssignature.py index c80ce63ba59d..e860f805edba 100644 --- a/src/azure/storage/sharedaccesssignature.py +++ b/src/azure/storage/sharedaccesssignature.py @@ -15,6 +15,7 @@ import base64 import hmac import hashlib +import urllib2 #------------------------------------------------------------------------- # Constants for the share access signature @@ -24,8 +25,8 @@ SIGNED_PERMISSION = 'sp' SIGNED_IDENTIFIER = 'si' SIGNED_SIGNATURE = 'sig' -RESOURCE_BLOB = 'blob' -RESOURCE_CONTAINER = 'container' +RESOURCE_BLOB = 'b' +RESOURCE_CONTAINER = 'c' SIGNED_RESOURCE_TYPE = 'resource' SHARED_ACCESS_PERMISSION = 'permission' @@ -126,11 +127,11 @@ def _convert_query_string(self, query_string): convert_str += SIGNED_START + '=' + query_string[SIGNED_START] + '&' convert_str += SIGNED_EXPIRY + '=' + query_string[SIGNED_EXPIRY] + '&' convert_str += SIGNED_PERMISSION + '=' + query_string[SIGNED_PERMISSION] + '&' - convert_str += SIGNED_RESOURCE_TYPE + '=' + query_string[SIGNED_RESOURCE] + '&' + convert_str += SIGNED_RESOURCE + '=' + query_string[SIGNED_RESOURCE] + '&' if query_string.has_key(SIGNED_IDENTIFIER): convert_str += SIGNED_IDENTIFIER + '=' + query_string[SIGNED_IDENTIFIER] + '&' - convert_str += SIGNED_SIGNATURE + '=' + query_string[SIGNED_SIGNATURE] + '&' + convert_str += SIGNED_SIGNATURE + '=' + urllib2.quote(query_string[SIGNED_SIGNATURE]) + '&' return convert_str def _generate_signature(self, path, resource_type, shared_access_policy): @@ -150,7 +151,7 @@ def get_value_to_append(value, no_new_line=False): canonicalized_resource = '/' + self.account_name + path; #form the string to sign from shared_access_policy and canonicalized resource. - #The order of values is import. + #The order of values is important. string_to_sign = (get_value_to_append(shared_access_policy.access_policy.permission) + get_value_to_append(shared_access_policy.access_policy.start) + get_value_to_append(shared_access_policy.access_policy.expiry) + @@ -180,11 +181,3 @@ def _sign(self, string_to_sign): decode_account_key = base64.b64decode(self.account_key) signed_hmac_sha256 = hmac.HMAC(decode_account_key, string_to_sign, hashlib.sha256) return base64.b64encode(signed_hmac_sha256.digest()) - - - - - - - - diff --git a/test/azuretest/test_blobservice.py b/test/azuretest/test_blobservice.py index e298d1cb3846..a1fb58c2e857 100644 --- a/test/azuretest/test_blobservice.py +++ b/test/azuretest/test_blobservice.py @@ -16,11 +16,27 @@ from azure.storage.blobservice import * from azure.storage import Metrics from azure.storage.storageclient import AZURE_STORAGE_ACCESS_KEY, AZURE_STORAGE_ACCOUNT, EMULATED, DEV_ACCOUNT_NAME, DEV_ACCOUNT_KEY -from azure import WindowsAzureError +from azure.storage.sharedaccesssignature import (SharedAccessSignature, + SharedAccessPolicy, + Permission, + WebResource, + SIGNED_START, + SIGNED_EXPIRY, + SIGNED_RESOURCE, + SIGNED_PERMISSION, + SIGNED_IDENTIFIER, + SIGNED_SIGNATURE, + RESOURCE_BLOB, + RESOURCE_CONTAINER, + SIGNED_RESOURCE_TYPE, + SHARED_ACCESS_PERMISSION) +from azure import WindowsAzureError, BLOB_SERVICE_HOST_BASE from azuretest.util import * from azure.http import HTTPRequest, HTTPResponse - +import datetime import unittest +import httplib +import time #------------------------------------------------------------------------------ class BlobServiceTest(AzureTestCase): @@ -69,6 +85,46 @@ def _create_container_and_page_blob(self, container_name, blob_name, content_len resp = self.bc.put_blob(self.container_name, blob_name, '', 'PageBlob', x_ms_blob_content_length=str(content_length)) self.assertIsNone(resp) + def _get_permission(self, sas, resource_type, resource_path): + date_format = "%Y-%m-%dT%H:%M:%SZ" + start = datetime.datetime.utcnow() - datetime.timedelta(minutes=1) + expiry = start + datetime.timedelta(hours=1) + + sap = SharedAccessPolicy(AccessPolicy(start.strftime(date_format), + expiry.strftime(date_format), + 'r')) + + signed_query = sas.generate_signed_query_string(resource_path, + resource_type, + sap) + + return Permission('/' + resource_path, signed_query) + + def _get_signed_web_resource(self, sas, resource_type, resource_path): + web_rsrc = WebResource() + web_rsrc.properties[SIGNED_RESOURCE_TYPE] = resource_type + web_rsrc.properties[SHARED_ACCESS_PERMISSION] = 'r' + web_rsrc.path = '/' + resource_path + web_rsrc.request_url = '/' + resource_path + + return sas.sign_request(web_rsrc) + + def _get_request(self, host, url): + connection = httplib.HTTPConnection(host) + connection.putrequest('GET', url) + connection.putheader('Content-Type', 'application/octet-stream Charset=UTF-8') + connection.endheaders() + + resp = connection.getresponse() + resp.getheaders() + respbody = None + if resp.length is None: + respbody = resp.read() + elif resp.length > 0: + respbody = resp.read(resp.length) + + return respbody + #--Test cases for blob service -------------------------------------------- def test_create_blob_service_missing_arguments(self): # Arrange @@ -1326,6 +1382,44 @@ def test_unicode_get_blob_binary_data(self): self.assertIsInstance(blob, BlobResult) self.assertEquals(blob, binary_data) + def test_shared_access_blob(self): + # Arrange + data = 'hello blob via blob permission' + self._create_container_and_block_blob(self.container_name, 'blob1.txt', data) + sas = SharedAccessSignature(credentials.getStorageServicesName(), + credentials.getStorageServicesKey()) + res_path = self.container_name + '/blob1.txt' + res_type = RESOURCE_BLOB + + # Act + sas.permission_set = [self._get_permission(sas, res_type, res_path)] + web_rsrc = self._get_signed_web_resource(sas, res_type, res_path) + + # Assert + host = credentials.getStorageServicesName() + BLOB_SERVICE_HOST_BASE + url = web_rsrc.request_url + respbody = self._get_request(host, url) + self.assertEquals(data, respbody) + + def test_shared_access_container(self): + # Arrange + data = 'hello blob via container permission' + self._create_container_and_block_blob(self.container_name, 'blob1.txt', data) + sas = SharedAccessSignature(credentials.getStorageServicesName(), + credentials.getStorageServicesKey()) + res_path = self.container_name + res_type = RESOURCE_CONTAINER + + # Act + sas.permission_set = [self._get_permission(sas, res_type, res_path)] + web_rsrc = self._get_signed_web_resource(sas, res_type, res_path + '/blob1.txt') + + # Assert + host = credentials.getStorageServicesName() + BLOB_SERVICE_HOST_BASE + url = web_rsrc.request_url + respbody = self._get_request(host, url) + self.assertEquals(data, respbody) + #------------------------------------------------------------------------------ if __name__ == '__main__': unittest.main() diff --git a/test/azuretest/test_sharedaccesssignature.py b/test/azuretest/test_sharedaccesssignature.py index 76c40cf7e900..c73eb0fa832b 100644 --- a/test/azuretest/test_sharedaccesssignature.py +++ b/test/azuretest/test_sharedaccesssignature.py @@ -17,24 +17,22 @@ from azure.storage.sharedaccesssignature import (SharedAccessSignature, SharedAccessPolicy, Permission, - WebResource) + WebResource, + SIGNED_START, + SIGNED_EXPIRY, + SIGNED_RESOURCE, + SIGNED_PERMISSION, + SIGNED_IDENTIFIER, + SIGNED_SIGNATURE, + RESOURCE_BLOB, + RESOURCE_CONTAINER, + SIGNED_RESOURCE_TYPE, + SHARED_ACCESS_PERMISSION) from azure.storage import AccessPolicy from azuretest.util import AzureTestCase import unittest -#------------------------------------------------------------------------------ -SIGNED_START = 'st' -SIGNED_EXPIRY = 'se' -SIGNED_RESOURCE = 'sr' -SIGNED_PERMISSION = 'sp' -SIGNED_IDENTIFIER = 'si' -SIGNED_SIGNATURE = 'sig' -RESOURCE_BLOB = 'blob' -RESOURCE_CONTAINER = 'container' -SIGNED_RESOURCE_TYPE = 'resource' -SHARED_ACCESS_PERMISSION = 'permission' - #------------------------------------------------------------------------------ class SharedAccessSignatureTest(AzureTestCase): From c47ea42edebfdb5b30fc55e9ece5c5162d3b000f Mon Sep 17 00:00:00 2001 From: huguesv Date: Wed, 20 Mar 2013 11:28:40 -0700 Subject: [PATCH 3/5] Updated version for next release --- ChangeLog.txt | 5 +++++ src/setup.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/ChangeLog.txt b/ChangeLog.txt index c9af47f03db2..24d89d54d79f 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,3 +1,8 @@ +2013-03-20 Version 0.6.2 + * Fixes for bugs: + #75 crash on python 2.7 x64 windows + #73 _convert_query_string return a wrong query string parameter + 2012-12-17 Version 0.6.1 * Fixes for bugs: #69 _get_readable_id doesn't support queues with slashes in their names diff --git a/src/setup.py b/src/setup.py index 6219854e80be..75365885f67c 100644 --- a/src/setup.py +++ b/src/setup.py @@ -18,7 +18,7 @@ from distutils.core import setup setup(name='azure', - version='0.6.1', + version='0.6.2', description='Windows Azure client APIs', url='https://github.com/WindowsAzure/azure-sdk-for-python', packages=['azure', From a31f86568d271a18a6b2d44799a9d8b84d9caacb Mon Sep 17 00:00:00 2001 From: huguesv Date: Thu, 21 Mar 2013 10:34:34 -0700 Subject: [PATCH 4/5] Added more tests for SAS --- test/azuretest/test_blobservice.py | 120 ++++++++++++++++++++++++----- 1 file changed, 102 insertions(+), 18 deletions(-) diff --git a/test/azuretest/test_blobservice.py b/test/azuretest/test_blobservice.py index a1fb58c2e857..66472d51af20 100644 --- a/test/azuretest/test_blobservice.py +++ b/test/azuretest/test_blobservice.py @@ -85,14 +85,14 @@ def _create_container_and_page_blob(self, container_name, blob_name, content_len resp = self.bc.put_blob(self.container_name, blob_name, '', 'PageBlob', x_ms_blob_content_length=str(content_length)) self.assertIsNone(resp) - def _get_permission(self, sas, resource_type, resource_path): + def _get_permission(self, sas, resource_type, resource_path, permission): date_format = "%Y-%m-%dT%H:%M:%SZ" start = datetime.datetime.utcnow() - datetime.timedelta(minutes=1) expiry = start + datetime.timedelta(hours=1) sap = SharedAccessPolicy(AccessPolicy(start.strftime(date_format), expiry.strftime(date_format), - 'r')) + permission)) signed_query = sas.generate_signed_query_string(resource_path, resource_type, @@ -100,21 +100,34 @@ def _get_permission(self, sas, resource_type, resource_path): return Permission('/' + resource_path, signed_query) - def _get_signed_web_resource(self, sas, resource_type, resource_path): + def _get_signed_web_resource(self, sas, resource_type, resource_path, permission): web_rsrc = WebResource() web_rsrc.properties[SIGNED_RESOURCE_TYPE] = resource_type - web_rsrc.properties[SHARED_ACCESS_PERMISSION] = 'r' + web_rsrc.properties[SHARED_ACCESS_PERMISSION] = permission web_rsrc.path = '/' + resource_path web_rsrc.request_url = '/' + resource_path return sas.sign_request(web_rsrc) def _get_request(self, host, url): + return self._web_request('GET', host, url, None) + + def _put_request(self, host, url, content): + return self._web_request('PUT', host, url, content) + + def _del_request(self, host, url): + return self._web_request('DELETE', host, url, None) + + def _web_request(self, method, host, url, content): connection = httplib.HTTPConnection(host) - connection.putrequest('GET', url) - connection.putheader('Content-Type', 'application/octet-stream Charset=UTF-8') + connection.putrequest(method, url) + connection.putheader('Content-Type', 'application/octet-stream;Charset=UTF-8') + if content is not None: + connection.putheader('Content-Length', str(len(content))) connection.endheaders() - + if content is not None: + connection.send(content) + resp = connection.getresponse() resp.getheaders() respbody = None @@ -1382,9 +1395,39 @@ def test_unicode_get_blob_binary_data(self): self.assertIsInstance(blob, BlobResult) self.assertEquals(blob, binary_data) - def test_shared_access_blob(self): + def test_no_sas_private_blob(self): + # Arrange + data = 'a private blob cannot be read without a shared access signature' + self._create_container_and_block_blob(self.container_name, 'blob1.txt', data) + res_path = self.container_name + '/blob1.txt' + + # Act + host = credentials.getStorageServicesName() + BLOB_SERVICE_HOST_BASE + url = '/' + res_path + respbody = self._get_request(host, url) + + # Assert + self.assertNotEquals(data, respbody) + self.assertNotEquals(-1, respbody.find('ResourceNotFound')) + + def test_no_sas_public_blob(self): # Arrange - data = 'hello blob via blob permission' + data = 'a public blob can be read without a shared access signature' + self.bc.create_container(self.container_name, None, 'blob') + self.bc.put_blob(self.container_name, 'blob1.txt', data, 'BlockBlob') + res_path = self.container_name + '/blob1.txt' + + # Act + host = credentials.getStorageServicesName() + BLOB_SERVICE_HOST_BASE + url = '/' + res_path + respbody = self._get_request(host, url) + + # Assert + self.assertEquals(data, respbody) + + def test_shared_read_access_blob(self): + # Arrange + data = 'shared access signature with read permission on blob' self._create_container_and_block_blob(self.container_name, 'blob1.txt', data) sas = SharedAccessSignature(credentials.getStorageServicesName(), credentials.getStorageServicesKey()) @@ -1392,18 +1435,59 @@ def test_shared_access_blob(self): res_type = RESOURCE_BLOB # Act - sas.permission_set = [self._get_permission(sas, res_type, res_path)] - web_rsrc = self._get_signed_web_resource(sas, res_type, res_path) - - # Assert + sas.permission_set = [self._get_permission(sas, res_type, res_path, 'r')] + web_rsrc = self._get_signed_web_resource(sas, res_type, res_path, 'r') host = credentials.getStorageServicesName() + BLOB_SERVICE_HOST_BASE url = web_rsrc.request_url respbody = self._get_request(host, url) + + # Assert self.assertEquals(data, respbody) + def test_shared_write_access_blob(self): + # Arrange + data = 'shared access signature with write permission on blob' + updated_data = 'updated blob data' + self._create_container_and_block_blob(self.container_name, 'blob1.txt', data) + sas = SharedAccessSignature(credentials.getStorageServicesName(), + credentials.getStorageServicesKey()) + res_path = self.container_name + '/blob1.txt' + res_type = RESOURCE_BLOB + + # Act + sas.permission_set = [self._get_permission(sas, res_type, res_path, 'w')] + web_rsrc = self._get_signed_web_resource(sas, res_type, res_path, 'w') + host = credentials.getStorageServicesName() + BLOB_SERVICE_HOST_BASE + url = web_rsrc.request_url + respbody = self._put_request(host, url, updated_data) + + # Assert + blob = self.bc.get_blob(self.container_name, 'blob1.txt') + self.assertEquals(updated_data, blob) + + def test_shared_delete_access_blob(self): + # Arrange + data = 'shared access signature with delete permission on blob' + self._create_container_and_block_blob(self.container_name, 'blob1.txt', data) + sas = SharedAccessSignature(credentials.getStorageServicesName(), + credentials.getStorageServicesKey()) + res_path = self.container_name + '/blob1.txt' + res_type = RESOURCE_BLOB + + # Act + sas.permission_set = [self._get_permission(sas, res_type, res_path, 'd')] + web_rsrc = self._get_signed_web_resource(sas, res_type, res_path, 'd') + host = credentials.getStorageServicesName() + BLOB_SERVICE_HOST_BASE + url = web_rsrc.request_url + respbody = self._del_request(host, url) + + # Assert + with self.assertRaises(WindowsAzureError): + blob = self.bc.get_blob(self.container_name, 'blob1.txt') + def test_shared_access_container(self): # Arrange - data = 'hello blob via container permission' + data = 'shared access signature with read permission on container' self._create_container_and_block_blob(self.container_name, 'blob1.txt', data) sas = SharedAccessSignature(credentials.getStorageServicesName(), credentials.getStorageServicesKey()) @@ -1411,13 +1495,13 @@ def test_shared_access_container(self): res_type = RESOURCE_CONTAINER # Act - sas.permission_set = [self._get_permission(sas, res_type, res_path)] - web_rsrc = self._get_signed_web_resource(sas, res_type, res_path + '/blob1.txt') - - # Assert + sas.permission_set = [self._get_permission(sas, res_type, res_path, 'r')] + web_rsrc = self._get_signed_web_resource(sas, res_type, res_path + '/blob1.txt', 'r') host = credentials.getStorageServicesName() + BLOB_SERVICE_HOST_BASE url = web_rsrc.request_url respbody = self._get_request(host, url) + + # Assert self.assertEquals(data, respbody) #------------------------------------------------------------------------------ From f03cebcf8052f1542e409f78dd0314d3c284d493 Mon Sep 17 00:00:00 2001 From: huguesv Date: Thu, 21 Mar 2013 14:15:39 -0700 Subject: [PATCH 5/5] Split normal run and coverage run in separate batch files and added ability to specify python folder --- test/coverage.bat | 62 +++++++++++++++++++++++++++++++++++++++++++ test/coverage_x64.bat | 1 + test/run.bat | 34 +++++++----------------- test/run_x64.bat | 1 + 4 files changed, 73 insertions(+), 25 deletions(-) create mode 100644 test/coverage.bat create mode 100644 test/coverage_x64.bat create mode 100644 test/run_x64.bat diff --git a/test/coverage.bat b/test/coverage.bat new file mode 100644 index 000000000000..175621ae3764 --- /dev/null +++ b/test/coverage.bat @@ -0,0 +1,62 @@ +@echo OFF +SETLOCAL +REM---------------------------------------------------------------------------- +REM Copyright (c) Microsoft. All rights reserved. +REM +REM Licensed under the Apache License, Version 2.0 (the "License"); +REM you may not use this file except in compliance with the License. +REM You may obtain a copy of the License at +REM http://www.apache.org/licenses/LICENSE-2.0 +REM +REM Unless required by applicable law or agreed to in writing, software +REM distributed under the License is distributed on an "AS IS" BASIS, +REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +REM See the License for the specific language governing permissions and +REM limitations under the License. +REM---------------------------------------------------------------------------- +cls + +if "%1%" == "" ( + set PYTHONDIR=%SystemDrive%\Python27 +) else ( + set PYTHONDIR=%1% +) + +if "%2%" == "" ( + set COVERAGEDIR=htmlcov +) else ( + set COVERAGEDIR=%2% +) + +if "%PYTHONPATH%" == "" ( + set PYTHONPATH=..\src +) else ( + set PYTHONPATH=%PYTHONPATH%;..\src +) + +if exist "%PYTHONDIR%\Scripts\coverage.exe" ( + goto :coverage +) + + +REM --------------------------------------------------------------------------- +if not exist "%PYTHONDIR%\Scripts\pip.exe" ( + echo Cannot do a code coverage run when neither 'coverage' nor 'pip' are installed. + goto :exit_door +) + +echo Installing 'coverage' package... +%PYTHONDIR%\Scripts\pip.exe install coverage +echo Finished installing 'coverage' package + +REM --------------------------------------------------------------------------- +:coverage +echo Starting coverage run using %PYTHONDIR% +%PYTHONDIR%\Scripts\coverage.exe run -m unittest discover -p "test_*.py" +%PYTHONDIR%\Scripts\coverage.exe html -d %COVERAGEDIR% +start %CD%\%COVERAGEDIR%\index.html +echo Finished coverage run! + +REM --------------------------------------------------------------------------- +:exit_door +exit /B %UNITTEST_EC% \ No newline at end of file diff --git a/test/coverage_x64.bat b/test/coverage_x64.bat new file mode 100644 index 000000000000..3828832186fb --- /dev/null +++ b/test/coverage_x64.bat @@ -0,0 +1 @@ +call coverage.bat C:\Python27_x64 htmlcov_x64 diff --git a/test/run.bat b/test/run.bat index b610556263da..9d5a8b46acf3 100644 --- a/test/run.bat +++ b/test/run.bat @@ -16,39 +16,23 @@ REM limitations under the License. REM---------------------------------------------------------------------------- cls +if "%1%" == "" ( + set PYTHONDIR=%SystemDrive%\Python27 +) else ( + set PYTHONDIR=%1% +) + if "%PYTHONPATH%" == "" ( set PYTHONPATH=..\src ) else ( - set PYTHONPATH=%PYTHONPATH%:..\src + set PYTHONPATH=%PYTHONPATH%;..\src ) -echo Running tests... -%SystemDrive%\Python27\python.exe -m unittest discover -p "test_*.py" +echo Running tests using %PYTHONDIR% +%PYTHONDIR%\python.exe -m unittest discover -p "test_*.py" set UNITTEST_EC=%ERRORLEVEL% echo Finished running tests! -if exist "%SystemDrive%\Python27\Scripts\coverage.exe" ( - goto :coverage -) - - -REM --------------------------------------------------------------------------- -if not exist "%SystemDrive%\Python27\Scripts\pip.exe" ( - echo Cannot do a code coverage run when neither 'coverage' nor 'pip' are installed. - goto :exit_door -) - -echo Installing 'coverage' package... -%SystemDrive%\Python27\Scripts\pip.exe install coverage==3.5.2 -echo Finished installing 'coverage' package - -REM --------------------------------------------------------------------------- -:coverage -echo Starting coverage run... -%SystemDrive%\Python27\Scripts\coverage.exe run -m unittest discover -p "test_*.py" -%SystemDrive%\Python27\Scripts\coverage.exe html -start %CD%\htmlcov\index.html -echo Finished coverage run! REM --------------------------------------------------------------------------- :exit_door diff --git a/test/run_x64.bat b/test/run_x64.bat new file mode 100644 index 000000000000..e3479942a699 --- /dev/null +++ b/test/run_x64.bat @@ -0,0 +1 @@ +call run.bat C:\Python27_x64