diff --git a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/models.py b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/models.py
index 7185141649f9..50f891de3012 100644
--- a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/models.py
+++ b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/models.py
@@ -420,3 +420,36 @@ def __str__(self):
Services.BLOB = Services(blob=True)
Services.QUEUE = Services(queue=True)
Services.FILE = Services(file=True)
+
+
+class UserDelegationKey(object):
+ """
+ Represents a user delegation key, provided to the user by Azure Storage
+ based on their Azure Active Directory access token.
+
+ The fields are saved as simple strings since the user does not have to interact with this object;
+ to generate an identify SAS, the user can simply pass it to the right API.
+
+ :ivar str signed_oid:
+ Object ID of this token.
+ :ivar str signed_tid:
+ Tenant ID of the tenant that issued this token.
+ :ivar str signed_start:
+ The datetime this token becomes valid.
+ :ivar str signed_expiry:
+ The datetime this token expires.
+ :ivar str signed_service:
+ What service this key is valid for.
+ :ivar str signed_version:
+ The version identifier of the REST service that created this token.
+ :ivar str value:
+ The user delegation key.
+ """
+ def __init__(self):
+ self.signed_oid = None
+ self.signed_tid = None
+ self.signed_start = None
+ self.signed_expiry = None
+ self.signed_service = None
+ self.signed_version = None
+ self.value = None
diff --git a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/response_handlers.py b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/response_handlers.py
index bf92763aa509..fbf9889d762c 100644
--- a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/response_handlers.py
+++ b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/response_handlers.py
@@ -19,7 +19,8 @@
ClientAuthenticationError,
DecodeError)
-from .models import StorageErrorCode
+from .parser import _to_utc_datetime
+from .models import StorageErrorCode, UserDelegationKey
if TYPE_CHECKING:
@@ -131,3 +132,15 @@ def process_storage_error(storage_error):
error.error_code = error_code
error.additional_info = additional_data
raise error
+
+
+def parse_to_internal_user_delegation_key(service_user_delegation_key):
+ internal_user_delegation_key = UserDelegationKey()
+ internal_user_delegation_key.signed_oid = service_user_delegation_key.signed_oid
+ internal_user_delegation_key.signed_tid = service_user_delegation_key.signed_tid
+ internal_user_delegation_key.signed_start = _to_utc_datetime(service_user_delegation_key.signed_start)
+ internal_user_delegation_key.signed_expiry = _to_utc_datetime(service_user_delegation_key.signed_expiry)
+ internal_user_delegation_key.signed_service = service_user_delegation_key.signed_service
+ internal_user_delegation_key.signed_version = service_user_delegation_key.signed_version
+ internal_user_delegation_key.value = service_user_delegation_key.value
+ return internal_user_delegation_key
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 81c7b6d09f7c..b3adf5b2ac1a 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
@@ -188,7 +188,7 @@ def generate_container(self, container_name, permission=None, expiry=None,
content_encoding, content_language,
content_type)
sas.add_resource_signature(self.account_name, self.account_key, container_name,
- user_delegation_key=None)
+ user_delegation_key=self.user_delegation_key)
return sas.get_token()
diff --git a/sdk/storage/azure-storage-blob/azure/storage/blob/aio/blob_service_client_async.py b/sdk/storage/azure-storage-blob/azure/storage/blob/aio/blob_service_client_async.py
index bd3b2e5cb292..653edab10995 100644
--- a/sdk/storage/azure-storage-blob/azure/storage/blob/aio/blob_service_client_async.py
+++ b/sdk/storage/azure-storage-blob/azure/storage/blob/aio/blob_service_client_async.py
@@ -18,8 +18,10 @@
from .._shared.policies_async import ExponentialRetry
from .._shared.base_client_async import AsyncStorageAccountHostsMixin
from .._shared.response_handlers import return_response_headers, process_storage_error
+from .._shared.parser import _to_utc_datetime
+from .._shared.response_handlers import parse_to_internal_user_delegation_key
from .._generated.aio import AzureBlobStorage
-from .._generated.models import StorageErrorException, StorageServiceProperties
+from .._generated.models import StorageErrorException, StorageServiceProperties, KeyInfo
from ..blob_service_client import BlobServiceClient as BlobServiceClientBase
from .container_client_async import ContainerClient
from .blob_client_async import BlobClient
@@ -110,6 +112,36 @@ def __init__(
self._client = AzureBlobStorage(url=self.url, pipeline=self._pipeline, loop=loop)
self._loop = loop
+ @distributed_trace_async
+ async def get_user_delegation_key(self, key_start_time, # type: datetime
+ key_expiry_time, # type: datetime
+ timeout=None, # type: Optional[int]
+ **kwargs # type: Any
+ ):
+ # type: (datetime, datetime, Optional[int]) -> UserDelegationKey
+ """
+ Obtain a user delegation key for the purpose of signing SAS tokens.
+ A token credential must be present on the service object for this request to succeed.
+
+ :param datetime key_start_time:
+ A DateTime value. Indicates when the key becomes valid.
+ :param datetime key_expiry_time:
+ A DateTime value. Indicates when the key stops being valid.
+ :param int timeout:
+ The timeout parameter is expressed in seconds.
+ :return: The user delegation key.
+ :rtype: ~azure.storage.blob._shared.models.UserDelegationKey
+ """
+ key_info = KeyInfo(start=_to_utc_datetime(key_start_time), expiry=_to_utc_datetime(key_expiry_time))
+ try:
+ user_delegation_key = await self._client.service.get_user_delegation_key(key_info=key_info,
+ timeout=timeout,
+ **kwargs) # type: ignore
+ except StorageErrorException as error:
+ process_storage_error(error)
+
+ return parse_to_internal_user_delegation_key(user_delegation_key) # type: ignore
+
@distributed_trace_async
async def get_account_information(self, **kwargs): # type: ignore
# type: (Optional[int]) -> Dict[str, str]
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 93b9d6d535b4..e882ed80f29e 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
@@ -29,7 +29,7 @@
validate_and_format_range_headers)
from ._shared.response_handlers import return_response_headers, process_storage_error
from ._generated import AzureBlobStorage
-from ._generated.models import (
+from ._generated.models import ( # pylint: disable=unused-import
DeleteSnapshotsOptionType,
BlobHTTPHeaders,
BlockLookupList,
@@ -38,7 +38,9 @@
ModifiedAccessConditions,
SequenceNumberAccessConditions,
StorageErrorException,
+ UserDelegationKey,
CpkInfo)
+
from ._deserialize import deserialize_blob_properties, deserialize_blob_stream
from ._upload_helpers import (
upload_block_blob,
@@ -134,6 +136,7 @@ def __init__(
except AttributeError:
raise ValueError("Blob URL must be a string.")
parsed_url = urlparse(blob_url.rstrip('/'))
+
if not parsed_url.path and not (container and blob):
raise ValueError("Please specify a container and blob name.")
if not parsed_url.netloc:
@@ -228,12 +231,14 @@ def generate_shared_access_signature(
policy_id=None, # type: Optional[str]
ip=None, # type: Optional[str]
protocol=None, # type: Optional[str]
+ account_name=None, # type: Optional[str]
cache_control=None, # type: Optional[str]
content_disposition=None, # type: Optional[str]
content_encoding=None, # type: Optional[str]
content_language=None, # type: Optional[str]
- content_type=None # type: Optional[str]
- ):
+ content_type=None, # type: Optional[str]
+ user_delegation_key=None # type: Optional[UserDelegationKey]
+ ):
# type: (...) -> Any
"""
Generates a shared access signature for the blob.
@@ -275,6 +280,8 @@ def generate_shared_access_signature(
restricts the request to those IP addresses.
:param str protocol:
Specifies the protocol permitted for a request made. The default value is https.
+ :param str account_name:
+ Specifies the account_name when using oauth token as credential. If you use oauth token as credential.
:param str cache_control:
Response header value for Cache-Control when resource is accessed
using this shared access signature.
@@ -290,12 +297,24 @@ def generate_shared_access_signature(
:param str content_type:
Response header value for Content-Type when resource is accessed
using this shared access signature.
+ :param ~azure.storage.blob._shared.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.
+ When present, the SAS is signed with the user delegation key instead.
:return: A Shared Access Signature (sas) token.
:rtype: str
"""
- 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)
+ if user_delegation_key is not None:
+ if not hasattr(self.credential, 'account_name') and not account_name:
+ raise ValueError("No account_name available. Please provide account_name parameter.")
+
+ account_name = self.credential.account_name if hasattr(self.credential, 'account_name') else account_name
+ sas = BlobSharedAccessSignature(account_name, user_delegation_key=user_delegation_key)
+ else:
+ 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)
return sas.generate_blob(
self.container_name,
self.blob_name,
diff --git a/sdk/storage/azure-storage-blob/azure/storage/blob/blob_service_client.py b/sdk/storage/azure-storage-blob/azure/storage/blob/blob_service_client.py
index 23c7937bb108..33af6d0001c2 100644
--- a/sdk/storage/azure-storage-blob/azure/storage/blob/blob_service_client.py
+++ b/sdk/storage/azure-storage-blob/azure/storage/blob/blob_service_client.py
@@ -9,6 +9,7 @@
Union, Optional, Any, Iterable, Dict, List,
TYPE_CHECKING
)
+
try:
from urllib.parse import urlparse
except ImportError:
@@ -18,11 +19,13 @@
from azure.core.tracing.decorator import distributed_trace
from ._shared.shared_access_signature import SharedAccessSignature
-from ._shared.models import LocationMode, Services
+from ._shared.models import LocationMode, Services, UserDelegationKey
from ._shared.base_client import StorageAccountHostsMixin, parse_connection_str, parse_query
-from ._shared.response_handlers import return_response_headers, process_storage_error
+from ._shared.parser import _to_utc_datetime
+from ._shared.response_handlers import return_response_headers, process_storage_error, \
+ parse_to_internal_user_delegation_key
from ._generated import AzureBlobStorage
-from ._generated.models import StorageErrorException, StorageServiceProperties
+from ._generated.models import StorageErrorException, StorageServiceProperties, KeyInfo
from .container_client import ContainerClient
from .blob_client import BlobClient
from .models import ContainerProperties, ContainerPropertiesPaged
@@ -223,6 +226,36 @@ def generate_shared_access_signature(
protocol=protocol
) # type: ignore
+ @distributed_trace
+ def get_user_delegation_key(self, key_start_time, # type: datetime
+ key_expiry_time, # type: datetime
+ timeout=None, # type: Optional[int]
+ **kwargs # type: Any
+ ):
+ # type: (datetime, datetime, Optional[int]) -> UserDelegationKey
+ """
+ Obtain a user delegation key for the purpose of signing SAS tokens.
+ A token credential must be present on the service object for this request to succeed.
+
+ :param datetime key_start_time:
+ A DateTime value. Indicates when the key becomes valid.
+ :param datetime key_expiry_time:
+ A DateTime value. Indicates when the key stops being valid.
+ :param int timeout:
+ The timeout parameter is expressed in seconds.
+ :return: The user delegation key.
+ :rtype: ~azure.storage.blob._shared.models.UserDelegationKey
+ """
+ key_info = KeyInfo(start=_to_utc_datetime(key_start_time), expiry=_to_utc_datetime(key_expiry_time))
+ try:
+ user_delegation_key = self._client.service.get_user_delegation_key(key_info=key_info,
+ timeout=timeout,
+ **kwargs) # type: ignore
+ except StorageErrorException as error:
+ process_storage_error(error)
+
+ return parse_to_internal_user_delegation_key(user_delegation_key) # type: ignore
+
@distributed_trace
def get_account_information(self, **kwargs): # type: ignore
# type: (Optional[int]) -> Dict[str, str]
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 a25da2c35e50..bab89ba6802e 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
@@ -119,6 +119,7 @@ def __init__(
except AttributeError:
raise ValueError("Container URL must be a string.")
parsed_url = urlparse(container_url.rstrip('/'))
+
if not parsed_url.path and not container:
raise ValueError("Please specify a container name.")
if not parsed_url.netloc:
@@ -188,11 +189,13 @@ def generate_shared_access_signature(
policy_id=None, # type: Optional[str]
ip=None, # type: Optional[str]
protocol=None, # type: Optional[str]
+ account_name=None, # type: Optional[str]
cache_control=None, # type: Optional[str]
content_disposition=None, # type: Optional[str]
content_encoding=None, # type: Optional[str]
content_language=None, # type: Optional[str]
- content_type=None # type: Optional[str]
+ content_type=None, # type: Optional[str]
+ user_delegation_key=None # type Optional[]
):
# type: (...) -> Any
"""Generates a shared access signature for the container.
@@ -233,6 +236,8 @@ def generate_shared_access_signature(
restricts the request to those IP addresses.
:param str protocol:
Specifies the protocol permitted for a request made. The default value is https.
+ :param str account_name:
+ Specifies the account_name when using oauth token as credential. If you use oauth token as credential.
:param str cache_control:
Response header value for Cache-Control when resource is accessed
using this shared access signature.
@@ -248,6 +253,11 @@ def generate_shared_access_signature(
:param str content_type:
Response header value for Content-Type when resource is accessed
using this shared access signature.
+ :param ~azure.storage.blob._shared.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.
+ When present, the SAS is signed with the user delegation key instead.
:return: A Shared Access Signature (sas) token.
:rtype: str
@@ -259,9 +269,16 @@ def generate_shared_access_signature(
:dedent: 12
:caption: Generating a sas token.
"""
- 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)
+ if user_delegation_key is not None:
+ if not hasattr(self.credential, 'account_name') and not account_name:
+ raise ValueError("No account_name available. Please provide account_name parameter.")
+
+ account_name = self.credential.account_name if hasattr(self.credential, 'account_name') else account_name
+ sas = BlobSharedAccessSignature(account_name, user_delegation_key=user_delegation_key)
+ else:
+ 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)
return sas.generate_container(
self.container_name,
permission=permission,
diff --git a/sdk/storage/azure-storage-blob/tests/recordings/test_common_blob.test_get_user_delegation_key.yaml b/sdk/storage/azure-storage-blob/tests/recordings/test_common_blob.test_get_user_delegation_key.yaml
new file mode 100644
index 000000000000..74da6748ca62
--- /dev/null
+++ b/sdk/storage/azure-storage-blob/tests/recordings/test_common_blob.test_get_user_delegation_key.yaml
@@ -0,0 +1,145 @@
+interactions:
+- request:
+ body: client_id=68390a19-a897-236b-b453-488abf67b4fc&client_secret=3Usxz7pzkOeE7flc6Z187ubs5%2FcJnszGPjAiXmcwhaY%3D&grant_type=client_credentials&scope=https%3A%2F%2Fstorage.azure.com%2F.default
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Connection:
+ - keep-alive
+ Content-Length:
+ - '188'
+ Content-Type:
+ - application/x-www-form-urlencoded
+ User-Agent:
+ - python-requests/2.22.0
+ method: POST
+ uri: https://login.microsoftonline.com/32f988bf-54f1-15af-36ab-2d7cd364db47/oauth2/v2.0/token
+ response:
+ body:
+ string: '{"token_type":"Bearer","expires_in":3600,"ext_expires_in":3600,"access_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6ImllX3FXQ1hoWHh0MXpJRXN1NGM3YWNRVkduNCIsImtpZCI6ImllX3FXQ1hoWHh0MXpJRXN1NGM3YWNRVkduNCJ9.eyJhdWQiOiJodHRwczovL3N0b3JhZ2UuYXp1cmUuY29tIiwiaXNzIjoiaHR0cHM6Ly9zdHMud2luZG93cy5uZXQvNzJmOTg4YmYtODZmMS00MWFmLTkxYWItMmQ3Y2QwMTFkYjQ3LyIsImlhdCI6MTU2ODAxMzI3MSwibmJmIjoxNTY4MDEzMjcxLCJleHAiOjE1NjgwMTcxNzEsImFpbyI6IjQyRmdZRWllNE9INFQ0YkZrR3RoaExHT2hma1pBQT09IiwiYXBwaWQiOiI2ODM5MGExOS1hNjQzLTQ1OGItYjcyNi00MDhhYmY2N2I0ZmMiLCJhcHBpZGFjciI6IjEiLCJpZHAiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC83MmY5ODhiZi04NmYxLTQxYWYtOTFhYi0yZDdjZDAxMWRiNDcvIiwib2lkIjoiYzRmNDgyODktYmI4NC00MDg2LWIyNTAtNmY5NGE4ZjY0Y2VlIiwic3ViIjoiYzRmNDgyODktYmI4NC00MDg2LWIyNTAtNmY5NGE4ZjY0Y2VlIiwidGlkIjoiNzJmOTg4YmYtODZmMS00MWFmLTkxYWItMmQ3Y2QwMTFkYjQ3IiwidXRpIjoiRkpWQVQxM2NYVXFLeFhlc0tUMHNBQSIsInZlciI6IjEuMCJ9.tk74OnnuvrPtFFWZe_v1H6GpGK5hU4n76EoIybK2O_Za8ddlQbBtgeMqNzifX6qlh8bce4RYOSggFNapknf01h9Z53UkdJXusB8IcIoPXv9t3NH-zkrSabU0VpWLh24cas9u5HT-re0YZy4b2T9czKCheU2ltyU3y2VwsFh1OAmaUUJaDUw0OSdQGRcOu2rS5LQAgAQu0UcQZG2bT_IoAeIGCnIIyOG25gaO7mt4_oHqU61b6q9AH72i7shblF1JBQp6eMmTkZe_Y1GnScOACUTh3R82nSQ-AKC2JrXXGCELl9L_bUmbxOVBwJ9F-La4d26axn6NcmFnMzaiGO1tyw"}'
+ headers:
+ Cache-Control:
+ - no-cache, no-store
+ Content-Length:
+ - '1233'
+ Content-Type:
+ - application/json; charset=utf-8
+ Date:
+ - Mon, 09 Sep 2019 07:19:30 GMT
+ Expires:
+ - '-1'
+ P3P:
+ - CP="DSP CUR OTPi IND OTRi ONL FIN"
+ Pragma:
+ - no-cache
+ Set-Cookie:
+ - fpc=Arv4rF3oXGJOuCYLHWg4UbzeSEc1AQAAAAL0B9UOAAAA; expires=Wed, 09-Oct-2019
+ 07:19:31 GMT; path=/; secure; HttpOnly
+ - x-ms-gateway-slice=estsfd; path=/; secure; HttpOnly
+ - stsservicecookie=estsfd; path=/; secure; HttpOnly
+ Strict-Transport-Security:
+ - max-age=31536000; includeSubDomains
+ X-Content-Type-Options:
+ - nosniff
+ x-ms-ests-server:
+ - 2.1.9368.5 - WUS ProdSlices
+ x-ms-request-id:
+ - 4f409514-dc5d-4a5d-8ac5-77ac293d2c00
+ status:
+ code: 200
+ message: OK
+- request:
+ body: '
+
+ 2019-09-09T07:19:30Z2019-09-09T08:19:30Z'
+ headers:
+ Accept:
+ - application/xml
+ Accept-Encoding:
+ - gzip, deflate
+ Connection:
+ - keep-alive
+ Content-Length:
+ - '130'
+ Content-Type:
+ - application/xml; charset=utf-8
+ User-Agent:
+ - azsdk-python-storage-blob/12.0.0b2 Python/3.7.3 (Windows-10-10.0.18362-SP0)
+ x-ms-client-request-id:
+ - 2a824312-d2d2-11e9-8739-001a7dda7113
+ x-ms-date:
+ - Mon, 09 Sep 2019 07:19:30 GMT
+ x-ms-version:
+ - '2019-02-02'
+ method: POST
+ uri: https://oauthstoragename.blob.core.windows.net/?restype=service&comp=userdelegationkey
+ response:
+ body:
+ string: "\uFEFFc4f48289-bb84-4086-b250-6f94a8f64cee32f988bf-54f1-15af-36ab-2d7cd364db472019-09-09T07:19:30Z2019-09-09T08:19:30Zb2019-02-021CVF1CqD5X/XQy7xviJADxqwoA9X1Sh/pQWYA6//6Pc="
+ headers:
+ Content-Type:
+ - application/xml
+ Date:
+ - Mon, 09 Sep 2019 07:19:30 GMT
+ Server:
+ - Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0
+ Transfer-Encoding:
+ - chunked
+ x-ms-client-request-id:
+ - 2a824312-d2d2-11e9-8739-001a7dda7113
+ x-ms-request-id:
+ - e2241bda-c01e-0028-5ade-66325c000000
+ x-ms-version:
+ - '2019-02-02'
+ status:
+ code: 200
+ message: OK
+- request:
+ body: '
+
+ 2019-09-09T07:19:30Z2019-09-09T08:19:30Z'
+ headers:
+ Accept:
+ - application/xml
+ Accept-Encoding:
+ - gzip, deflate
+ Connection:
+ - keep-alive
+ Content-Length:
+ - '130'
+ Content-Type:
+ - application/xml; charset=utf-8
+ User-Agent:
+ - azsdk-python-storage-blob/12.0.0b2 Python/3.7.3 (Windows-10-10.0.18362-SP0)
+ x-ms-client-request-id:
+ - 2af3d72e-d2d2-11e9-8331-001a7dda7113
+ x-ms-date:
+ - Mon, 09 Sep 2019 07:19:31 GMT
+ x-ms-version:
+ - '2019-02-02'
+ method: POST
+ uri: https://oauthstoragename.blob.core.windows.net/?restype=service&comp=userdelegationkey
+ response:
+ body:
+ string: "\uFEFFc4f48289-bb84-4086-b250-6f94a8f64cee32f988bf-54f1-15af-36ab-2d7cd364db472019-09-09T07:19:30Z2019-09-09T08:19:30Zb2019-02-021CVF1CqD5X/XQy7xviJADxqwoA9X1Sh/pQWYA6//6Pc="
+ headers:
+ Content-Type:
+ - application/xml
+ Date:
+ - Mon, 09 Sep 2019 07:19:30 GMT
+ Server:
+ - Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0
+ Transfer-Encoding:
+ - chunked
+ x-ms-client-request-id:
+ - 2af3d72e-d2d2-11e9-8331-001a7dda7113
+ x-ms-request-id:
+ - e2241c96-c01e-0028-01de-66325c000000
+ x-ms-version:
+ - '2019-02-02'
+ status:
+ code: 200
+ message: OK
+version: 1
diff --git a/sdk/storage/azure-storage-blob/tests/recordings/test_common_blob_async.test_get_user_delegation_key_async.yaml b/sdk/storage/azure-storage-blob/tests/recordings/test_common_blob_async.test_get_user_delegation_key_async.yaml
new file mode 100644
index 000000000000..a9e13a93866c
--- /dev/null
+++ b/sdk/storage/azure-storage-blob/tests/recordings/test_common_blob_async.test_get_user_delegation_key_async.yaml
@@ -0,0 +1,155 @@
+interactions:
+- request:
+ body: client_id=68390a19-a897-236b-b453-488abf67b4fc&client_secret=3Usxz7pzkOeE7flc6Z187ubs5%2FcJnszGPjAiXmcwhaY%3D&grant_type=client_credentials&scope=https%3A%2F%2Fstorage.azure.com%2F.default
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Connection:
+ - keep-alive
+ Content-Length:
+ - '188'
+ Content-Type:
+ - application/x-www-form-urlencoded
+ User-Agent:
+ - python-requests/2.22.0
+ method: POST
+ uri: https://login.microsoftonline.com/32f988bf-54f1-15af-36ab-2d7cd364db47/oauth2/v2.0/token
+ response:
+ body:
+ string: '{"token_type":"Bearer","expires_in":3599,"ext_expires_in":3599,"access_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6ImllX3FXQ1hoWHh0MXpJRXN1NGM3YWNRVkduNCIsImtpZCI6ImllX3FXQ1hoWHh0MXpJRXN1NGM3YWNRVkduNCJ9.eyJhdWQiOiJodHRwczovL3N0b3JhZ2UuYXp1cmUuY29tIiwiaXNzIjoiaHR0cHM6Ly9zdHMud2luZG93cy5uZXQvNzJmOTg4YmYtODZmMS00MWFmLTkxYWItMmQ3Y2QwMTFkYjQ3LyIsImlhdCI6MTU2ODAxNDM5OSwibmJmIjoxNTY4MDE0Mzk5LCJleHAiOjE1NjgwMTgyOTksImFpbyI6IjQyRmdZQkE2SWkzVnYrL3dyUzMrMHcrMEZDMmVDQUE9IiwiYXBwaWQiOiI2ODM5MGExOS1hNjQzLTQ1OGItYjcyNi00MDhhYmY2N2I0ZmMiLCJhcHBpZGFjciI6IjEiLCJpZHAiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC83MmY5ODhiZi04NmYxLTQxYWYtOTFhYi0yZDdjZDAxMWRiNDcvIiwib2lkIjoiYzRmNDgyODktYmI4NC00MDg2LWIyNTAtNmY5NGE4ZjY0Y2VlIiwic3ViIjoiYzRmNDgyODktYmI4NC00MDg2LWIyNTAtNmY5NGE4ZjY0Y2VlIiwidGlkIjoiNzJmOTg4YmYtODZmMS00MWFmLTkxYWItMmQ3Y2QwMTFkYjQ3IiwidXRpIjoiUHMyLS1ZQjQ2MHluYUJ5S1ZDTXRBQSIsInZlciI6IjEuMCJ9.d3JgzJ664_Xmbbe7aWRDfUuw4g2W7MtKbCcOFAMko-gN5qXcmSNyAiOGHSUrvuORtEPOLye3CZuBmYw6hczqhl-9dF760mRxLyZjOjqE78YTJkudwU5iV69nr9VIAIMOk5Lh7N3sgUdWrSsz-ZUT8665da1pN3bpsl8zEHf9ZBM1qFia4J9OLtGQRb_Th2jJpm_LbMKdnMJ45cgoYxZeKcofo43we-BHSU5T-Jt4O8i6nalMSelp5ZuLI7KL308sm81iUyrrK3PCGGhGVdNIg5B4cOdMmq3B10m-Kk2gZfoPj2cJrsUkhY005mimBh7aa05B1BIwooRzxCE7t0ne_Q"}'
+ headers:
+ Cache-Control:
+ - no-cache, no-store
+ Content-Length:
+ - '1233'
+ Content-Type:
+ - application/json; charset=utf-8
+ Date:
+ - Mon, 09 Sep 2019 07:38:19 GMT
+ Expires:
+ - '-1'
+ P3P:
+ - CP="DSP CUR OTPi IND OTRi ONL FIN"
+ Pragma:
+ - no-cache
+ Set-Cookie:
+ - fpc=AiNGuczA96ZBt-KrKg4lYNneSEc1AQAAAGv4B9UOAAAA; expires=Wed, 09-Oct-2019
+ 07:38:19 GMT; path=/; secure; HttpOnly
+ - x-ms-gateway-slice=estsfd; path=/; secure; HttpOnly
+ - stsservicecookie=estsfd; path=/; secure; HttpOnly
+ Strict-Transport-Security:
+ - max-age=31536000; includeSubDomains
+ X-Content-Type-Options:
+ - nosniff
+ x-ms-ests-server:
+ - 2.1.9368.5 - WUS ProdSlices
+ x-ms-request-id:
+ - f9becd3e-7880-4ceb-a768-1c8a54232d00
+ status:
+ code: 200
+ message: OK
+- request:
+ body: '
+
+ 2019-09-09T07:38:19Z2019-09-09T08:38:19Z'
+ headers:
+ Accept:
+ - application/xml
+ Content-Length:
+ - '130'
+ Content-Type:
+ - application/xml; charset=utf-8
+ User-Agent:
+ - azsdk-python-storage-blob/12.0.0b2 Python/3.7.3 (Windows-10-10.0.18362-SP0)
+ x-ms-client-request-id:
+ - cb271d62-d2d4-11e9-bda0-001a7dda7113
+ x-ms-date:
+ - Mon, 09 Sep 2019 07:38:19 GMT
+ x-ms-version:
+ - '2019-02-02'
+ method: POST
+ uri: https://oauthstoragename.blob.core.windows.net/?restype=service&comp=userdelegationkey
+ response:
+ body:
+ string: "\uFEFFc4f48289-bb84-4086-b250-6f94a8f64cee32f988bf-54f1-15af-36ab-2d7cd364db472019-09-09T07:38:19Z2019-09-09T08:38:19Zb2019-02-02kFFrkuiikpAqQywiYShReWmx/tyTvEh8yl8fhjZoH7Q="
+ headers:
+ ? !!python/object/new:multidict._istr.istr
+ - Content-Type
+ : application/xml
+ ? !!python/object/new:multidict._istr.istr
+ - Date
+ : Mon, 09 Sep 2019 07:38:19 GMT
+ ? !!python/object/new:multidict._istr.istr
+ - Server
+ : Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0
+ ? !!python/object/new:multidict._istr.istr
+ - Transfer-Encoding
+ : chunked
+ x-ms-client-request-id: cb271d62-d2d4-11e9-bda0-001a7dda7113
+ x-ms-request-id: f99f7a82-d01e-00b2-4be1-66ac85000000
+ x-ms-version: '2019-02-02'
+ status:
+ code: 200
+ message: OK
+ url: !!python/object/new:yarl.URL
+ state: !!python/tuple
+ - !!python/object/new:urllib.parse.SplitResult
+ - https
+ - emilydevtest.blob.core.windows.net
+ - /
+ - restype=service&comp=userdelegationkey
+ - ''
+- request:
+ body: '
+
+ 2019-09-09T07:38:19Z2019-09-09T08:38:19Z'
+ headers:
+ Accept:
+ - application/xml
+ Content-Length:
+ - '130'
+ Content-Type:
+ - application/xml; charset=utf-8
+ User-Agent:
+ - azsdk-python-storage-blob/12.0.0b2 Python/3.7.3 (Windows-10-10.0.18362-SP0)
+ x-ms-client-request-id:
+ - cb88c8d2-d2d4-11e9-b99d-001a7dda7113
+ x-ms-date:
+ - Mon, 09 Sep 2019 07:38:20 GMT
+ x-ms-version:
+ - '2019-02-02'
+ method: POST
+ uri: https://oauthstoragename.blob.core.windows.net/?restype=service&comp=userdelegationkey
+ response:
+ body:
+ string: "\uFEFFc4f48289-bb84-4086-b250-6f94a8f64cee32f988bf-54f1-15af-36ab-2d7cd364db472019-09-09T07:38:19Z2019-09-09T08:38:19Zb2019-02-02kFFrkuiikpAqQywiYShReWmx/tyTvEh8yl8fhjZoH7Q="
+ headers:
+ ? !!python/object/new:multidict._istr.istr
+ - Content-Type
+ : application/xml
+ ? !!python/object/new:multidict._istr.istr
+ - Date
+ : Mon, 09 Sep 2019 07:38:19 GMT
+ ? !!python/object/new:multidict._istr.istr
+ - Server
+ : Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0
+ ? !!python/object/new:multidict._istr.istr
+ - Transfer-Encoding
+ : chunked
+ x-ms-client-request-id: cb88c8d2-d2d4-11e9-b99d-001a7dda7113
+ x-ms-request-id: f99f7af3-d01e-00b2-36e1-66ac85000000
+ x-ms-version: '2019-02-02'
+ status:
+ code: 200
+ message: OK
+ url: !!python/object/new:yarl.URL
+ state: !!python/tuple
+ - !!python/object/new:urllib.parse.SplitResult
+ - https
+ - emilydevtest.blob.core.windows.net
+ - /
+ - restype=service&comp=userdelegationkey
+ - ''
+version: 1
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 b0ad55c71d5f..5cd2a7f5be2f 100644
--- a/sdk/storage/azure-storage-blob/tests/test_common_blob.py
+++ b/sdk/storage/azure-storage-blob/tests/test_common_blob.py
@@ -1264,6 +1264,67 @@ def test_account_sas(self):
self.assertEqual(self.byte_data, blob_response.content)
self.assertTrue(container_response.ok)
+ @record
+ def test_get_user_delegation_key(self):
+ # Act
+ token_credential = self.generate_oauth_token()
+
+ # Action 1: make sure token works
+ service = BlobServiceClient(self._get_oauth_account_url(), credential=token_credential)
+
+ start = datetime.utcnow()
+ expiry = datetime.utcnow() + timedelta(hours=1)
+ user_delegation_key_1 = service.get_user_delegation_key(key_start_time=start, key_expiry_time=expiry)
+ user_delegation_key_2 = service.get_user_delegation_key(key_start_time=start, key_expiry_time=expiry)
+
+ # Assert key1 is valid
+ self.assertIsNotNone(user_delegation_key_1.signed_oid)
+ self.assertIsNotNone(user_delegation_key_1.signed_tid)
+ self.assertIsNotNone(user_delegation_key_1.signed_start)
+ self.assertIsNotNone(user_delegation_key_1.signed_expiry)
+ self.assertIsNotNone(user_delegation_key_1.signed_version)
+ self.assertIsNotNone(user_delegation_key_1.signed_service)
+ self.assertIsNotNone(user_delegation_key_1.value)
+
+ # Assert key1 and key2 are equal, since they have the exact same start and end times
+ self.assertEqual(user_delegation_key_1.signed_oid, user_delegation_key_2.signed_oid)
+ self.assertEqual(user_delegation_key_1.signed_tid, user_delegation_key_2.signed_tid)
+ self.assertEqual(user_delegation_key_1.signed_start, user_delegation_key_2.signed_start)
+ self.assertEqual(user_delegation_key_1.signed_expiry, user_delegation_key_2.signed_expiry)
+ self.assertEqual(user_delegation_key_1.signed_version, user_delegation_key_2.signed_version)
+ self.assertEqual(user_delegation_key_1.signed_service, user_delegation_key_2.signed_service)
+ self.assertEqual(user_delegation_key_1.value, user_delegation_key_2.value)
+
+ def test_user_delegation_sas_for_blob(self):
+ # SAS URL is calculated from storage key, so this test runs live only
+ if TestMode.need_recording_file(self.test_mode):
+ return
+
+ # Arrange
+ token_credential = self.generate_oauth_token()
+ service_client = BlobServiceClient(self._get_oauth_account_url(), credential=token_credential)
+ user_delegation_key = service_client.get_user_delegation_key(datetime.utcnow(),
+ datetime.utcnow() + timedelta(hours=1))
+
+ container_client = service_client.create_container(self.get_resource_name('oauthcontainer'))
+ blob_client = container_client.get_blob_client(self.get_resource_name('oauthblob'))
+ blob_client.upload_blob(self.byte_data, length=len(self.byte_data))
+
+ token = blob_client.generate_shared_access_signature(
+ permission=BlobPermissions.READ,
+ expiry=datetime.utcnow() + timedelta(hours=1),
+ user_delegation_key=user_delegation_key,
+ account_name='emilydevtest',
+ )
+
+ # Act
+ # Use the generated identity sas
+ new_blob_client = BlobClient(blob_client.url, credential=token)
+ content = new_blob_client.download_blob()
+
+ # Assert
+ self.assertEqual(self.byte_data, b"".join(list(content)))
+
@record
def test_token_credential(self):
token_credential = self.generate_oauth_token()
diff --git a/sdk/storage/azure-storage-blob/tests/test_common_blob_async.py b/sdk/storage/azure-storage-blob/tests/test_common_blob_async.py
index c77c59749a69..ba8fa6cc8ac6 100644
--- a/sdk/storage/azure-storage-blob/tests/test_common_blob_async.py
+++ b/sdk/storage/azure-storage-blob/tests/test_common_blob_async.py
@@ -192,6 +192,15 @@ async def _test_blob_exists(self):
# Assert
self.assertTrue(exists)
+ def _generate_oauth_token(self):
+ from azure.identity.aio import ClientSecretCredential
+
+ return ClientSecretCredential(
+ self.settings.ACTIVE_DIRECTORY_APPLICATION_ID,
+ self.settings.ACTIVE_DIRECTORY_APPLICATION_SECRET,
+ self.settings.ACTIVE_DIRECTORY_TENANT_ID
+ )
+
@record
def test_blob_exists(self):
loop = asyncio.get_event_loop()
@@ -1620,14 +1629,49 @@ def test_account_sas(self):
loop = asyncio.get_event_loop()
loop.run_until_complete(self._test_account_sas())
+ async def _test_get_user_delegation_key(self):
+ # TODO: figure out why recording does not work
+ if TestMode.need_recording_file(self.test_mode):
+ return
+ # Act
+ token_credential = self._generate_oauth_token()
+
+ # Action 1: make sure token works
+ service = BlobServiceClient(self._get_oauth_account_url(), credential=token_credential)
+
+ start = datetime.utcnow()
+ expiry = datetime.utcnow() + timedelta(hours=1)
+ user_delegation_key_1 = await service.get_user_delegation_key(key_start_time=start, key_expiry_time=expiry)
+ user_delegation_key_2 = await service.get_user_delegation_key(key_start_time=start, key_expiry_time=expiry)
+
+ # Assert key1 is valid
+ self.assertIsNotNone(user_delegation_key_1.signed_oid)
+ self.assertIsNotNone(user_delegation_key_1.signed_tid)
+ self.assertIsNotNone(user_delegation_key_1.signed_start)
+ self.assertIsNotNone(user_delegation_key_1.signed_expiry)
+ self.assertIsNotNone(user_delegation_key_1.signed_version)
+ self.assertIsNotNone(user_delegation_key_1.signed_service)
+ self.assertIsNotNone(user_delegation_key_1.value)
+
+ # Assert key1 and key2 are equal, since they have the exact same start and end times
+ self.assertEqual(user_delegation_key_1.signed_oid, user_delegation_key_2.signed_oid)
+ self.assertEqual(user_delegation_key_1.signed_tid, user_delegation_key_2.signed_tid)
+ self.assertEqual(user_delegation_key_1.signed_start, user_delegation_key_2.signed_start)
+ self.assertEqual(user_delegation_key_1.signed_expiry, user_delegation_key_2.signed_expiry)
+ self.assertEqual(user_delegation_key_1.signed_version, user_delegation_key_2.signed_version)
+ self.assertEqual(user_delegation_key_1.signed_service, user_delegation_key_2.signed_service)
+ self.assertEqual(user_delegation_key_1.value, user_delegation_key_2.value)
+
+ def test_get_user_delegation_key_async(self):
+ loop = asyncio.get_event_loop()
+ loop.run_until_complete(self._test_get_user_delegation_key())
+
async def _test_token_credential(self):
- pytest.skip("")
if TestMode.need_recording_file(self.test_mode):
return
await self._setup()
- token_credential = self.generate_oauth_token()
- get_token = token_credential.get_token
+ token_credential = self._generate_oauth_token()
# Action 1: make sure token works
service = BlobServiceClient(self._get_oauth_account_url(), credential=token_credential, transport=AiohttpTestTransport())
diff --git a/sdk/storage/azure-storage-blob/tests/test_container.py b/sdk/storage/azure-storage-blob/tests/test_container.py
index 4ef49436f78d..18420af2b91c 100644
--- a/sdk/storage/azure-storage-blob/tests/test_container.py
+++ b/sdk/storage/azure-storage-blob/tests/test_container.py
@@ -24,6 +24,7 @@
AccessPolicy
)
+from azure.identity import ClientSecretCredential
from testcase import StorageTestCase, TestMode, record, LogCaptured
#------------------------------------------------------------------------------
@@ -71,6 +72,14 @@ def _create_container(self, prefix=TEST_CONTAINER_PREFIX):
pass
return container
+ def _generate_oauth_token(self):
+
+ return ClientSecretCredential(
+ self.settings.ACTIVE_DIRECTORY_APPLICATION_ID,
+ self.settings.ACTIVE_DIRECTORY_APPLICATION_SECRET,
+ self.settings.ACTIVE_DIRECTORY_TENANT_ID
+ )
+
#--Test cases for containers -----------------------------------------
@record
def test_create_container(self):
@@ -1068,6 +1077,35 @@ def test_web_container_normal_operations_working(self):
# delete container
container.delete_container()
+ def test_user_delegation_sas_for_container(self):
+ # SAS URL is calculated from storage key, so this test runs live only
+ if TestMode.need_recording_file(self.test_mode):
+ return
+
+ # Arrange
+ token_credential = self.generate_oauth_token()
+ service_client = BlobServiceClient(self._get_oauth_account_url(), credential=token_credential)
+ user_delegation_key = service_client.get_user_delegation_key(datetime.utcnow(),
+ datetime.utcnow() + timedelta(hours=1))
+
+ container_client = service_client.create_container(self.get_resource_name('oauthcontainer'))
+ token = container_client.generate_shared_access_signature(
+ expiry=datetime.utcnow() + timedelta(hours=1),
+ permission=ContainerPermissions.READ,
+ user_delegation_key=user_delegation_key,
+ account_name='emilydevtest'
+ )
+
+ blob_client = container_client.get_blob_client(self.get_resource_name('oauthblob'))
+ blob_content = self.get_random_text_data(1024)
+ blob_client.upload_blob(blob_content, length=len(blob_content))
+
+ # Act
+ new_blob_client = BlobClient(blob_client.url, credential=token)
+ content = new_blob_client.download_blob()
+
+ # Assert
+ self.assertEqual(blob_content, b"".join(list(content)).decode('utf-8'))
#------------------------------------------------------------------------------
if __name__ == '__main__':
diff --git a/sdk/storage/azure-storage-file/azure/storage/file/_shared/models.py b/sdk/storage/azure-storage-file/azure/storage/file/_shared/models.py
index 7185141649f9..50f891de3012 100644
--- a/sdk/storage/azure-storage-file/azure/storage/file/_shared/models.py
+++ b/sdk/storage/azure-storage-file/azure/storage/file/_shared/models.py
@@ -420,3 +420,36 @@ def __str__(self):
Services.BLOB = Services(blob=True)
Services.QUEUE = Services(queue=True)
Services.FILE = Services(file=True)
+
+
+class UserDelegationKey(object):
+ """
+ Represents a user delegation key, provided to the user by Azure Storage
+ based on their Azure Active Directory access token.
+
+ The fields are saved as simple strings since the user does not have to interact with this object;
+ to generate an identify SAS, the user can simply pass it to the right API.
+
+ :ivar str signed_oid:
+ Object ID of this token.
+ :ivar str signed_tid:
+ Tenant ID of the tenant that issued this token.
+ :ivar str signed_start:
+ The datetime this token becomes valid.
+ :ivar str signed_expiry:
+ The datetime this token expires.
+ :ivar str signed_service:
+ What service this key is valid for.
+ :ivar str signed_version:
+ The version identifier of the REST service that created this token.
+ :ivar str value:
+ The user delegation key.
+ """
+ def __init__(self):
+ self.signed_oid = None
+ self.signed_tid = None
+ self.signed_start = None
+ self.signed_expiry = None
+ self.signed_service = None
+ self.signed_version = None
+ self.value = None
diff --git a/sdk/storage/azure-storage-file/azure/storage/file/_shared/response_handlers.py b/sdk/storage/azure-storage-file/azure/storage/file/_shared/response_handlers.py
index bf92763aa509..fbf9889d762c 100644
--- a/sdk/storage/azure-storage-file/azure/storage/file/_shared/response_handlers.py
+++ b/sdk/storage/azure-storage-file/azure/storage/file/_shared/response_handlers.py
@@ -19,7 +19,8 @@
ClientAuthenticationError,
DecodeError)
-from .models import StorageErrorCode
+from .parser import _to_utc_datetime
+from .models import StorageErrorCode, UserDelegationKey
if TYPE_CHECKING:
@@ -131,3 +132,15 @@ def process_storage_error(storage_error):
error.error_code = error_code
error.additional_info = additional_data
raise error
+
+
+def parse_to_internal_user_delegation_key(service_user_delegation_key):
+ internal_user_delegation_key = UserDelegationKey()
+ internal_user_delegation_key.signed_oid = service_user_delegation_key.signed_oid
+ internal_user_delegation_key.signed_tid = service_user_delegation_key.signed_tid
+ internal_user_delegation_key.signed_start = _to_utc_datetime(service_user_delegation_key.signed_start)
+ internal_user_delegation_key.signed_expiry = _to_utc_datetime(service_user_delegation_key.signed_expiry)
+ internal_user_delegation_key.signed_service = service_user_delegation_key.signed_service
+ internal_user_delegation_key.signed_version = service_user_delegation_key.signed_version
+ internal_user_delegation_key.value = service_user_delegation_key.value
+ return internal_user_delegation_key
diff --git a/sdk/storage/azure-storage-file/tests/recordings/test_file_async.test_set_file_properties_async.yaml b/sdk/storage/azure-storage-file/tests/recordings/test_file_async.test_set_file_properties_async.yaml
index 8552d42a39ee..676c3732a9bb 100644
--- a/sdk/storage/azure-storage-file/tests/recordings/test_file_async.test_set_file_properties_async.yaml
+++ b/sdk/storage/azure-storage-file/tests/recordings/test_file_async.test_set_file_properties_async.yaml
@@ -7,9 +7,9 @@ interactions:
content-type:
- application/xml; charset=utf-8
x-ms-client-request-id:
- - 8df4997a-d064-11e9-842c-001a7dda7113
+ - 3a658f40-d357-11e9-a521-001a7dda7113
x-ms-date:
- - Fri, 06 Sep 2019 05:09:50 GMT
+ - Mon, 09 Sep 2019 23:12:00 GMT
x-ms-version:
- '2019-02-02'
method: PUT
@@ -23,18 +23,18 @@ interactions:
: '0'
? !!python/object/new:multidict._istr.istr
- Date
- : Fri, 06 Sep 2019 05:09:50 GMT
+ : Mon, 09 Sep 2019 23:12:00 GMT
? !!python/object/new:multidict._istr.istr
- Etag
- : '"0x8D732887226203F"'
+ : '"0x8D7357B1EAA605B"'
? !!python/object/new:multidict._istr.istr
- Last-Modified
- : Fri, 06 Sep 2019 05:09:50 GMT
+ : Mon, 09 Sep 2019 23:12:00 GMT
? !!python/object/new:multidict._istr.istr
- Server
: Windows-Azure-File/1.0 Microsoft-HTTPAPI/2.0
- x-ms-client-request-id: 8df4997a-d064-11e9-842c-001a7dda7113
- x-ms-request-id: 1cfdd9e5-301a-00aa-1071-6473e2000000
+ x-ms-client-request-id: 3a658f40-d357-11e9-a521-001a7dda7113
+ x-ms-request-id: 684e1630-f01a-006e-2963-6706db000000
x-ms-version: '2019-02-02'
status:
code: 201
@@ -55,11 +55,11 @@ interactions:
content-type:
- application/xml; charset=utf-8
x-ms-client-request-id:
- - 8dffc79a-d064-11e9-82dc-001a7dda7113
+ - 3a8c7bf8-d357-11e9-a069-001a7dda7113
x-ms-content-length:
- '1024'
x-ms-date:
- - Fri, 06 Sep 2019 05:09:50 GMT
+ - Mon, 09 Sep 2019 23:12:00 GMT
x-ms-file-attributes:
- none
x-ms-file-creation-time:
@@ -83,25 +83,25 @@ interactions:
: '0'
? !!python/object/new:multidict._istr.istr
- Date
- : Fri, 06 Sep 2019 05:09:50 GMT
+ : Mon, 09 Sep 2019 23:12:01 GMT
? !!python/object/new:multidict._istr.istr
- Etag
- : '"0x8D73288723342F3"'
+ : '"0x8D7357B1ED1C9E2"'
? !!python/object/new:multidict._istr.istr
- Last-Modified
- : Fri, 06 Sep 2019 05:09:50 GMT
+ : Mon, 09 Sep 2019 23:12:01 GMT
? !!python/object/new:multidict._istr.istr
- Server
: Windows-Azure-File/1.0 Microsoft-HTTPAPI/2.0
- x-ms-client-request-id: 8dffc79a-d064-11e9-82dc-001a7dda7113
+ x-ms-client-request-id: 3a8c7bf8-d357-11e9-a069-001a7dda7113
x-ms-file-attributes: Archive
- x-ms-file-change-time: '2019-09-06T05:09:50.9117683Z'
- x-ms-file-creation-time: '2019-09-06T05:09:50.9117683Z'
+ x-ms-file-change-time: '2019-09-09T23:12:01.0529250Z'
+ x-ms-file-creation-time: '2019-09-09T23:12:01.0529250Z'
x-ms-file-id: '13835128424026341376'
- x-ms-file-last-write-time: '2019-09-06T05:09:50.9117683Z'
+ x-ms-file-last-write-time: '2019-09-09T23:12:01.0529250Z'
x-ms-file-parent-id: '0'
x-ms-file-permission-key: 4099112195243312672*10394889115079208622
- x-ms-request-id: d77571d0-701a-002d-1971-64e087000000
+ x-ms-request-id: 64fb3cd0-501a-0093-7e63-6788fe000000
x-ms-request-server-encrypted: 'true'
x-ms-version: '2019-02-02'
status:
@@ -143,9 +143,9 @@ interactions:
User-Agent:
- azsdk-python-storage-file/12.0.0b2 Python/3.7.3 (Windows-10-10.0.18362-SP0)
x-ms-client-request-id:
- - 8e0b0ed8-d064-11e9-9d11-001a7dda7113
+ - 3ab04b4c-d357-11e9-b80e-001a7dda7113
x-ms-date:
- - Fri, 06 Sep 2019 05:09:50 GMT
+ - Mon, 09 Sep 2019 23:12:01 GMT
x-ms-range:
- bytes=0-1023
x-ms-version:
@@ -166,18 +166,18 @@ interactions:
: aHnHh6kpL9BLaCLM3XK+Zg==
? !!python/object/new:multidict._istr.istr
- Date
- : Fri, 06 Sep 2019 05:09:50 GMT
+ : Mon, 09 Sep 2019 23:12:01 GMT
? !!python/object/new:multidict._istr.istr
- Etag
- : '"0x8D73288723762A8"'
+ : '"0x8D7357B1EDF3AA9"'
? !!python/object/new:multidict._istr.istr
- Last-Modified
- : Fri, 06 Sep 2019 05:09:50 GMT
+ : Mon, 09 Sep 2019 23:12:01 GMT
? !!python/object/new:multidict._istr.istr
- Server
: Windows-Azure-File/1.0 Microsoft-HTTPAPI/2.0
- x-ms-client-request-id: 8e0b0ed8-d064-11e9-9d11-001a7dda7113
- x-ms-request-id: d77571d2-701a-002d-1a71-64e087000000
+ x-ms-client-request-id: 3ab04b4c-d357-11e9-b80e-001a7dda7113
+ x-ms-request-id: 64fb3cd2-501a-0093-7f63-6788fe000000
x-ms-request-server-encrypted: 'true'
x-ms-version: '2019-02-02'
status:
@@ -199,13 +199,13 @@ interactions:
content-type:
- application/xml; charset=utf-8
x-ms-client-request-id:
- - 8e0e19c2-d064-11e9-a9e2-001a7dda7113
+ - 3ab3d6de-d357-11e9-834d-001a7dda7113
x-ms-content-disposition:
- inline
x-ms-content-language:
- spanish
x-ms-date:
- - Fri, 06 Sep 2019 05:09:51 GMT
+ - Mon, 09 Sep 2019 23:12:01 GMT
x-ms-file-attributes:
- preserve
x-ms-file-creation-time:
@@ -227,25 +227,25 @@ interactions:
: '0'
? !!python/object/new:multidict._istr.istr
- Date
- : Fri, 06 Sep 2019 05:09:50 GMT
+ : Mon, 09 Sep 2019 23:12:01 GMT
? !!python/object/new:multidict._istr.istr
- Etag
- : '"0x8D73288723A70A2"'
+ : '"0x8D7357B1EE1FA79"'
? !!python/object/new:multidict._istr.istr
- Last-Modified
- : Fri, 06 Sep 2019 05:09:50 GMT
+ : Mon, 09 Sep 2019 23:12:01 GMT
? !!python/object/new:multidict._istr.istr
- Server
: Windows-Azure-File/1.0 Microsoft-HTTPAPI/2.0
- x-ms-client-request-id: 8e0e19c2-d064-11e9-a9e2-001a7dda7113
+ x-ms-client-request-id: 3ab3d6de-d357-11e9-834d-001a7dda7113
x-ms-file-attributes: Archive
- x-ms-file-change-time: '2019-09-06T05:09:50.9588130Z'
- x-ms-file-creation-time: '2019-09-06T05:09:50.9117683Z'
+ x-ms-file-change-time: '2019-09-09T23:12:01.1590265Z'
+ x-ms-file-creation-time: '2019-09-09T23:12:01.0529250Z'
x-ms-file-id: '13835128424026341376'
- x-ms-file-last-write-time: '2019-09-06T05:09:50.9117683Z'
+ x-ms-file-last-write-time: '2019-09-09T23:12:01.0529250Z'
x-ms-file-parent-id: '0'
x-ms-file-permission-key: 4099112195243312672*10394889115079208622
- x-ms-request-id: d77571d3-701a-002d-1b71-64e087000000
+ x-ms-request-id: 64fb3cd3-501a-0093-8063-6788fe000000
x-ms-request-server-encrypted: 'true'
x-ms-version: '2019-02-02'
status:
@@ -267,9 +267,9 @@ interactions:
content-type:
- application/xml; charset=utf-8
x-ms-client-request-id:
- - 8e1198c0-d064-11e9-8d7f-001a7dda7113
+ - 3ab6551c-d357-11e9-b471-001a7dda7113
x-ms-date:
- - Fri, 06 Sep 2019 05:09:51 GMT
+ - Mon, 09 Sep 2019 23:12:01 GMT
x-ms-version:
- '2019-02-02'
method: HEAD
@@ -292,25 +292,25 @@ interactions:
: application/xml; charset=utf-8
? !!python/object/new:multidict._istr.istr
- Date
- : Fri, 06 Sep 2019 05:09:50 GMT
+ : Mon, 09 Sep 2019 23:12:01 GMT
? !!python/object/new:multidict._istr.istr
- Etag
- : '"0x8D73288723A70A2"'
+ : '"0x8D7357B1EE1FA79"'
? !!python/object/new:multidict._istr.istr
- Last-Modified
- : Fri, 06 Sep 2019 05:09:50 GMT
+ : Mon, 09 Sep 2019 23:12:01 GMT
? !!python/object/new:multidict._istr.istr
- Server
: Windows-Azure-File/1.0 Microsoft-HTTPAPI/2.0
- x-ms-client-request-id: 8e1198c0-d064-11e9-8d7f-001a7dda7113
+ x-ms-client-request-id: 3ab6551c-d357-11e9-b471-001a7dda7113
x-ms-file-attributes: Archive
- x-ms-file-change-time: '2019-09-06T05:09:50.9588130Z'
- x-ms-file-creation-time: '2019-09-06T05:09:50.9117683Z'
+ x-ms-file-change-time: '2019-09-09T23:12:01.1590265Z'
+ x-ms-file-creation-time: '2019-09-09T23:12:01.0529250Z'
x-ms-file-id: '13835128424026341376'
- x-ms-file-last-write-time: '2019-09-06T05:09:50.9117683Z'
+ x-ms-file-last-write-time: '2019-09-09T23:12:01.0529250Z'
x-ms-file-parent-id: '0'
x-ms-file-permission-key: 4099112195243312672*10394889115079208622
- x-ms-request-id: d77571d4-701a-002d-1c71-64e087000000
+ x-ms-request-id: 64fb3cd4-501a-0093-0163-6788fe000000
x-ms-server-encrypted: 'true'
x-ms-type: File
x-ms-version: '2019-02-02'
diff --git a/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/models.py b/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/models.py
index 7185141649f9..50f891de3012 100644
--- a/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/models.py
+++ b/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/models.py
@@ -420,3 +420,36 @@ def __str__(self):
Services.BLOB = Services(blob=True)
Services.QUEUE = Services(queue=True)
Services.FILE = Services(file=True)
+
+
+class UserDelegationKey(object):
+ """
+ Represents a user delegation key, provided to the user by Azure Storage
+ based on their Azure Active Directory access token.
+
+ The fields are saved as simple strings since the user does not have to interact with this object;
+ to generate an identify SAS, the user can simply pass it to the right API.
+
+ :ivar str signed_oid:
+ Object ID of this token.
+ :ivar str signed_tid:
+ Tenant ID of the tenant that issued this token.
+ :ivar str signed_start:
+ The datetime this token becomes valid.
+ :ivar str signed_expiry:
+ The datetime this token expires.
+ :ivar str signed_service:
+ What service this key is valid for.
+ :ivar str signed_version:
+ The version identifier of the REST service that created this token.
+ :ivar str value:
+ The user delegation key.
+ """
+ def __init__(self):
+ self.signed_oid = None
+ self.signed_tid = None
+ self.signed_start = None
+ self.signed_expiry = None
+ self.signed_service = None
+ self.signed_version = None
+ self.value = None
diff --git a/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/response_handlers.py b/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/response_handlers.py
index bf92763aa509..fbf9889d762c 100644
--- a/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/response_handlers.py
+++ b/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/response_handlers.py
@@ -19,7 +19,8 @@
ClientAuthenticationError,
DecodeError)
-from .models import StorageErrorCode
+from .parser import _to_utc_datetime
+from .models import StorageErrorCode, UserDelegationKey
if TYPE_CHECKING:
@@ -131,3 +132,15 @@ def process_storage_error(storage_error):
error.error_code = error_code
error.additional_info = additional_data
raise error
+
+
+def parse_to_internal_user_delegation_key(service_user_delegation_key):
+ internal_user_delegation_key = UserDelegationKey()
+ internal_user_delegation_key.signed_oid = service_user_delegation_key.signed_oid
+ internal_user_delegation_key.signed_tid = service_user_delegation_key.signed_tid
+ internal_user_delegation_key.signed_start = _to_utc_datetime(service_user_delegation_key.signed_start)
+ internal_user_delegation_key.signed_expiry = _to_utc_datetime(service_user_delegation_key.signed_expiry)
+ internal_user_delegation_key.signed_service = service_user_delegation_key.signed_service
+ internal_user_delegation_key.signed_version = service_user_delegation_key.signed_version
+ internal_user_delegation_key.value = service_user_delegation_key.value
+ return internal_user_delegation_key