diff --git a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/constants.py b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/constants.py index 62886ccd7a7d..967773eb0a71 100644 --- a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/constants.py +++ b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/constants.py @@ -7,7 +7,7 @@ import sys -X_MS_VERSION = '2018-03-28' +X_MS_VERSION = '2018-06-17' # Socket timeout in seconds DEFAULT_SOCKET_TIMEOUT = 20 diff --git a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/shared_access_signature.py b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/shared_access_signature.py index cad3f270600b..0c16711db6c5 100644 --- a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/shared_access_signature.py +++ b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/shared_access_signature.py @@ -7,9 +7,9 @@ import sys from datetime import date +from azure.storage.blob._shared.utils import _QueryStringConstants from .constants import X_MS_VERSION -from .utils import _sign_string, url_quote, _QueryStringConstants - +from .utils import _sign_string, url_quote if sys.version_info < (3,): def _str(value): @@ -33,7 +33,7 @@ class SharedAccessSignature(object): generate_*_shared_access_signature method directly. ''' - def __init__(self, account_name, account_key, x_ms_version=X_MS_VERSION): + def __init__(self, account_name, account_key, user_delegation_key=None, x_ms_version=X_MS_VERSION): ''' :param str account_name: The storage account name used to generate the shared access signatures. @@ -45,6 +45,7 @@ def __init__(self, account_name, account_key, x_ms_version=X_MS_VERSION): self.account_name = account_name self.account_key = account_key self.x_ms_version = x_ms_version + self.user_delegation_key = user_delegation_key def generate_account(self, services, resource_types, permission, expiry, start=None, ip=None, protocol=None): @@ -97,110 +98,7 @@ def generate_account(self, services, resource_types, permission, expiry, start=N return sas.get_token() -class _SharedAccessHelper(object): - def __init__(self): - self.query_dict = {} - - def _add_query(self, name, val): - if val: - self.query_dict[name] = _str(val) if val is not None else None - - def add_base(self, permission, expiry, start, ip, protocol, x_ms_version): - if isinstance(start, date): - start = _to_utc_datetime(start) - - if isinstance(expiry, date): - expiry = _to_utc_datetime(expiry) - - self._add_query(_QueryStringConstants.SIGNED_START, start) - self._add_query(_QueryStringConstants.SIGNED_EXPIRY, expiry) - self._add_query(_QueryStringConstants.SIGNED_PERMISSION, permission) - self._add_query(_QueryStringConstants.SIGNED_IP, ip) - self._add_query(_QueryStringConstants.SIGNED_PROTOCOL, protocol) - self._add_query(_QueryStringConstants.SIGNED_VERSION, x_ms_version) - - def add_resource(self, resource): - self._add_query(_QueryStringConstants.SIGNED_RESOURCE, resource) - - def add_id(self, policy_id): - self._add_query(_QueryStringConstants.SIGNED_IDENTIFIER, policy_id) - - def add_account(self, services, resource_types): - self._add_query(_QueryStringConstants.SIGNED_SERVICES, services) - self._add_query(_QueryStringConstants.SIGNED_RESOURCE_TYPES, resource_types) - - def add_override_response_headers(self, cache_control, - content_disposition, - content_encoding, - content_language, - content_type): - self._add_query(_QueryStringConstants.SIGNED_CACHE_CONTROL, cache_control) - self._add_query(_QueryStringConstants.SIGNED_CONTENT_DISPOSITION, content_disposition) - self._add_query(_QueryStringConstants.SIGNED_CONTENT_ENCODING, content_encoding) - self._add_query(_QueryStringConstants.SIGNED_CONTENT_LANGUAGE, content_language) - self._add_query(_QueryStringConstants.SIGNED_CONTENT_TYPE, content_type) - - def add_resource_signature(self, account_name, account_key, service, path): - def get_value_to_append(query): - return_value = self.query_dict.get(query) or '' - return return_value + '\n' - - if path[0] != '/': - path = '/' + path - - canonicalized_resource = '/' + service + '/' + account_name + path + '\n' - - # Form the string to sign from shared_access_policy and canonicalized - # resource. The order of values is important. - string_to_sign = \ - (get_value_to_append(_QueryStringConstants.SIGNED_PERMISSION) + - get_value_to_append(_QueryStringConstants.SIGNED_START) + - get_value_to_append(_QueryStringConstants.SIGNED_EXPIRY) + - canonicalized_resource + - get_value_to_append(_QueryStringConstants.SIGNED_IDENTIFIER) + - get_value_to_append(_QueryStringConstants.SIGNED_IP) + - get_value_to_append(_QueryStringConstants.SIGNED_PROTOCOL) + - get_value_to_append(_QueryStringConstants.SIGNED_VERSION)) - - if service in ['blob', 'file']: - string_to_sign += \ - (get_value_to_append(_QueryStringConstants.SIGNED_CACHE_CONTROL) + - get_value_to_append(_QueryStringConstants.SIGNED_CONTENT_DISPOSITION) + - get_value_to_append(_QueryStringConstants.SIGNED_CONTENT_ENCODING) + - get_value_to_append(_QueryStringConstants.SIGNED_CONTENT_LANGUAGE) + - get_value_to_append(_QueryStringConstants.SIGNED_CONTENT_TYPE)) - - # remove the trailing newline - if string_to_sign[-1] == '\n': - string_to_sign = string_to_sign[:-1] - - self._add_query(_QueryStringConstants.SIGNED_SIGNATURE, - _sign_string(account_key, string_to_sign)) - - def add_account_signature(self, account_name, account_key): - def get_value_to_append(query): - return_value = self.query_dict.get(query) or '' - return return_value + '\n' - - string_to_sign = \ - (account_name + '\n' + - get_value_to_append(_QueryStringConstants.SIGNED_PERMISSION) + - get_value_to_append(_QueryStringConstants.SIGNED_SERVICES) + - get_value_to_append(_QueryStringConstants.SIGNED_RESOURCE_TYPES) + - get_value_to_append(_QueryStringConstants.SIGNED_START) + - get_value_to_append(_QueryStringConstants.SIGNED_EXPIRY) + - get_value_to_append(_QueryStringConstants.SIGNED_IP) + - get_value_to_append(_QueryStringConstants.SIGNED_PROTOCOL) + - get_value_to_append(_QueryStringConstants.SIGNED_VERSION)) - - self._add_query(_QueryStringConstants.SIGNED_SIGNATURE, - _sign_string(account_key, string_to_sign)) - - def get_token(self): - return '&'.join(['{0}={1}'.format(n, url_quote(v)) for n, v in self.query_dict.items() if v is not None]) - - -class BlobSharedAccessSignature(SharedAccessSignature): +class ResourceSharedAccessSignature(SharedAccessSignature): ''' Provides a factory for creating blob and container access signature tokens with a common account name and account key. Users can either @@ -208,28 +106,36 @@ class BlobSharedAccessSignature(SharedAccessSignature): generate_*_shared_access_signature method directly. ''' - def __init__(self, account_name, account_key): + def __init__(self, account_name, account_key=None, user_delegation_key=None): ''' :param str account_name: The storage account name used to generate the shared access signatures. :param str account_key: The access key to generate the shares access signatures. + :param ~azure.storage.blob.models.UserDelegationKey user_delegation_key: + Instead of an account key, the user could pass in a user delegation key. + A user delegation key can be obtained from the service by authenticating with an AAD identity; + this can be accomplished by calling get_user_delegation_key on any Blob service object. ''' - super(BlobSharedAccessSignature, self).__init__(account_name, account_key, x_ms_version=X_MS_VERSION) + super(ResourceSharedAccessSignature, self).__init__(account_name, account_key, x_ms_version=X_MS_VERSION) + self.user_delegation_key = user_delegation_key - def generate_blob(self, container_name, blob_name, permission=None, + def generate_blob(self, container_name, blob_name, snapshot=None, permission=None, expiry=None, start=None, policy_id=None, ip=None, protocol=None, cache_control=None, content_disposition=None, content_encoding=None, content_language=None, content_type=None): ''' - Generates a shared access signature for the blob. + Generates a shared access signature for the blob or one of its snapshots. Use the returned signature with the sas_token parameter of any BlobService. :param str container_name: Name of container. :param str blob_name: Name of blob. + :param str snapshot: + The snapshot parameter is an opaque DateTime value that, + when present, specifies the blob snapshot to grant permission. :param BlobPermissions permission: The permissions associated with the shared access signature. The user is restricted to operations allowed by the permissions. @@ -252,7 +158,7 @@ def generate_blob(self, container_name, blob_name, permission=None, to UTC. If a date is passed in without timezone info, it is assumed to be UTC. :type start: datetime or str - :param str id: + :param str policy_id: A unique value up to 64 characters in length that correlates to a stored access policy. To create a stored access policy, use set_blob_service_properties. @@ -283,14 +189,16 @@ def generate_blob(self, container_name, blob_name, permission=None, ''' resource_path = container_name + '/' + blob_name - sas = _SharedAccessHelper() + sas = _ResourceSharedAccessHelper() sas.add_base(permission, expiry, start, ip, protocol, self.x_ms_version) sas.add_id(policy_id) - sas.add_resource('b') + sas.add_resource('b' if snapshot is None else 'bs') + sas.add_timestamp(snapshot) sas.add_override_response_headers(cache_control, content_disposition, content_encoding, content_language, content_type) - sas.add_resource_signature(self.account_name, self.account_key, 'blob', resource_path) + sas.add_resource_signature(self.account_name, self.account_key, resource_path, + user_delegation_key=self.user_delegation_key) return sas.get_token() @@ -356,113 +264,151 @@ def generate_container(self, container_name, permission=None, expiry=None, Response header value for Content-Type when resource is accessed using this shared access signature. ''' - sas = _SharedAccessHelper() + sas = _ResourceSharedAccessHelper() sas.add_base(permission, expiry, start, ip, protocol, self.x_ms_version) sas.add_id(policy_id) sas.add_resource('c') sas.add_override_response_headers(cache_control, content_disposition, content_encoding, content_language, content_type) - sas.add_resource_signature(self.account_name, self.account_key, 'blob', container_name) - + sas.add_resource_signature(self.account_name, self.account_key, container_name, + user_delegation_key=None) return sas.get_token() -class QueueSharedAccessSignature(SharedAccessSignature): - ''' - Provides a factory for creating queue shares access - signature tokens with a common account name and account key. Users can either - use the factory or can construct the appropriate service and use the - generate_*_shared_access_signature method directly. - ''' +class _SharedAccessHelper(object): + def __init__(self): + self.query_dict = {} - def __init__(self, account_name, account_key): - ''' - :param str account_name: - The storage account name used to generate the shared access signatures. - :param str account_key: - The access key to generate the shares access signatures. - ''' - super(QueueSharedAccessSignature, self).__init__(account_name, account_key, x_ms_version=X_MS_VERSION) + def _add_query(self, name, val): + if val: + self.query_dict[name] = _str(val) if val is not None else None - def generate_queue(self, queue_name, permission=None, - expiry=None, start=None, policy_id=None, - ip=None, protocol=None): - ''' - Generates a shared access signature for the queue. - Use the returned signature with the sas_token parameter of QueueService. - :param str queue_name: - Name of queue. - :param QueuePermissions permission: - The permissions associated with the shared access signature. The - user is restricted to operations allowed by the permissions. - Permissions must be ordered read, add, update, process. - Required unless an id is given referencing a stored access policy - which contains this field. This field must be omitted if it has been - specified in an associated stored access policy. - :param expiry: - The time at which the shared access signature becomes invalid. - Required unless an id is given referencing a stored access policy - which contains this field. This field must be omitted if it has - been specified in an associated stored access policy. Azure will always - convert values to UTC. If a date is passed in without timezone info, it - is assumed to be UTC. - :type expiry: datetime or str - :param start: - The time at which the shared access signature becomes valid. If - omitted, start time for this call is assumed to be the time when the - storage service receives the request. Azure will always convert values - to UTC. If a date is passed in without timezone info, it is assumed to - be UTC. - :type start: datetime or str - :param str policy_id: - A unique value up to 64 characters in length that correlates to a - stored access policy. - :param str ip: - Specifies an IP address or a range of IP addresses from which to accept requests. - If the IP address from which the request originates does not match the IP address - or address range specified on the SAS token, the request is not authenticated. - For example, specifying sip=168.1.5.65 or sip=168.1.5.60-168.1.5.70 on the SAS - restricts the request to those IP addresses. - :param str protocol: - Specifies the protocol permitted for a request made. The default value - is https,http. See :class:`~azure.storage.common.models.Protocol` for possible values. - ''' - sas = _QueueSharedAccessHelper() - sas.add_base(permission, expiry, start, ip, protocol, self.x_ms_version) - sas.add_id(policy_id) - sas.add_resource_signature(self.account_name, self.account_key, queue_name) + def add_base(self, permission, expiry, start, ip, protocol, x_ms_version): + if isinstance(start, date): + start = _to_utc_datetime(start) - return sas.get_token() + if isinstance(expiry, date): + expiry = _to_utc_datetime(expiry) + self._add_query(_QueryStringConstants.SIGNED_START, start) + self._add_query(_QueryStringConstants.SIGNED_EXPIRY, expiry) + self._add_query(_QueryStringConstants.SIGNED_PERMISSION, permission) + self._add_query(_QueryStringConstants.SIGNED_IP, ip) + self._add_query(_QueryStringConstants.SIGNED_PROTOCOL, protocol) + self._add_query(_QueryStringConstants.SIGNED_VERSION, x_ms_version) -class _QueueSharedAccessHelper(_SharedAccessHelper): + def add_resource(self, resource): + self._add_query(_QueryStringConstants.SIGNED_RESOURCE, resource) + + def add_id(self, policy_id): + self._add_query(_QueryStringConstants.SIGNED_IDENTIFIER, policy_id) + + def add_account(self, services, resource_types): + self._add_query(_QueryStringConstants.SIGNED_SERVICES, services) + self._add_query(_QueryStringConstants.SIGNED_RESOURCE_TYPES, resource_types) - def add_resource_signature(self, account_name, account_key, path): # pylint: disable=arguments-differ + def add_override_response_headers(self, cache_control, + content_disposition, + content_encoding, + content_language, + content_type): + self._add_query(_QueryStringConstants.SIGNED_CACHE_CONTROL, cache_control) + self._add_query(_QueryStringConstants.SIGNED_CONTENT_DISPOSITION, content_disposition) + self._add_query(_QueryStringConstants.SIGNED_CONTENT_ENCODING, content_encoding) + self._add_query(_QueryStringConstants.SIGNED_CONTENT_LANGUAGE, content_language) + self._add_query(_QueryStringConstants.SIGNED_CONTENT_TYPE, content_type) + + def add_account_signature(self, account_name, account_key): def get_value_to_append(query): return_value = self.query_dict.get(query) or '' return return_value + '\n' + string_to_sign = \ + (account_name + '\n' + + get_value_to_append(_QueryStringConstants.SIGNED_PERMISSION) + + get_value_to_append(_QueryStringConstants.SIGNED_SERVICES) + + get_value_to_append(_QueryStringConstants.SIGNED_RESOURCE_TYPES) + + get_value_to_append(_QueryStringConstants.SIGNED_START) + + get_value_to_append(_QueryStringConstants.SIGNED_EXPIRY) + + get_value_to_append(_QueryStringConstants.SIGNED_IP) + + get_value_to_append(_QueryStringConstants.SIGNED_PROTOCOL) + + get_value_to_append(_QueryStringConstants.SIGNED_VERSION)) + + self._add_query(_QueryStringConstants.SIGNED_SIGNATURE, + _sign_string(account_key, string_to_sign)) + + def get_token(self): + return '&'.join(['{0}={1}'.format(n, url_quote(v)) for n, v in self.query_dict.items() if v is not None]) + + +class _ResourceSharedAccessHelper(_SharedAccessHelper): + def __init__(self): + super(_ResourceSharedAccessHelper, self).__init__() + + def add_timestamp(self, timestamp): + self._add_query(_QueryStringConstants.SIGNED_TIMESTAMP, timestamp) + + def get_value_to_append(self, query): + return_value = self.query_dict.get(query) or '' + return return_value + '\n' + + def add_resource_signature(self, account_name, account_key, path, user_delegation_key=None): if path[0] != '/': path = '/' + path - canonicalized_resource = '/queue/' + account_name + path + '\n' + canonicalized_resource = '/blob/' + account_name + path + '\n' # Form the string to sign from shared_access_policy and canonicalized # resource. The order of values is important. string_to_sign = \ - (get_value_to_append(_QueryStringConstants.SIGNED_PERMISSION) + - get_value_to_append(_QueryStringConstants.SIGNED_START) + - get_value_to_append(_QueryStringConstants.SIGNED_EXPIRY) + - canonicalized_resource + - get_value_to_append(_QueryStringConstants.SIGNED_IDENTIFIER) + - get_value_to_append(_QueryStringConstants.SIGNED_IP) + - get_value_to_append(_QueryStringConstants.SIGNED_PROTOCOL) + - get_value_to_append(_QueryStringConstants.SIGNED_VERSION)) + (self.get_value_to_append(_QueryStringConstants.SIGNED_PERMISSION) + + self.get_value_to_append(_QueryStringConstants.SIGNED_START) + + self.get_value_to_append(_QueryStringConstants.SIGNED_EXPIRY) + + canonicalized_resource) + + if user_delegation_key is not None: + self._add_query(_QueryStringConstants.SIGNED_OID, user_delegation_key.signed_oid) + self._add_query(_QueryStringConstants.SIGNED_TID, user_delegation_key.signed_tid) + self._add_query(_QueryStringConstants.SIGNED_KEY_START, user_delegation_key.signed_start) + self._add_query(_QueryStringConstants.SIGNED_KEY_EXPIRY, user_delegation_key.signed_expiry) + self._add_query(_QueryStringConstants.SIGNED_KEY_SERVICE, user_delegation_key.signed_service) + self._add_query(_QueryStringConstants.SIGNED_KEY_VERSION, user_delegation_key.signed_version) + + string_to_sign += \ + (self.get_value_to_append(_QueryStringConstants.SIGNED_OID) + + self.get_value_to_append(_QueryStringConstants.SIGNED_TID) + + self.get_value_to_append(_QueryStringConstants.SIGNED_KEY_START) + + self.get_value_to_append(_QueryStringConstants.SIGNED_KEY_EXPIRY) + + self.get_value_to_append(_QueryStringConstants.SIGNED_KEY_SERVICE) + + self.get_value_to_append(_QueryStringConstants.SIGNED_KEY_VERSION)) + else: + string_to_sign += self.get_value_to_append(_QueryStringConstants.SIGNED_IDENTIFIER) + + string_to_sign += \ + (self.get_value_to_append(_QueryStringConstants.SIGNED_IP) + + self.get_value_to_append(_QueryStringConstants.SIGNED_PROTOCOL) + + self.get_value_to_append(_QueryStringConstants.SIGNED_VERSION) + + self.get_value_to_append(_QueryStringConstants.SIGNED_RESOURCE) + + self.get_value_to_append(_QueryStringConstants.SIGNED_TIMESTAMP) + + self.get_value_to_append(_QueryStringConstants.SIGNED_CACHE_CONTROL) + + self.get_value_to_append(_QueryStringConstants.SIGNED_CONTENT_DISPOSITION) + + self.get_value_to_append(_QueryStringConstants.SIGNED_CONTENT_ENCODING) + + self.get_value_to_append(_QueryStringConstants.SIGNED_CONTENT_LANGUAGE) + + self.get_value_to_append(_QueryStringConstants.SIGNED_CONTENT_TYPE)) # remove the trailing newline if string_to_sign[-1] == '\n': string_to_sign = string_to_sign[:-1] self._add_query(_QueryStringConstants.SIGNED_SIGNATURE, - _sign_string(account_key, string_to_sign)) + _sign_string(account_key if user_delegation_key is None else user_delegation_key.value, + string_to_sign)) + + def get_token(self): + # a conscious decision was made to exclude the timestamp in the generated token + # this is to avoid having two snapshot ids in the query parameters when the user appends the snapshot timestamp + exclude = [_QueryStringConstants.SIGNED_TIMESTAMP] + return '&'.join(['{0}={1}'.format(n, url_quote(v)) + for n, v in self.query_dict.items() if v is not None and n not in exclude]) + diff --git a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/utils.py b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/utils.py index 6d980d967891..4a1ed324ded1 100644 --- a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/utils.py +++ b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/utils.py @@ -5,7 +5,7 @@ # -------------------------------------------------------------------------- from typing import ( # pylint: disable=unused-import - Union, Optional, Any, Iterable, Dict, List, Type, Tuple, + Optional, Any, Tuple, TYPE_CHECKING ) import base64 @@ -58,11 +58,7 @@ if TYPE_CHECKING: - from datetime import datetime from azure.core.pipeline.transport import HttpTransport - from azure.core.pipeline.policies import HTTPPolicy - from azure.core.exceptions import AzureError - _LOGGER = logging.getLogger(__name__) @@ -88,6 +84,13 @@ class _QueryStringConstants(object): END_RK = 'erk' SIGNED_RESOURCE_TYPES = 'srt' SIGNED_SERVICES = 'ss' + SIGNED_TIMESTAMP = 'snapshot' + SIGNED_OID = 'skoid' + SIGNED_TID = 'sktid' + SIGNED_KEY_START = 'skt' + SIGNED_KEY_EXPIRY = 'ske' + SIGNED_KEY_SERVICE = 'sks' + SIGNED_KEY_VERSION = 'skv' @staticmethod def to_list(): diff --git a/sdk/storage/azure-storage-blob/azure/storage/blob/blob_client.py b/sdk/storage/azure-storage-blob/azure/storage/blob/blob_client.py index dd0018a17386..bcbfbc249cd7 100644 --- a/sdk/storage/azure-storage-blob/azure/storage/blob/blob_client.py +++ b/sdk/storage/azure-storage-blob/azure/storage/blob/blob_client.py @@ -18,7 +18,7 @@ import six -from ._shared.shared_access_signature import BlobSharedAccessSignature +from ._shared.shared_access_signature import ResourceSharedAccessSignature from ._shared.encryption import _generate_blob_encryption_data from ._shared.upload_chunking import IterStreamer from ._shared.utils import ( @@ -298,12 +298,12 @@ def generate_shared_access_signature( """ if not hasattr(self.credential, 'account_key') or not self.credential.account_key: raise ValueError("No account SAS key available.") - sas = BlobSharedAccessSignature(self.credential.account_name, self.credential.account_key) + sas = ResourceSharedAccessSignature(self.credential.account_name, self.credential.account_key) return sas.generate_blob( self.container_name, self.blob_name, - permission, - expiry, + permission=permission, + expiry=expiry, start=start, policy_id=policy_id, ip=ip, diff --git a/sdk/storage/azure-storage-blob/azure/storage/blob/container_client.py b/sdk/storage/azure-storage-blob/azure/storage/blob/container_client.py index 28da09873c1e..26a32bce9659 100644 --- a/sdk/storage/azure-storage-blob/azure/storage/blob/container_client.py +++ b/sdk/storage/azure-storage-blob/azure/storage/blob/container_client.py @@ -18,7 +18,7 @@ import six -from ._shared.shared_access_signature import BlobSharedAccessSignature +from ._shared.shared_access_signature import ResourceSharedAccessSignature from ._shared.utils import ( StorageAccountHostsMixin, process_storage_error, @@ -263,7 +263,7 @@ def generate_shared_access_signature( """ if not hasattr(self.credential, 'account_key') and not self.credential.account_key: raise ValueError("No account SAS key available.") - sas = BlobSharedAccessSignature(self.credential.account_name, self.credential.account_key) + sas = ResourceSharedAccessSignature(self.credential.account_name, self.credential.account_key) return sas.generate_container( self.container_name, permission=permission, diff --git a/sdk/storage/azure-storage-blob/azure/storage/blob/models.py b/sdk/storage/azure-storage-blob/azure/storage/blob/models.py index ab75e1d7722d..11f8d71768d9 100644 --- a/sdk/storage/azure-storage-blob/azure/storage/blob/models.py +++ b/sdk/storage/azure-storage-blob/azure/storage/blob/models.py @@ -1013,3 +1013,36 @@ def __str__(self): BlobPermissions.DELETE = BlobPermissions(delete=True) BlobPermissions.READ = BlobPermissions(read=True) BlobPermissions.WRITE = BlobPermissions(write=True) + + +class UserDelegationKey(object): + """ + Represents a user delegation key, provided to the user by Azure Storage + based on their Azure Active Directory access token. + + The fields are saved as simple strings since the user does not have to interact with this object; + to generate an identify SAS, the user can simply pass it to the right API. + + :ivar str signed_oid: + Object ID of this token. + :ivar str signed_tid: + Tenant ID of the tenant that issued this token. + :ivar str signed_start: + The datetime this token becomes valid. + :ivar str signed_expiry: + The datetime this token expires. + :ivar str signed_service: + What service this key is valid for. + :ivar str signed_version: + The version identifier of the REST service that created this token. + :ivar str value: + The user delegation key. + """ + def __init__(self): + self.signed_oid = None + self.signed_tid = None + self.signed_start = None + self.signed_expiry = None + self.signed_service = None + self.signed_version = None + self.value = None \ No newline at end of file diff --git a/sdk/storage/azure-storage-blob/tests/test_common_blob.py b/sdk/storage/azure-storage-blob/tests/test_common_blob.py index ac4a2a8869cb..44ee87610f20 100644 --- a/sdk/storage/azure-storage-blob/tests/test_common_blob.py +++ b/sdk/storage/azure-storage-blob/tests/test_common_blob.py @@ -1190,6 +1190,37 @@ def test_sas_access_blob(self): # Assert self.assertEqual(self.byte_data, content) + def test_sas_access_blob_snapshot(self): + # SAS URL is calculated from storage key, so this test runs live only + if TestMode.need_recording_file(self.test_mode): + return + + # Arrange + blob_name = self._create_block_blob() + blob_client = self.bsc.get_blob_client(self.container_name, blob_name) + blob_snapshot = blob_client.create_snapshot() + blob_snapshot_client = self.bsc.get_blob_client(self.container_name, blob_name, snapshot=blob_snapshot) + + token = blob_snapshot_client.generate_shared_access_signature( + permission=BlobPermissions.READ + BlobPermissions.DELETE, + expiry=datetime.utcnow() + timedelta(hours=1), + ) + + service = BlobClient(blob_snapshot_client.url, credential=token) + + # Act + snapshot_content = service.download_blob().content_as_bytes() + + # Assert + self.assertEqual(self.byte_data, snapshot_content) + + # Act + service.delete_blob() + + # Assert + with self.assertRaises(ResourceNotFoundError): + service.get_blob_properties() + @record def test_sas_signed_identifier(self): # SAS URL is calculated from storage key, so this test runs live only diff --git a/sdk/storage/azure-storage-file/azure/storage/file/_shared/shared_access_signature.py b/sdk/storage/azure-storage-file/azure/storage/file/_shared/shared_access_signature.py index 8802ad905270..8302f3d18e8e 100644 --- a/sdk/storage/azure-storage-file/azure/storage/file/_shared/shared_access_signature.py +++ b/sdk/storage/azure-storage-file/azure/storage/file/_shared/shared_access_signature.py @@ -96,144 +96,26 @@ def generate_account(self, services, resource_types, permission, expiry, start=N return sas.get_token() - -class _SharedAccessHelper(object): - def __init__(self): - self.query_dict = {} - - def _add_query(self, name, val): - if val: - self.query_dict[name] = _str(val) if val is not None else None - - def add_base(self, permission, expiry, start, ip, protocol, x_ms_version): - if isinstance(start, date): - start = _to_utc_datetime(start) - - if isinstance(expiry, date): - expiry = _to_utc_datetime(expiry) - - self._add_query(_QueryStringConstants.SIGNED_START, start) - self._add_query(_QueryStringConstants.SIGNED_EXPIRY, expiry) - self._add_query(_QueryStringConstants.SIGNED_PERMISSION, permission) - self._add_query(_QueryStringConstants.SIGNED_IP, ip) - self._add_query(_QueryStringConstants.SIGNED_PROTOCOL, protocol) - self._add_query(_QueryStringConstants.SIGNED_VERSION, x_ms_version) - - def add_resource(self, resource): - self._add_query(_QueryStringConstants.SIGNED_RESOURCE, resource) - - def add_id(self, policy_id): - self._add_query(_QueryStringConstants.SIGNED_IDENTIFIER, policy_id) - - def add_account(self, services, resource_types): - self._add_query(_QueryStringConstants.SIGNED_SERVICES, services) - self._add_query(_QueryStringConstants.SIGNED_RESOURCE_TYPES, resource_types) - - def add_override_response_headers(self, cache_control, - content_disposition, - content_encoding, - content_language, - content_type): - self._add_query(_QueryStringConstants.SIGNED_CACHE_CONTROL, cache_control) - self._add_query(_QueryStringConstants.SIGNED_CONTENT_DISPOSITION, content_disposition) - self._add_query(_QueryStringConstants.SIGNED_CONTENT_ENCODING, content_encoding) - self._add_query(_QueryStringConstants.SIGNED_CONTENT_LANGUAGE, content_language) - self._add_query(_QueryStringConstants.SIGNED_CONTENT_TYPE, content_type) - - def add_resource_signature(self, account_name, account_key, service, path): - def get_value_to_append(query): - return_value = self.query_dict.get(query) or '' - return return_value + '\n' - - if path[0] != '/': - path = '/' + path - - canonicalized_resource = '/' + service + '/' + account_name + path + '\n' - - # Form the string to sign from shared_access_policy and canonicalized - # resource. The order of values is important. - string_to_sign = \ - (get_value_to_append(_QueryStringConstants.SIGNED_PERMISSION) + - get_value_to_append(_QueryStringConstants.SIGNED_START) + - get_value_to_append(_QueryStringConstants.SIGNED_EXPIRY) + - canonicalized_resource + - get_value_to_append(_QueryStringConstants.SIGNED_IDENTIFIER) + - get_value_to_append(_QueryStringConstants.SIGNED_IP) + - get_value_to_append(_QueryStringConstants.SIGNED_PROTOCOL) + - get_value_to_append(_QueryStringConstants.SIGNED_VERSION)) - - if service in ['blob', 'file']: - string_to_sign += \ - (get_value_to_append(_QueryStringConstants.SIGNED_CACHE_CONTROL) + - get_value_to_append(_QueryStringConstants.SIGNED_CONTENT_DISPOSITION) + - get_value_to_append(_QueryStringConstants.SIGNED_CONTENT_ENCODING) + - get_value_to_append(_QueryStringConstants.SIGNED_CONTENT_LANGUAGE) + - get_value_to_append(_QueryStringConstants.SIGNED_CONTENT_TYPE)) - - # remove the trailing newline - if string_to_sign[-1] == '\n': - string_to_sign = string_to_sign[:-1] - - self._add_query(_QueryStringConstants.SIGNED_SIGNATURE, - _sign_string(account_key, string_to_sign)) - - def add_account_signature(self, account_name, account_key): - def get_value_to_append(query): - return_value = self.query_dict.get(query) or '' - return return_value + '\n' - - string_to_sign = \ - (account_name + '\n' + - get_value_to_append(_QueryStringConstants.SIGNED_PERMISSION) + - get_value_to_append(_QueryStringConstants.SIGNED_SERVICES) + - get_value_to_append(_QueryStringConstants.SIGNED_RESOURCE_TYPES) + - get_value_to_append(_QueryStringConstants.SIGNED_START) + - get_value_to_append(_QueryStringConstants.SIGNED_EXPIRY) + - get_value_to_append(_QueryStringConstants.SIGNED_IP) + - get_value_to_append(_QueryStringConstants.SIGNED_PROTOCOL) + - get_value_to_append(_QueryStringConstants.SIGNED_VERSION)) - - self._add_query(_QueryStringConstants.SIGNED_SIGNATURE, - _sign_string(account_key, string_to_sign)) - - def get_token(self): - return '&'.join(['{0}={1}'.format(n, url_quote(v)) for n, v in self.query_dict.items() if v is not None]) - - -class BlobSharedAccessSignature(SharedAccessSignature): - ''' - Provides a factory for creating blob and container access - signature tokens with a common account name and account key. Users can either - use the factory or can construct the appropriate service and use the - generate_*_shared_access_signature method directly. - ''' - - def __init__(self, account_name, account_key): - ''' - :param str account_name: - The storage account name used to generate the shared access signatures. - :param str account_key: - The access key to generate the shares access signatures. + def generate_file(self, share_name, directory_name=None, file_name=None, + permission=None, expiry=None, start=None, policy_id=None, + ip=None, protocol=None, cache_control=None, + content_disposition=None, content_encoding=None, + content_language=None, content_type=None): ''' - super(BlobSharedAccessSignature, self).__init__(account_name, account_key, x_ms_version=X_MS_VERSION) + Generates a shared access signature for the file. + Use the returned signature with the sas_token parameter of FileService. - def generate_blob(self, container_name, blob_name, permission=None, - expiry=None, start=None, policy_id=None, ip=None, protocol=None, - cache_control=None, content_disposition=None, - content_encoding=None, content_language=None, - content_type=None): - ''' - Generates a shared access signature for the blob. - Use the returned signature with the sas_token parameter of any BlobService. - - :param str container_name: - Name of container. - :param str blob_name: - Name of blob. - :param BlobPermissions permission: + :param str share_name: + Name of share. + :param str directory_name: + Name of directory. SAS tokens cannot be created for directories, so + this parameter should only be present if file_name is provided. + :param str file_name: + Name of file. + :param FilePermissions permission: The permissions associated with the shared access signature. The user is restricted to operations allowed by the permissions. - Permissions must be ordered read, write, delete, list. + Permissions must be ordered read, create, write, delete, list. Required unless an id is given referencing a stored access policy which contains this field. This field must be omitted if it has been specified in an associated stored access policy. @@ -252,10 +134,10 @@ def generate_blob(self, container_name, blob_name, permission=None, to UTC. If a date is passed in without timezone info, it is assumed to be UTC. :type start: datetime or str - :param str id: + :param str policy_id: A unique value up to 64 characters in length that correlates to a stored access policy. To create a stored access policy, use - set_blob_service_properties. + set_file_service_properties. :param str ip: Specifies an IP address or a range of IP addresses from which to accept requests. If the IP address from which the request originates does not match the IP address @@ -281,34 +163,37 @@ def generate_blob(self, container_name, blob_name, permission=None, Response header value for Content-Type when resource is accessed using this shared access signature. ''' - resource_path = container_name + '/' + blob_name + resource_path = share_name + if directory_name is not None: + resource_path += '/' + _str(directory_name) if directory_name is not None else None + resource_path += '/' + _str(file_name) if file_name is not None else None sas = _SharedAccessHelper() sas.add_base(permission, expiry, start, ip, protocol, self.x_ms_version) sas.add_id(policy_id) - sas.add_resource('b') + sas.add_resource('f') sas.add_override_response_headers(cache_control, content_disposition, content_encoding, content_language, content_type) - sas.add_resource_signature(self.account_name, self.account_key, 'blob', resource_path) + sas.add_resource_signature(self.account_name, self.account_key, resource_path) return sas.get_token() - def generate_container(self, container_name, permission=None, expiry=None, - start=None, policy_id=None, ip=None, protocol=None, - cache_control=None, content_disposition=None, - content_encoding=None, content_language=None, - content_type=None): + def generate_share(self, share_name, permission=None, expiry=None, + start=None, policy_id=None, ip=None, protocol=None, + cache_control=None, content_disposition=None, + content_encoding=None, content_language=None, + content_type=None): ''' - Generates a shared access signature for the container. - Use the returned signature with the sas_token parameter of any BlobService. + Generates a shared access signature for the share. + Use the returned signature with the sas_token parameter of FileService. - :param str container_name: - Name of container. - :param ContainerPermissions permission: + :param str share_name: + Name of share. + :param SharePermissions permission: The permissions associated with the shared access signature. The user is restricted to operations allowed by the permissions. - Permissions must be ordered read, write, delete, list. + Permissions must be ordered read, create, write, delete, list. Required unless an id is given referencing a stored access policy which contains this field. This field must be omitted if it has been specified in an associated stored access policy. @@ -330,7 +215,7 @@ def generate_container(self, container_name, permission=None, expiry=None, :param str policy_id: A unique value up to 64 characters in length that correlates to a stored access policy. To create a stored access policy, use - set_blob_service_properties. + set_file_service_properties. :param str ip: Specifies an IP address or a range of IP addresses from which to accept requests. If the IP address from which the request originates does not match the IP address @@ -359,86 +244,59 @@ def generate_container(self, container_name, permission=None, expiry=None, sas = _SharedAccessHelper() sas.add_base(permission, expiry, start, ip, protocol, self.x_ms_version) sas.add_id(policy_id) - sas.add_resource('c') + sas.add_resource('s') sas.add_override_response_headers(cache_control, content_disposition, content_encoding, content_language, content_type) - sas.add_resource_signature(self.account_name, self.account_key, 'blob', container_name) + sas.add_resource_signature(self.account_name, self.account_key, share_name) return sas.get_token() -class QueueSharedAccessSignature(SharedAccessSignature): - ''' - Provides a factory for creating queue shares access - signature tokens with a common account name and account key. Users can either - use the factory or can construct the appropriate service and use the - generate_*_shared_access_signature method directly. - ''' +class _SharedAccessHelper(object): + def __init__(self): + self.query_dict = {} - def __init__(self, account_name, account_key): - ''' - :param str account_name: - The storage account name used to generate the shared access signatures. - :param str account_key: - The access key to generate the shares access signatures. - ''' - super(QueueSharedAccessSignature, self).__init__(account_name, account_key, x_ms_version=X_MS_VERSION) + def _add_query(self, name, val): + if val: + self.query_dict[name] = _str(val) if val is not None else None - def generate_queue(self, queue_name, permission=None, - expiry=None, start=None, policy_id=None, - ip=None, protocol=None): - ''' - Generates a shared access signature for the queue. - Use the returned signature with the sas_token parameter of QueueService. - :param str queue_name: - Name of queue. - :param QueuePermissions permission: - The permissions associated with the shared access signature. The - user is restricted to operations allowed by the permissions. - Permissions must be ordered read, add, update, process. - Required unless an id is given referencing a stored access policy - which contains this field. This field must be omitted if it has been - specified in an associated stored access policy. - :param expiry: - The time at which the shared access signature becomes invalid. - Required unless an id is given referencing a stored access policy - which contains this field. This field must be omitted if it has - been specified in an associated stored access policy. Azure will always - convert values to UTC. If a date is passed in without timezone info, it - is assumed to be UTC. - :type expiry: datetime or str - :param start: - The time at which the shared access signature becomes valid. If - omitted, start time for this call is assumed to be the time when the - storage service receives the request. Azure will always convert values - to UTC. If a date is passed in without timezone info, it is assumed to - be UTC. - :type start: datetime or str - :param str policy_id: - A unique value up to 64 characters in length that correlates to a - stored access policy. - :param str ip: - Specifies an IP address or a range of IP addresses from which to accept requests. - If the IP address from which the request originates does not match the IP address - or address range specified on the SAS token, the request is not authenticated. - For example, specifying sip=168.1.5.65 or sip=168.1.5.60-168.1.5.70 on the SAS - restricts the request to those IP addresses. - :param str protocol: - Specifies the protocol permitted for a request made. The default value - is https,http. See :class:`~azure.storage.common.models.Protocol` for possible values. - ''' - sas = _QueueSharedAccessHelper() - sas.add_base(permission, expiry, start, ip, protocol, self.x_ms_version) - sas.add_id(policy_id) - sas.add_resource_signature(self.account_name, self.account_key, queue_name) + def add_base(self, permission, expiry, start, ip, protocol, x_ms_version): + if isinstance(start, date): + start = _to_utc_datetime(start) - return sas.get_token() + if isinstance(expiry, date): + expiry = _to_utc_datetime(expiry) + + self._add_query(_QueryStringConstants.SIGNED_START, start) + self._add_query(_QueryStringConstants.SIGNED_EXPIRY, expiry) + self._add_query(_QueryStringConstants.SIGNED_PERMISSION, permission) + self._add_query(_QueryStringConstants.SIGNED_IP, ip) + self._add_query(_QueryStringConstants.SIGNED_PROTOCOL, protocol) + self._add_query(_QueryStringConstants.SIGNED_VERSION, x_ms_version) + def add_resource(self, resource): + self._add_query(_QueryStringConstants.SIGNED_RESOURCE, resource) + + def add_id(self, policy_id): + self._add_query(_QueryStringConstants.SIGNED_IDENTIFIER, policy_id) -class _QueueSharedAccessHelper(_SharedAccessHelper): + def add_account(self, services, resource_types): + self._add_query(_QueryStringConstants.SIGNED_SERVICES, services) + self._add_query(_QueryStringConstants.SIGNED_RESOURCE_TYPES, resource_types) + + def add_override_response_headers(self, cache_control, + content_disposition, + content_encoding, + content_language, + content_type): + self._add_query(_QueryStringConstants.SIGNED_CACHE_CONTROL, cache_control) + self._add_query(_QueryStringConstants.SIGNED_CONTENT_DISPOSITION, content_disposition) + self._add_query(_QueryStringConstants.SIGNED_CONTENT_ENCODING, content_encoding) + self._add_query(_QueryStringConstants.SIGNED_CONTENT_LANGUAGE, content_language) + self._add_query(_QueryStringConstants.SIGNED_CONTENT_TYPE, content_type) - def add_resource_signature(self, account_name, account_key, path): # pylint: disable=arguments-differ + def add_resource_signature(self, account_name, account_key, path): def get_value_to_append(query): return_value = self.query_dict.get(query) or '' return return_value + '\n' @@ -446,7 +304,7 @@ def get_value_to_append(query): if path[0] != '/': path = '/' + path - canonicalized_resource = '/queue/' + account_name + path + '\n' + canonicalized_resource = '/file/' + account_name + path + '\n' # Form the string to sign from shared_access_policy and canonicalized # resource. The order of values is important. @@ -458,7 +316,12 @@ def get_value_to_append(query): get_value_to_append(_QueryStringConstants.SIGNED_IDENTIFIER) + get_value_to_append(_QueryStringConstants.SIGNED_IP) + get_value_to_append(_QueryStringConstants.SIGNED_PROTOCOL) + - get_value_to_append(_QueryStringConstants.SIGNED_VERSION)) + get_value_to_append(_QueryStringConstants.SIGNED_VERSION) + + get_value_to_append(_QueryStringConstants.SIGNED_CACHE_CONTROL) + + get_value_to_append(_QueryStringConstants.SIGNED_CONTENT_DISPOSITION) + + get_value_to_append(_QueryStringConstants.SIGNED_CONTENT_ENCODING) + + get_value_to_append(_QueryStringConstants.SIGNED_CONTENT_LANGUAGE) + + get_value_to_append(_QueryStringConstants.SIGNED_CONTENT_TYPE)) # remove the trailing newline if string_to_sign[-1] == '\n': @@ -467,177 +330,24 @@ def get_value_to_append(query): self._add_query(_QueryStringConstants.SIGNED_SIGNATURE, _sign_string(account_key, string_to_sign)) + def add_account_signature(self, account_name, account_key): + def get_value_to_append(query): + return_value = self.query_dict.get(query) or '' + return return_value + '\n' + string_to_sign = \ + (account_name + '\n' + + get_value_to_append(_QueryStringConstants.SIGNED_PERMISSION) + + get_value_to_append(_QueryStringConstants.SIGNED_SERVICES) + + get_value_to_append(_QueryStringConstants.SIGNED_RESOURCE_TYPES) + + get_value_to_append(_QueryStringConstants.SIGNED_START) + + get_value_to_append(_QueryStringConstants.SIGNED_EXPIRY) + + get_value_to_append(_QueryStringConstants.SIGNED_IP) + + get_value_to_append(_QueryStringConstants.SIGNED_PROTOCOL) + + get_value_to_append(_QueryStringConstants.SIGNED_VERSION)) -class FileSharedAccessSignature(SharedAccessSignature): - ''' - Provides a factory for creating file and share access - signature tokens with a common account name and account key. Users can either - use the factory or can construct the appropriate service and use the - generate_*_shared_access_signature method directly. - ''' - - def __init__(self, account_name, account_key): - ''' - :param str account_name: - The storage account name used to generate the shared access signatures. - :param str account_key: - The access key to generate the shares access signatures. - ''' - super(FileSharedAccessSignature, self).__init__(account_name, account_key, x_ms_version=X_MS_VERSION) - - def generate_file(self, share_name, directory_name=None, file_name=None, - permission=None, expiry=None, start=None, policy_id=None, - ip=None, protocol=None, cache_control=None, - content_disposition=None, content_encoding=None, - content_language=None, content_type=None): - ''' - Generates a shared access signature for the file. - Use the returned signature with the sas_token parameter of FileService. - - :param str share_name: - Name of share. - :param str directory_name: - Name of directory. SAS tokens cannot be created for directories, so - this parameter should only be present if file_name is provided. - :param str file_name: - Name of file. - :param FilePermissions permission: - The permissions associated with the shared access signature. The - user is restricted to operations allowed by the permissions. - Permissions must be ordered read, create, write, delete, list. - Required unless an id is given referencing a stored access policy - which contains this field. This field must be omitted if it has been - specified in an associated stored access policy. - :param expiry: - The time at which the shared access signature becomes invalid. - Required unless an id is given referencing a stored access policy - which contains this field. This field must be omitted if it has - been specified in an associated stored access policy. Azure will always - convert values to UTC. If a date is passed in without timezone info, it - is assumed to be UTC. - :type expiry: datetime or str - :param start: - The time at which the shared access signature becomes valid. If - omitted, start time for this call is assumed to be the time when the - storage service receives the request. Azure will always convert values - to UTC. If a date is passed in without timezone info, it is assumed to - be UTC. - :type start: datetime or str - :param str policy_id: - A unique value up to 64 characters in length that correlates to a - stored access policy. To create a stored access policy, use - set_file_service_properties. - :param str ip: - Specifies an IP address or a range of IP addresses from which to accept requests. - If the IP address from which the request originates does not match the IP address - or address range specified on the SAS token, the request is not authenticated. - For example, specifying sip=168.1.5.65 or sip=168.1.5.60-168.1.5.70 on the SAS - restricts the request to those IP addresses. - :param str protocol: - Specifies the protocol permitted for a request made. The default value - is https,http. See :class:`~azure.storage.common.models.Protocol` for possible values. - :param str cache_control: - Response header value for Cache-Control when resource is accessed - using this shared access signature. - :param str content_disposition: - Response header value for Content-Disposition when resource is accessed - using this shared access signature. - :param str content_encoding: - Response header value for Content-Encoding when resource is accessed - using this shared access signature. - :param str content_language: - Response header value for Content-Language when resource is accessed - using this shared access signature. - :param str content_type: - Response header value for Content-Type when resource is accessed - using this shared access signature. - ''' - resource_path = share_name - if directory_name is not None: - resource_path += '/' + _str(directory_name) if directory_name is not None else None - resource_path += '/' + _str(file_name) if file_name is not None else None - - sas = _SharedAccessHelper() - sas.add_base(permission, expiry, start, ip, protocol, self.x_ms_version) - sas.add_id(policy_id) - sas.add_resource('f') - sas.add_override_response_headers(cache_control, content_disposition, - content_encoding, content_language, - content_type) - sas.add_resource_signature(self.account_name, self.account_key, 'file', resource_path) - - return sas.get_token() - - def generate_share(self, share_name, permission=None, expiry=None, - start=None, policy_id=None, ip=None, protocol=None, - cache_control=None, content_disposition=None, - content_encoding=None, content_language=None, - content_type=None): - ''' - Generates a shared access signature for the share. - Use the returned signature with the sas_token parameter of FileService. - - :param str share_name: - Name of share. - :param SharePermissions permission: - The permissions associated with the shared access signature. The - user is restricted to operations allowed by the permissions. - Permissions must be ordered read, create, write, delete, list. - Required unless an id is given referencing a stored access policy - which contains this field. This field must be omitted if it has been - specified in an associated stored access policy. - :param expiry: - The time at which the shared access signature becomes invalid. - Required unless an id is given referencing a stored access policy - which contains this field. This field must be omitted if it has - been specified in an associated stored access policy. Azure will always - convert values to UTC. If a date is passed in without timezone info, it - is assumed to be UTC. - :type expiry: datetime or str - :param start: - The time at which the shared access signature becomes valid. If - omitted, start time for this call is assumed to be the time when the - storage service receives the request. Azure will always convert values - to UTC. If a date is passed in without timezone info, it is assumed to - be UTC. - :type start: datetime or str - :param str policy_id: - A unique value up to 64 characters in length that correlates to a - stored access policy. To create a stored access policy, use - set_file_service_properties. - :param str ip: - Specifies an IP address or a range of IP addresses from which to accept requests. - If the IP address from which the request originates does not match the IP address - or address range specified on the SAS token, the request is not authenticated. - For example, specifying sip=168.1.5.65 or sip=168.1.5.60-168.1.5.70 on the SAS - restricts the request to those IP addresses. - :param str protocol: - Specifies the protocol permitted for a request made. The default value - is https,http. See :class:`~azure.storage.common.models.Protocol` for possible values. - :param str cache_control: - Response header value for Cache-Control when resource is accessed - using this shared access signature. - :param str content_disposition: - Response header value for Content-Disposition when resource is accessed - using this shared access signature. - :param str content_encoding: - Response header value for Content-Encoding when resource is accessed - using this shared access signature. - :param str content_language: - Response header value for Content-Language when resource is accessed - using this shared access signature. - :param str content_type: - Response header value for Content-Type when resource is accessed - using this shared access signature. - ''' - sas = _SharedAccessHelper() - sas.add_base(permission, expiry, start, ip, protocol, self.x_ms_version) - sas.add_id(policy_id) - sas.add_resource('s') - sas.add_override_response_headers(cache_control, content_disposition, - content_encoding, content_language, - content_type) - sas.add_resource_signature(self.account_name, self.account_key, 'file', share_name) + self._add_query(_QueryStringConstants.SIGNED_SIGNATURE, + _sign_string(account_key, string_to_sign)) - return sas.get_token() + def get_token(self): + return '&'.join(['{0}={1}'.format(n, url_quote(v)) for n, v in self.query_dict.items() if v is not None]) diff --git a/sdk/storage/azure-storage-file/azure/storage/file/file_client.py b/sdk/storage/azure-storage-file/azure/storage/file/file_client.py index ec492fe25197..0e7382f1e39c 100644 --- a/sdk/storage/azure-storage-file/azure/storage/file/file_client.py +++ b/sdk/storage/azure-storage-file/azure/storage/file/file_client.py @@ -24,7 +24,7 @@ from ._generated.version import VERSION from ._generated.models import StorageErrorException, FileHTTPHeaders from ._shared.upload_chunking import IterStreamer -from ._shared.shared_access_signature import FileSharedAccessSignature +from ._shared.shared_access_signature import SharedAccessSignature from ._shared.utils import ( StorageAccountHostsMixin, parse_query, @@ -257,7 +257,7 @@ def generate_shared_access_signature( """ if not hasattr(self.credential, 'account_key') or not self.credential.account_key: raise ValueError("No account SAS key available.") - sas = FileSharedAccessSignature(self.credential.account_name, self.credential.account_key) + sas = SharedAccessSignature(self.credential.account_name, self.credential.account_key) if len(self.file_path) > 1: file_path = '/'.join(self.file_path[:-1]) else: diff --git a/sdk/storage/azure-storage-file/azure/storage/file/share_client.py b/sdk/storage/azure-storage-file/azure/storage/file/share_client.py index 28fb30aea66f..33936ff3ac64 100644 --- a/sdk/storage/azure-storage-file/azure/storage/file/share_client.py +++ b/sdk/storage/azure-storage-file/azure/storage/file/share_client.py @@ -15,7 +15,7 @@ import six -from ._shared.shared_access_signature import FileSharedAccessSignature +from ._shared.shared_access_signature import SharedAccessSignature from .directory_client import DirectoryClient from .file_client import FileClient from ._generated import AzureFileStorage @@ -241,7 +241,7 @@ def generate_shared_access_signature( """ if not hasattr(self.credential, 'account_key') or not self.credential.account_key: raise ValueError("No account SAS key available.") - sas = FileSharedAccessSignature(self.credential.account_name, self.credential.account_key) + sas = SharedAccessSignature(self.credential.account_name, self.credential.account_key) return sas.generate_share( self.share_name, permission, diff --git a/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/shared_access_signature.py b/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/shared_access_signature.py index cad3f270600b..8cde66659498 100644 --- a/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/shared_access_signature.py +++ b/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/shared_access_signature.py @@ -96,6 +96,56 @@ def generate_account(self, services, resource_types, permission, expiry, start=N return sas.get_token() + def generate_queue(self, queue_name, permission=None, + expiry=None, start=None, policy_id=None, + ip=None, protocol=None): + ''' + Generates a shared access signature for the queue. + Use the returned signature with the sas_token parameter of QueueService. + :param str queue_name: + Name of queue. + :param QueuePermissions permission: + The permissions associated with the shared access signature. The + user is restricted to operations allowed by the permissions. + Permissions must be ordered read, add, update, process. + Required unless an id is given referencing a stored access policy + which contains this field. This field must be omitted if it has been + specified in an associated stored access policy. + :param expiry: + The time at which the shared access signature becomes invalid. + Required unless an id is given referencing a stored access policy + which contains this field. This field must be omitted if it has + been specified in an associated stored access policy. Azure will always + convert values to UTC. If a date is passed in without timezone info, it + is assumed to be UTC. + :type expiry: datetime or str + :param start: + The time at which the shared access signature becomes valid. If + omitted, start time for this call is assumed to be the time when the + storage service receives the request. Azure will always convert values + to UTC. If a date is passed in without timezone info, it is assumed to + be UTC. + :type start: datetime or str + :param str policy_id: + A unique value up to 64 characters in length that correlates to a + stored access policy. + :param str ip: + Specifies an IP address or a range of IP addresses from which to accept requests. + If the IP address from which the request originates does not match the IP address + or address range specified on the SAS token, the request is not authenticated. + For example, specifying sip=168.1.5.65 or sip=168.1.5.60-168.1.5.70 on the SAS + restricts the request to those IP addresses. + :param str protocol: + Specifies the protocol permitted for a request made. The default value + is https,http. See :class:`~azure.storage.common.models.Protocol` for possible values. + ''' + sas = _SharedAccessHelper() + sas.add_base(permission, expiry, start, ip, protocol, self.x_ms_version) + sas.add_id(policy_id) + sas.add_resource_signature(self.account_name, self.account_key, queue_name) + + return sas.get_token() + class _SharedAccessHelper(object): def __init__(self): @@ -140,7 +190,7 @@ def add_override_response_headers(self, cache_control, self._add_query(_QueryStringConstants.SIGNED_CONTENT_LANGUAGE, content_language) self._add_query(_QueryStringConstants.SIGNED_CONTENT_TYPE, content_type) - def add_resource_signature(self, account_name, account_key, service, path): + def add_resource_signature(self, account_name, account_key, path): # pylint: disable=arguments-differ def get_value_to_append(query): return_value = self.query_dict.get(query) or '' return return_value + '\n' @@ -148,7 +198,7 @@ def get_value_to_append(query): if path[0] != '/': path = '/' + path - canonicalized_resource = '/' + service + '/' + account_name + path + '\n' + canonicalized_resource = '/queue/' + account_name + path + '\n' # Form the string to sign from shared_access_policy and canonicalized # resource. The order of values is important. @@ -162,14 +212,6 @@ def get_value_to_append(query): get_value_to_append(_QueryStringConstants.SIGNED_PROTOCOL) + get_value_to_append(_QueryStringConstants.SIGNED_VERSION)) - if service in ['blob', 'file']: - string_to_sign += \ - (get_value_to_append(_QueryStringConstants.SIGNED_CACHE_CONTROL) + - get_value_to_append(_QueryStringConstants.SIGNED_CONTENT_DISPOSITION) + - get_value_to_append(_QueryStringConstants.SIGNED_CONTENT_ENCODING) + - get_value_to_append(_QueryStringConstants.SIGNED_CONTENT_LANGUAGE) + - get_value_to_append(_QueryStringConstants.SIGNED_CONTENT_TYPE)) - # remove the trailing newline if string_to_sign[-1] == '\n': string_to_sign = string_to_sign[:-1] @@ -198,271 +240,3 @@ def get_value_to_append(query): def get_token(self): return '&'.join(['{0}={1}'.format(n, url_quote(v)) for n, v in self.query_dict.items() if v is not None]) - - -class BlobSharedAccessSignature(SharedAccessSignature): - ''' - Provides a factory for creating blob and container access - signature tokens with a common account name and account key. Users can either - use the factory or can construct the appropriate service and use the - generate_*_shared_access_signature method directly. - ''' - - def __init__(self, account_name, account_key): - ''' - :param str account_name: - The storage account name used to generate the shared access signatures. - :param str account_key: - The access key to generate the shares access signatures. - ''' - super(BlobSharedAccessSignature, self).__init__(account_name, account_key, x_ms_version=X_MS_VERSION) - - def generate_blob(self, container_name, blob_name, permission=None, - expiry=None, start=None, policy_id=None, ip=None, protocol=None, - cache_control=None, content_disposition=None, - content_encoding=None, content_language=None, - content_type=None): - ''' - Generates a shared access signature for the blob. - Use the returned signature with the sas_token parameter of any BlobService. - - :param str container_name: - Name of container. - :param str blob_name: - Name of blob. - :param BlobPermissions permission: - The permissions associated with the shared access signature. The - user is restricted to operations allowed by the permissions. - Permissions must be ordered read, write, delete, list. - Required unless an id is given referencing a stored access policy - which contains this field. This field must be omitted if it has been - specified in an associated stored access policy. - :param expiry: - The time at which the shared access signature becomes invalid. - Required unless an id is given referencing a stored access policy - which contains this field. This field must be omitted if it has - been specified in an associated stored access policy. Azure will always - convert values to UTC. If a date is passed in without timezone info, it - is assumed to be UTC. - :type expiry: datetime or str - :param start: - The time at which the shared access signature becomes valid. If - omitted, start time for this call is assumed to be the time when the - storage service receives the request. Azure will always convert values - to UTC. If a date is passed in without timezone info, it is assumed to - be UTC. - :type start: datetime or str - :param str id: - A unique value up to 64 characters in length that correlates to a - stored access policy. To create a stored access policy, use - set_blob_service_properties. - :param str ip: - Specifies an IP address or a range of IP addresses from which to accept requests. - If the IP address from which the request originates does not match the IP address - or address range specified on the SAS token, the request is not authenticated. - For example, specifying sip=168.1.5.65 or sip=168.1.5.60-168.1.5.70 on the SAS - restricts the request to those IP addresses. - :param str protocol: - Specifies the protocol permitted for a request made. The default value - is https,http. See :class:`~azure.storage.common.models.Protocol` for possible values. - :param str cache_control: - Response header value for Cache-Control when resource is accessed - using this shared access signature. - :param str content_disposition: - Response header value for Content-Disposition when resource is accessed - using this shared access signature. - :param str content_encoding: - Response header value for Content-Encoding when resource is accessed - using this shared access signature. - :param str content_language: - Response header value for Content-Language when resource is accessed - using this shared access signature. - :param str content_type: - Response header value for Content-Type when resource is accessed - using this shared access signature. - ''' - resource_path = container_name + '/' + blob_name - - sas = _SharedAccessHelper() - sas.add_base(permission, expiry, start, ip, protocol, self.x_ms_version) - sas.add_id(policy_id) - sas.add_resource('b') - sas.add_override_response_headers(cache_control, content_disposition, - content_encoding, content_language, - content_type) - sas.add_resource_signature(self.account_name, self.account_key, 'blob', resource_path) - - return sas.get_token() - - def generate_container(self, container_name, permission=None, expiry=None, - start=None, policy_id=None, ip=None, protocol=None, - cache_control=None, content_disposition=None, - content_encoding=None, content_language=None, - content_type=None): - ''' - Generates a shared access signature for the container. - Use the returned signature with the sas_token parameter of any BlobService. - - :param str container_name: - Name of container. - :param ContainerPermissions permission: - The permissions associated with the shared access signature. The - user is restricted to operations allowed by the permissions. - Permissions must be ordered read, write, delete, list. - Required unless an id is given referencing a stored access policy - which contains this field. This field must be omitted if it has been - specified in an associated stored access policy. - :param expiry: - The time at which the shared access signature becomes invalid. - Required unless an id is given referencing a stored access policy - which contains this field. This field must be omitted if it has - been specified in an associated stored access policy. Azure will always - convert values to UTC. If a date is passed in without timezone info, it - is assumed to be UTC. - :type expiry: datetime or str - :param start: - The time at which the shared access signature becomes valid. If - omitted, start time for this call is assumed to be the time when the - storage service receives the request. Azure will always convert values - to UTC. If a date is passed in without timezone info, it is assumed to - be UTC. - :type start: datetime or str - :param str policy_id: - A unique value up to 64 characters in length that correlates to a - stored access policy. To create a stored access policy, use - set_blob_service_properties. - :param str ip: - Specifies an IP address or a range of IP addresses from which to accept requests. - If the IP address from which the request originates does not match the IP address - or address range specified on the SAS token, the request is not authenticated. - For example, specifying sip=168.1.5.65 or sip=168.1.5.60-168.1.5.70 on the SAS - restricts the request to those IP addresses. - :param str protocol: - Specifies the protocol permitted for a request made. The default value - is https,http. See :class:`~azure.storage.common.models.Protocol` for possible values. - :param str cache_control: - Response header value for Cache-Control when resource is accessed - using this shared access signature. - :param str content_disposition: - Response header value for Content-Disposition when resource is accessed - using this shared access signature. - :param str content_encoding: - Response header value for Content-Encoding when resource is accessed - using this shared access signature. - :param str content_language: - Response header value for Content-Language when resource is accessed - using this shared access signature. - :param str content_type: - Response header value for Content-Type when resource is accessed - using this shared access signature. - ''' - sas = _SharedAccessHelper() - sas.add_base(permission, expiry, start, ip, protocol, self.x_ms_version) - sas.add_id(policy_id) - sas.add_resource('c') - sas.add_override_response_headers(cache_control, content_disposition, - content_encoding, content_language, - content_type) - sas.add_resource_signature(self.account_name, self.account_key, 'blob', container_name) - - return sas.get_token() - - -class QueueSharedAccessSignature(SharedAccessSignature): - ''' - Provides a factory for creating queue shares access - signature tokens with a common account name and account key. Users can either - use the factory or can construct the appropriate service and use the - generate_*_shared_access_signature method directly. - ''' - - def __init__(self, account_name, account_key): - ''' - :param str account_name: - The storage account name used to generate the shared access signatures. - :param str account_key: - The access key to generate the shares access signatures. - ''' - super(QueueSharedAccessSignature, self).__init__(account_name, account_key, x_ms_version=X_MS_VERSION) - - def generate_queue(self, queue_name, permission=None, - expiry=None, start=None, policy_id=None, - ip=None, protocol=None): - ''' - Generates a shared access signature for the queue. - Use the returned signature with the sas_token parameter of QueueService. - :param str queue_name: - Name of queue. - :param QueuePermissions permission: - The permissions associated with the shared access signature. The - user is restricted to operations allowed by the permissions. - Permissions must be ordered read, add, update, process. - Required unless an id is given referencing a stored access policy - which contains this field. This field must be omitted if it has been - specified in an associated stored access policy. - :param expiry: - The time at which the shared access signature becomes invalid. - Required unless an id is given referencing a stored access policy - which contains this field. This field must be omitted if it has - been specified in an associated stored access policy. Azure will always - convert values to UTC. If a date is passed in without timezone info, it - is assumed to be UTC. - :type expiry: datetime or str - :param start: - The time at which the shared access signature becomes valid. If - omitted, start time for this call is assumed to be the time when the - storage service receives the request. Azure will always convert values - to UTC. If a date is passed in without timezone info, it is assumed to - be UTC. - :type start: datetime or str - :param str policy_id: - A unique value up to 64 characters in length that correlates to a - stored access policy. - :param str ip: - Specifies an IP address or a range of IP addresses from which to accept requests. - If the IP address from which the request originates does not match the IP address - or address range specified on the SAS token, the request is not authenticated. - For example, specifying sip=168.1.5.65 or sip=168.1.5.60-168.1.5.70 on the SAS - restricts the request to those IP addresses. - :param str protocol: - Specifies the protocol permitted for a request made. The default value - is https,http. See :class:`~azure.storage.common.models.Protocol` for possible values. - ''' - sas = _QueueSharedAccessHelper() - sas.add_base(permission, expiry, start, ip, protocol, self.x_ms_version) - sas.add_id(policy_id) - sas.add_resource_signature(self.account_name, self.account_key, queue_name) - - return sas.get_token() - - -class _QueueSharedAccessHelper(_SharedAccessHelper): - - def add_resource_signature(self, account_name, account_key, path): # pylint: disable=arguments-differ - def get_value_to_append(query): - return_value = self.query_dict.get(query) or '' - return return_value + '\n' - - if path[0] != '/': - path = '/' + path - - canonicalized_resource = '/queue/' + account_name + path + '\n' - - # Form the string to sign from shared_access_policy and canonicalized - # resource. The order of values is important. - string_to_sign = \ - (get_value_to_append(_QueryStringConstants.SIGNED_PERMISSION) + - get_value_to_append(_QueryStringConstants.SIGNED_START) + - get_value_to_append(_QueryStringConstants.SIGNED_EXPIRY) + - canonicalized_resource + - get_value_to_append(_QueryStringConstants.SIGNED_IDENTIFIER) + - get_value_to_append(_QueryStringConstants.SIGNED_IP) + - get_value_to_append(_QueryStringConstants.SIGNED_PROTOCOL) + - get_value_to_append(_QueryStringConstants.SIGNED_VERSION)) - - # remove the trailing newline - if string_to_sign[-1] == '\n': - string_to_sign = string_to_sign[:-1] - - self._add_query(_QueryStringConstants.SIGNED_SIGNATURE, - _sign_string(account_key, string_to_sign)) diff --git a/sdk/storage/azure-storage-queue/azure/storage/queue/queue_client.py b/sdk/storage/azure-storage-queue/azure/storage/queue/queue_client.py index a8d58b638f73..97559573ec6b 100644 --- a/sdk/storage/azure-storage-queue/azure/storage/queue/queue_client.py +++ b/sdk/storage/azure-storage-queue/azure/storage/queue/queue_client.py @@ -16,7 +16,7 @@ import six -from ._shared.shared_access_signature import QueueSharedAccessSignature +from ._shared.shared_access_signature import SharedAccessSignature from ._shared.utils import ( StorageAccountHostsMixin, add_metadata_headers, @@ -224,7 +224,7 @@ def generate_shared_access_signature( """ if not hasattr(self.credential, 'account_key') and not self.credential.account_key: raise ValueError("No account SAS key available.") - sas = QueueSharedAccessSignature( + sas = SharedAccessSignature( self.credential.account_name, self.credential.account_key) return sas.generate_queue( self.queue_name,