From 2ac88dda5d02c4d5c9baf22a015805c84975b619 Mon Sep 17 00:00:00 2001 From: xiafu Date: Mon, 22 Jul 2019 15:48:32 -0700 Subject: [PATCH 1/5] [SnapshotSAS]Add Snapshot SAS --- .../azure/storage/blob/_shared/constants.py | 2 +- .../blob/_shared/shared_access_signature.py | 494 +++++------------ .../azure/storage/blob/blob_client.py | 8 +- .../azure/storage/blob/container_client.py | 4 +- .../tests/test_common_blob.py | 31 ++ .../file/_shared/shared_access_signature.py | 492 ++++------------- .../azure/storage/file/file_client.py | 4 +- .../azure/storage/file/share_client.py | 4 +- .../queue/_shared/shared_access_signature.py | 505 ++---------------- .../azure/storage/queue/queue_client.py | 4 +- 10 files changed, 332 insertions(+), 1216 deletions(-) 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 16ff778c5c1e..84eb95d0ca76 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 @@ -46,6 +46,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(): @@ -145,110 +152,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 @@ -256,28 +160,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. @@ -300,7 +212,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. @@ -331,14 +243,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() @@ -404,288 +318,150 @@ 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) + + 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) -class _QueueSharedAccessHelper(_SharedAccessHelper): + 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_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' - 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) + + (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) + - 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)) + 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 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. - ''' +class _ResourceSharedAccessHelper(_SharedAccessHelper): + def __init__(self): + super(_ResourceSharedAccessHelper, self).__init__() - 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 add_timestamp(self, timestamp): + self._add_query(QueryStringConstants.SIGNED_TIMESTAMP, timestamp) - 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 + def get_value_to_append(self, query): + return_value = self.query_dict.get(query) or '' + return return_value + '\n' - 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) + def add_resource_signature(self, account_name, account_key, path, user_delegation_key=None): + if path[0] != '/': + path = '/' + path - return sas.get_token() + canonicalized_resource = '/blob/' + account_name + path + '\n' - 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. + # Form the string to sign from shared_access_policy and canonicalized + # resource. The order of values is important. + string_to_sign = \ + (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) - :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) + 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)) - return sas.get_token() + # 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 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/blob_client.py b/sdk/storage/azure-storage-blob/azure/storage/blob/blob_client.py index 3dd942365967..ff8a5b1b0d3f 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 @@ -21,8 +21,8 @@ from ._shared import encode_base64 from ._shared.base_client import StorageAccountHostsMixin, parse_connection_str, parse_query -from ._shared.shared_access_signature import BlobSharedAccessSignature from ._shared.encryption import generate_blob_encryption_data +from ._shared.shared_access_signature import ResourceSharedAccessSignature from ._shared.uploads import IterStreamer from ._shared.downloads import StorageStreamDownloader from ._shared.request_handlers import ( @@ -294,12 +294,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 c2756c4f8fb9..ad6ef71ff2b7 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 @@ -21,7 +21,7 @@ import six -from ._shared.shared_access_signature import BlobSharedAccessSignature +from ._shared.shared_access_signature import ResourceSharedAccessSignature from ._shared.base_client import StorageAccountHostsMixin, parse_connection_str, parse_query from ._shared.request_handlers import add_metadata_headers, serialize_iso from ._shared.response_handlers import ( @@ -261,7 +261,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/tests/test_common_blob.py b/sdk/storage/azure-storage-blob/tests/test_common_blob.py index db4870033f13..b0ad55c71d5f 100644 --- a/sdk/storage/azure-storage-blob/tests/test_common_blob.py +++ b/sdk/storage/azure-storage-blob/tests/test_common_blob.py @@ -1175,6 +1175,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 16ff778c5c1e..22fcef64f287 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 @@ -144,144 +144,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. @@ -300,10 +182,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 @@ -329,34 +211,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. @@ -378,7 +263,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 @@ -407,86 +292,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' @@ -494,7 +352,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. @@ -506,7 +364,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': @@ -515,177 +378,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 9ff3cf9b22e1..041c20ecff78 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 @@ -26,13 +26,13 @@ from ._generated.models import StorageErrorException, FileHTTPHeaders from ._shared.uploads import IterStreamer, FileChunkUploader, upload_data_chunks from ._shared.downloads import StorageStreamDownloader -from ._shared.shared_access_signature import FileSharedAccessSignature from ._shared.base_client import StorageAccountHostsMixin, parse_connection_str, parse_query from ._shared.request_handlers import add_metadata_headers, get_length from ._shared.response_handlers import return_response_headers, process_storage_error from ._deserialize import deserialize_file_properties, deserialize_file_stream from ._polling import CloseHandles from .models import HandlesPaged +from ._shared.shared_access_signature import SharedAccessSignature if TYPE_CHECKING: from datetime import datetime @@ -294,7 +294,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 4fd917d31c3e..7ed7ddc0b5cf 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,13 +15,13 @@ import six from azure.core.tracing.decorator import distributed_trace -from ._shared.shared_access_signature import FileSharedAccessSignature from ._shared.base_client import StorageAccountHostsMixin, parse_connection_str, parse_query from ._shared.request_handlers import add_metadata_headers, serialize_iso from ._shared.response_handlers import ( return_response_headers, process_storage_error, return_headers_and_deserialized) +from ._shared.shared_access_signature import SharedAccessSignature from ._generated import AzureFileStorage from ._generated.version import VERSION from ._generated.models import ( @@ -237,7 +237,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 16ff778c5c1e..2bd663b81e63 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 @@ -144,6 +144,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): @@ -188,7 +238,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' @@ -196,7 +246,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. @@ -210,14 +260,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] @@ -246,446 +288,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)) - - - -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) - - return sas.get_token() 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 71df413b9af0..fa024c067c7a 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 @@ -18,13 +18,13 @@ from azure.core.paging import ItemPaged from azure.core.tracing.decorator import distributed_trace -from ._shared.shared_access_signature import QueueSharedAccessSignature from ._shared.base_client import StorageAccountHostsMixin, parse_connection_str, parse_query from ._shared.request_handlers import add_metadata_headers, serialize_iso from ._shared.response_handlers import ( process_storage_error, return_response_headers, return_headers_and_deserialized) +from ._shared.shared_access_signature import SharedAccessSignature from ._message_encoding import TextXMLEncodePolicy, TextXMLDecodePolicy from ._deserialize import deserialize_queue_properties, deserialize_queue_creation from ._generated import AzureQueueStorage @@ -219,7 +219,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, From 7a018471fd0fba8e2ace588fb53d81ee1ad9049b Mon Sep 17 00:00:00 2001 From: xiafu Date: Mon, 19 Aug 2019 16:33:53 -0700 Subject: [PATCH 2/5] [SnapshotSAS]Extract SharedAccessSignature for Blob File and Queue --- .../blob/_shared/shared_access_signature.py | 247 ----------------- .../azure/storage/blob/blob_client.py | 5 +- .../azure/storage/blob/container_client.py | 4 +- .../storage/blob/shared_access_signature.py | 253 ++++++++++++++++++ .../file/_shared/shared_access_signature.py | 190 ------------- .../azure/storage/file/file_client.py | 4 +- .../azure/storage/file/share_client.py | 4 +- .../storage/file/shared_access_signature.py | 217 +++++++++++++++ .../queue/_shared/shared_access_signature.py | 79 ------ .../azure/storage/queue/queue_client.py | 4 +- .../storage/queue/shared_access_signature.py | 106 ++++++++ 11 files changed, 587 insertions(+), 526 deletions(-) create mode 100644 sdk/storage/azure-storage-blob/azure/storage/blob/shared_access_signature.py create mode 100644 sdk/storage/azure-storage-file/azure/storage/file/shared_access_signature.py create mode 100644 sdk/storage/azure-storage-queue/azure/storage/queue/shared_access_signature.py 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 84eb95d0ca76..79224299ff27 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 @@ -152,184 +152,6 @@ def generate_account(self, services, resource_types, permission, expiry, start=N return sas.get_token() -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 - 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=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(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, 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 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. - 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. - ''' - resource_path = container_name + '/' + blob_name - - sas = _ResourceSharedAccessHelper() - sas.add_base(permission, expiry, start, ip, protocol, self.x_ms_version) - sas.add_id(policy_id) - 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, resource_path, - user_delegation_key=self.user_delegation_key) - - 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 = _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, container_name, - user_delegation_key=None) - return sas.get_token() - - class _SharedAccessHelper(object): def __init__(self): self.query_dict = {} @@ -396,72 +218,3 @@ 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 = '/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 = \ - (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 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/blob_client.py b/sdk/storage/azure-storage-blob/azure/storage/blob/blob_client.py index ff8a5b1b0d3f..ed1d68d79cba 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 @@ -22,7 +22,7 @@ from ._shared import encode_base64 from ._shared.base_client import StorageAccountHostsMixin, parse_connection_str, parse_query from ._shared.encryption import generate_blob_encryption_data -from ._shared.shared_access_signature import ResourceSharedAccessSignature +from azure.storage.blob.shared_access_signature import BlobSharedAccessSignature from ._shared.uploads import IterStreamer from ._shared.downloads import StorageStreamDownloader from ._shared.request_handlers import ( @@ -294,10 +294,11 @@ 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 = ResourceSharedAccessSignature(self.credential.account_name, self.credential.account_key) + sas = BlobSharedAccessSignature(self.credential.account_name, self.credential.account_key) return sas.generate_blob( self.container_name, self.blob_name, + snapshot=self.snapshot, permission=permission, expiry=expiry, start=start, 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 ad6ef71ff2b7..820bf0d901f3 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 @@ -21,7 +21,7 @@ import six -from ._shared.shared_access_signature import ResourceSharedAccessSignature +from azure.storage.blob.shared_access_signature import BlobSharedAccessSignature from ._shared.base_client import StorageAccountHostsMixin, parse_connection_str, parse_query from ._shared.request_handlers import add_metadata_headers, serialize_iso from ._shared.response_handlers import ( @@ -261,7 +261,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 = ResourceSharedAccessSignature(self.credential.account_name, self.credential.account_key) + sas = BlobSharedAccessSignature(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/shared_access_signature.py b/sdk/storage/azure-storage-blob/azure/storage/blob/shared_access_signature.py new file mode 100644 index 000000000000..8825652f657d --- /dev/null +++ b/sdk/storage/azure-storage-blob/azure/storage/blob/shared_access_signature.py @@ -0,0 +1,253 @@ +from azure.storage.blob._shared import sign_string, url_quote +from azure.storage.blob._shared.constants import X_MS_VERSION +from azure.storage.blob._shared.shared_access_signature import SharedAccessSignature, _SharedAccessHelper, \ + QueryStringConstants + + +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=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) + self.user_delegation_key = user_delegation_key + + 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 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. + 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. + ''' + resource_path = container_name + '/' + blob_name + + sas = _BlobSharedAccessHelper() + sas.add_base(permission, expiry, start, ip, protocol, self.x_ms_version) + sas.add_id(policy_id) + 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, resource_path, + user_delegation_key=self.user_delegation_key) + + 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 = _BlobSharedAccessHelper() + 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, container_name, + user_delegation_key=None) + return sas.get_token() + + +class _BlobSharedAccessHelper(_SharedAccessHelper): + def __init__(self): + super(_BlobSharedAccessHelper, 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 = '/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 = \ + (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 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]) \ No newline at end of file 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 22fcef64f287..3c28feb9432e 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 @@ -144,162 +144,6 @@ def generate_account(self, services, resource_types, permission, expiry, start=N return sas.get_token() - 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, 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, share_name) - - return sas.get_token() - class _SharedAccessHelper(object): def __init__(self): @@ -344,40 +188,6 @@ 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, 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 = '/file/' + 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) + - 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 '' 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 041c20ecff78..350e09af3ac5 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 @@ -32,7 +32,7 @@ from ._deserialize import deserialize_file_properties, deserialize_file_stream from ._polling import CloseHandles from .models import HandlesPaged -from ._shared.shared_access_signature import SharedAccessSignature +from azure.storage.file.shared_access_signature import FileSharedAccessSignature if TYPE_CHECKING: from datetime import datetime @@ -294,7 +294,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 = SharedAccessSignature(self.credential.account_name, self.credential.account_key) + sas = FileSharedAccessSignature(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 7ed7ddc0b5cf..5b54fad4bc0d 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 @@ -21,7 +21,7 @@ return_response_headers, process_storage_error, return_headers_and_deserialized) -from ._shared.shared_access_signature import SharedAccessSignature +from .shared_access_signature import FileSharedAccessSignature from ._generated import AzureFileStorage from ._generated.version import VERSION from ._generated.models import ( @@ -237,7 +237,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 = SharedAccessSignature(self.credential.account_name, self.credential.account_key) + sas = FileSharedAccessSignature(self.credential.account_name, self.credential.account_key) return sas.generate_share( self.share_name, permission, diff --git a/sdk/storage/azure-storage-file/azure/storage/file/shared_access_signature.py b/sdk/storage/azure-storage-file/azure/storage/file/shared_access_signature.py new file mode 100644 index 000000000000..858d96e2a5f2 --- /dev/null +++ b/sdk/storage/azure-storage-file/azure/storage/file/shared_access_signature.py @@ -0,0 +1,217 @@ +from azure.storage.file._shared import sign_string +from azure.storage.file._shared.constants import X_MS_VERSION +from azure.storage.file._shared.shared_access_signature import SharedAccessSignature, _str, _SharedAccessHelper, \ + QueryStringConstants + + +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 = _FileSharedAccessHelper() + 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, 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 = _FileSharedAccessHelper() + 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, share_name) + + return sas.get_token() + + +class _FileSharedAccessHelper(_SharedAccessHelper): + def __init__(self): + super(_FileSharedAccessHelper, self).__init__() + + 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' + + if path[0] != '/': + path = '/' + path + + 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. + 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) + + 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)) \ No newline at end of file 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 2bd663b81e63..3c28feb9432e 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 @@ -144,56 +144,6 @@ 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): @@ -238,35 +188,6 @@ 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, 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)) - def add_account_signature(self, account_name, account_key): def get_value_to_append(query): return_value = self.query_dict.get(query) or '' 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 fa024c067c7a..3a2ba42f72f4 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 @@ -24,7 +24,7 @@ process_storage_error, return_response_headers, return_headers_and_deserialized) -from ._shared.shared_access_signature import SharedAccessSignature +from .shared_access_signature import QueueSharedAccessSignature from ._message_encoding import TextXMLEncodePolicy, TextXMLDecodePolicy from ._deserialize import deserialize_queue_properties, deserialize_queue_creation from ._generated import AzureQueueStorage @@ -219,7 +219,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 = SharedAccessSignature( + sas = QueueSharedAccessSignature( self.credential.account_name, self.credential.account_key) return sas.generate_queue( self.queue_name, diff --git a/sdk/storage/azure-storage-queue/azure/storage/queue/shared_access_signature.py b/sdk/storage/azure-storage-queue/azure/storage/queue/shared_access_signature.py new file mode 100644 index 000000000000..6c37db665c39 --- /dev/null +++ b/sdk/storage/azure-storage-queue/azure/storage/queue/shared_access_signature.py @@ -0,0 +1,106 @@ +from azure.storage.queue._shared import sign_string +from azure.storage.queue._shared.constants import X_MS_VERSION +from azure.storage.queue._shared.shared_access_signature import SharedAccessSignature, _SharedAccessHelper, \ + QueryStringConstants + + +class QueueSharedAccessSignature(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(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 __init__(self): + super(_QueueSharedAccessHelper, self).__init__() + + 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)) \ No newline at end of file From f1e9f70433ee47393b3361bc54d7291f7e4d234b Mon Sep 17 00:00:00 2001 From: xiafu Date: Mon, 19 Aug 2019 18:32:04 -0700 Subject: [PATCH 3/5] [SnapshotSAS]Fix Typo --- .../azure/storage/queue/shared_access_signature.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/storage/azure-storage-queue/azure/storage/queue/shared_access_signature.py b/sdk/storage/azure-storage-queue/azure/storage/queue/shared_access_signature.py index 6c37db665c39..3316bf237001 100644 --- a/sdk/storage/azure-storage-queue/azure/storage/queue/shared_access_signature.py +++ b/sdk/storage/azure-storage-queue/azure/storage/queue/shared_access_signature.py @@ -6,7 +6,7 @@ class QueueSharedAccessSignature(SharedAccessSignature): ''' - Provides a factory for creating file and share access + 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. From 4118167eb82ba452904f802ee35d091303e73819 Mon Sep 17 00:00:00 2001 From: xiafu Date: Tue, 20 Aug 2019 16:34:36 -0700 Subject: [PATCH 4/5] [SnapshotSAS]Delete Redundant code The removed code is for another feature. --- .../storage/blob/_shared/shared_access_signature.py | 9 --------- .../azure/storage/blob/shared_access_signature.py | 10 +++++++--- 2 files changed, 7 insertions(+), 12 deletions(-) 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 79224299ff27..3c28feb9432e 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 @@ -46,13 +46,6 @@ 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(): @@ -216,5 +209,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]) - - diff --git a/sdk/storage/azure-storage-blob/azure/storage/blob/shared_access_signature.py b/sdk/storage/azure-storage-blob/azure/storage/blob/shared_access_signature.py index 8825652f657d..f4790d47480d 100644 --- a/sdk/storage/azure-storage-blob/azure/storage/blob/shared_access_signature.py +++ b/sdk/storage/azure-storage-blob/azure/storage/blob/shared_access_signature.py @@ -4,6 +4,10 @@ QueryStringConstants +class BlobQueryStringConstants(object): + SIGNED_TIMESTAMP = 'snapshot' + + class BlobSharedAccessSignature(SharedAccessSignature): ''' Provides a factory for creating blob and container access @@ -187,7 +191,7 @@ def __init__(self): super(_BlobSharedAccessHelper, self).__init__() def add_timestamp(self, timestamp): - self._add_query(QueryStringConstants.SIGNED_TIMESTAMP, timestamp) + self._add_query(BlobQueryStringConstants.SIGNED_TIMESTAMP, timestamp) def get_value_to_append(self, query): return_value = self.query_dict.get(query) or '' @@ -230,7 +234,7 @@ def add_resource_signature(self, account_name, account_key, path, user_delegatio 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(BlobQueryStringConstants.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) + @@ -248,6 +252,6 @@ def add_resource_signature(self, account_name, account_key, path, user_delegatio 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] + exclude = [BlobQueryStringConstants.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]) \ No newline at end of file From 8945d0c2daf346c62525a16a354aa0245a827184 Mon Sep 17 00:00:00 2001 From: xiafu Date: Wed, 21 Aug 2019 13:02:17 -0700 Subject: [PATCH 5/5] [SnapshotSAS]Stylistic Tweak --- .../{shared_access_signature.py => _shared_access_signature.py} | 0 .../azure-storage-blob/azure/storage/blob/blob_client.py | 2 +- .../azure-storage-blob/azure/storage/blob/container_client.py | 2 +- .../{shared_access_signature.py => _shared_access_signature.py} | 0 .../azure-storage-file/azure/storage/file/file_client.py | 2 +- .../azure-storage-file/azure/storage/file/share_client.py | 2 +- .../{shared_access_signature.py => _shared_access_signature.py} | 0 .../azure-storage-queue/azure/storage/queue/queue_client.py | 2 +- 8 files changed, 5 insertions(+), 5 deletions(-) rename sdk/storage/azure-storage-blob/azure/storage/blob/{shared_access_signature.py => _shared_access_signature.py} (100%) rename sdk/storage/azure-storage-file/azure/storage/file/{shared_access_signature.py => _shared_access_signature.py} (100%) rename sdk/storage/azure-storage-queue/azure/storage/queue/{shared_access_signature.py => _shared_access_signature.py} (100%) diff --git a/sdk/storage/azure-storage-blob/azure/storage/blob/shared_access_signature.py b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared_access_signature.py similarity index 100% rename from sdk/storage/azure-storage-blob/azure/storage/blob/shared_access_signature.py rename to sdk/storage/azure-storage-blob/azure/storage/blob/_shared_access_signature.py 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 ed1d68d79cba..331f8b15d76c 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 @@ -22,7 +22,6 @@ from ._shared import encode_base64 from ._shared.base_client import StorageAccountHostsMixin, parse_connection_str, parse_query from ._shared.encryption import generate_blob_encryption_data -from azure.storage.blob.shared_access_signature import BlobSharedAccessSignature from ._shared.uploads import IterStreamer from ._shared.downloads import StorageStreamDownloader from ._shared.request_handlers import ( @@ -46,6 +45,7 @@ upload_page_blob) from .models import BlobType, BlobBlock from .lease import LeaseClient, get_access_conditions +from ._shared_access_signature import BlobSharedAccessSignature if TYPE_CHECKING: from datetime import datetime 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 820bf0d901f3..423ce0157486 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 @@ -21,7 +21,6 @@ import six -from azure.storage.blob.shared_access_signature import BlobSharedAccessSignature from ._shared.base_client import StorageAccountHostsMixin, parse_connection_str, parse_query from ._shared.request_handlers import add_metadata_headers, serialize_iso from ._shared.response_handlers import ( @@ -42,6 +41,7 @@ BlobPrefix) from .lease import LeaseClient, get_access_conditions from .blob_client import BlobClient +from ._shared_access_signature import BlobSharedAccessSignature if TYPE_CHECKING: from azure.core.pipeline.transport import HttpTransport # pylint: disable=ungrouped-imports diff --git a/sdk/storage/azure-storage-file/azure/storage/file/shared_access_signature.py b/sdk/storage/azure-storage-file/azure/storage/file/_shared_access_signature.py similarity index 100% rename from sdk/storage/azure-storage-file/azure/storage/file/shared_access_signature.py rename to sdk/storage/azure-storage-file/azure/storage/file/_shared_access_signature.py 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 350e09af3ac5..41faee810add 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 @@ -32,7 +32,7 @@ from ._deserialize import deserialize_file_properties, deserialize_file_stream from ._polling import CloseHandles from .models import HandlesPaged -from azure.storage.file.shared_access_signature import FileSharedAccessSignature +from ._shared_access_signature import FileSharedAccessSignature if TYPE_CHECKING: from datetime import datetime 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 5b54fad4bc0d..b5ed26f74eb0 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 @@ -21,7 +21,6 @@ return_response_headers, process_storage_error, return_headers_and_deserialized) -from .shared_access_signature import FileSharedAccessSignature from ._generated import AzureFileStorage from ._generated.version import VERSION from ._generated.models import ( @@ -31,6 +30,7 @@ from ._deserialize import deserialize_share_properties from .directory_client import DirectoryClient from .file_client import FileClient +from ._shared_access_signature import FileSharedAccessSignature if TYPE_CHECKING: from .models import ShareProperties, AccessPolicy diff --git a/sdk/storage/azure-storage-queue/azure/storage/queue/shared_access_signature.py b/sdk/storage/azure-storage-queue/azure/storage/queue/_shared_access_signature.py similarity index 100% rename from sdk/storage/azure-storage-queue/azure/storage/queue/shared_access_signature.py rename to sdk/storage/azure-storage-queue/azure/storage/queue/_shared_access_signature.py 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 3a2ba42f72f4..019d00395264 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 @@ -24,13 +24,13 @@ process_storage_error, return_response_headers, return_headers_and_deserialized) -from .shared_access_signature import QueueSharedAccessSignature from ._message_encoding import TextXMLEncodePolicy, TextXMLDecodePolicy from ._deserialize import deserialize_queue_properties, deserialize_queue_creation from ._generated import AzureQueueStorage from ._generated.models import StorageErrorException, SignedIdentifier from ._generated.models import QueueMessage as GenQueueMessage +from ._shared_access_signature import QueueSharedAccessSignature from .models import QueueMessage, AccessPolicy, MessagesPaged if TYPE_CHECKING: